Extract class Framework from AndrolibResources (#3105)

This commit is contained in:
sv99 2023-06-27 17:51:26 +03:00 committed by GitHub
parent 9c495cae29
commit 40d427e5bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 261 additions and 224 deletions

View File

@ -22,6 +22,7 @@ import brut.androlib.exceptions.CantFindFrameworkResException;
import brut.androlib.exceptions.InFileNotFoundException;
import brut.androlib.exceptions.OutDirExistsException;
import brut.androlib.res.AndrolibResources;
import brut.androlib.res.Framework;
import brut.common.BrutException;
import brut.directory.DirectoryException;
import brut.directory.ExtFile;
@ -271,23 +272,23 @@ public class Main {
private static void cmdInstallFramework(CommandLine cli, Config config) throws AndrolibException {
String apkName = getLastArg(cli);
new AndrolibResources(config).installFramework(new File(apkName));
new Framework(config).installFramework(new File(apkName));
}
private static void cmdListFrameworks(CommandLine cli, Config config) throws AndrolibException {
new AndrolibResources(config).listFrameworkDirectory();
new Framework(config).listFrameworkDirectory();
}
private static void cmdPublicizeResources(CommandLine cli, Config config) throws AndrolibException {
String apkName = getLastArg(cli);
new AndrolibResources(config).publicizeResources(new File(apkName));
new Framework(config).publicizeResources(new File(apkName));
}
private static void cmdEmptyFrameworkDirectory(CommandLine cli, Config config) throws AndrolibException {
if (cli.hasOption("f") || cli.hasOption("force")) {
config.forceDeleteFramework = true;
}
new AndrolibResources(config).emptyFrameworkDirectory();
new Framework(config).emptyFrameworkDirectory();
}
private static String getLastArg(CommandLine cli) {

View File

@ -20,6 +20,7 @@ import brut.androlib.exceptions.AndrolibException;
import brut.androlib.meta.MetaInfo;
import brut.androlib.meta.UsesFramework;
import brut.androlib.res.AndrolibResources;
import brut.androlib.res.Framework;
import brut.androlib.res.data.ResConfigFlags;
import brut.androlib.res.xml.ResXmlPatcher;
import brut.androlib.src.SmaliBuilder;
@ -570,11 +571,12 @@ public class ApkBuilder {
return null;
}
Framework framework = new Framework(config);
String tag = usesFramework.tag;
File[] files = new File[ids.size()];
int i = 0;
for (int id : ids) {
files[i++] = mAndRes.getFrameworkApk(id, tag);
files[i++] = framework.getFrameworkApk(id, tag);
}
return files;
}

View File

@ -139,7 +139,8 @@ final public class AndrolibResources {
public ResPackage loadFrameworkPkg(ResTable resTable, int id)
throws AndrolibException {
File apk = getFrameworkApk(id, config.frameworkTag);
Framework framework = new Framework(config);
File apk = framework.getFrameworkApk(id, config.frameworkTag);
LOGGER.info("Loading resource table from file: " + apk);
mFramework = new ExtFile(apk);
@ -819,215 +820,6 @@ final public class AndrolibResources {
}
}
public File getFrameworkApk(int id, String frameTag)
throws AndrolibException {
File dir = getFrameworkDirectory();
File apk;
if (frameTag != null) {
apk = new File(dir, String.valueOf(id) + '-' + frameTag + ".apk");
if (apk.exists()) {
return apk;
}
}
apk = new File(dir, id + ".apk");
if (apk.exists()) {
return apk;
}
if (id == 1) {
try (InputStream in = getAndroidFrameworkResourcesAsStream();
OutputStream out = Files.newOutputStream(apk.toPath())) {
IOUtils.copy(in, out);
return apk;
} catch (IOException ex) {
throw new AndrolibException(ex);
}
}
throw new CantFindFrameworkResException(id);
}
public void emptyFrameworkDirectory() throws AndrolibException {
File dir = getFrameworkDirectory();
File apk;
apk = new File(dir, "1.apk");
if (! apk.exists()) {
LOGGER.warning("Can't empty framework directory, no file found at: " + apk.getAbsolutePath());
} else {
try {
if (apk.exists() && Objects.requireNonNull(dir.listFiles()).length > 1 && ! config.forceDeleteFramework) {
LOGGER.warning("More than default framework detected. Please run command with `--force` parameter to wipe framework directory.");
} else {
for (File file : Objects.requireNonNull(dir.listFiles())) {
if (file.isFile() && file.getName().endsWith(".apk")) {
LOGGER.info("Removing " + file.getName() + " framework file...");
//noinspection ResultOfMethodCallIgnored
file.delete();
}
}
}
} catch (NullPointerException e) {
throw new AndrolibException(e);
}
}
}
public void listFrameworkDirectory() throws AndrolibException {
File dir = getFrameworkDirectory();
if (dir == null) {
LOGGER.severe("No framework directory found. Nothing to list.");
return;
}
for (File file : Objects.requireNonNull(dir.listFiles())) {
if (file.isFile() && file.getName().endsWith(".apk")) {
LOGGER.info(file.getName());
}
}
}
public void installFramework(File frameFile) throws AndrolibException {
installFramework(frameFile, config.frameworkTag);
}
public void installFramework(File frameFile, String tag)
throws AndrolibException {
InputStream in = null;
ZipOutputStream out = null;
try {
ZipFile zip = new ZipFile(frameFile);
ZipEntry entry = zip.getEntry("resources.arsc");
if (entry == null) {
throw new AndrolibException("Can't find resources.arsc file");
}
in = zip.getInputStream(entry);
byte[] data = IOUtils.toByteArray(in);
ARSCData arsc = ARSCDecoder.decode(new ByteArrayInputStream(data), true, true);
publicizeResources(data, arsc.getFlagsOffsets());
File outFile = new File(getFrameworkDirectory(), arsc
.getOnePackage().getId()
+ (tag == null ? "" : '-' + tag)
+ ".apk");
out = new ZipOutputStream(Files.newOutputStream(outFile.toPath()));
out.setMethod(ZipOutputStream.STORED);
CRC32 crc = new CRC32();
crc.update(data);
entry = new ZipEntry("resources.arsc");
entry.setSize(data.length);
entry.setMethod(ZipOutputStream.STORED);
entry.setCrc(crc.getValue());
out.putNextEntry(entry);
out.write(data);
out.closeEntry();
//Write fake AndroidManifest.xml file to support original aapt
entry = zip.getEntry("AndroidManifest.xml");
if (entry != null) {
in = zip.getInputStream(entry);
byte[] manifest = IOUtils.toByteArray(in);
CRC32 manifestCrc = new CRC32();
manifestCrc.update(manifest);
entry.setSize(manifest.length);
entry.setCompressedSize(-1);
entry.setCrc(manifestCrc.getValue());
out.putNextEntry(entry);
out.write(manifest);
out.closeEntry();
}
zip.close();
LOGGER.info("Framework installed to: " + outFile);
} catch (IOException ex) {
throw new AndrolibException(ex);
} finally {
IOUtils.closeQuietly(in);
IOUtils.closeQuietly(out);
}
}
public void publicizeResources(File arscFile) throws AndrolibException {
byte[] data = new byte[(int) arscFile.length()];
try(InputStream in = Files.newInputStream(arscFile.toPath());
OutputStream out = Files.newOutputStream(arscFile.toPath())) {
//noinspection ResultOfMethodCallIgnored
in.read(data);
publicizeResources(data);
out.write(data);
} catch (IOException ex){
throw new AndrolibException(ex);
}
}
public void publicizeResources(byte[] arsc) throws AndrolibException {
publicizeResources(arsc, ARSCDecoder.decode(new ByteArrayInputStream(arsc), true, true).getFlagsOffsets());
}
public void publicizeResources(byte[] arsc, FlagsOffset[] flagsOffsets) {
for (FlagsOffset flags : flagsOffsets) {
int offset = flags.offset + 3;
int end = offset + 4 * flags.count;
while (offset < end) {
arsc[offset] |= (byte) 0x40;
offset += 4;
}
}
}
public File getFrameworkDirectory() throws AndrolibException {
if (mFrameworkDirectory != null) {
return mFrameworkDirectory;
}
String path;
// use default framework path or specified on the command line
path = config.frameworkDirectory;
File dir = new File(path);
if (!dir.isDirectory() && dir.isFile()) {
throw new AndrolibException("--frame-path is set to a file, not a directory.");
}
if (dir.getParentFile() != null && dir.getParentFile().isFile()) {
throw new AndrolibException("Please remove file at " + dir.getParentFile());
}
if (! dir.exists()) {
if (! dir.mkdirs()) {
if (config.frameworkDirectory != null) {
LOGGER.severe("Can't create Framework directory: " + dir);
}
throw new AndrolibException(String.format(
"Can't create directory: (%s). Pass a writable path with --frame-path {DIR}. ", dir
));
}
}
if (config.frameworkDirectory == null) {
if (! dir.canWrite()) {
LOGGER.severe(String.format("WARNING: Could not write to (%1$s), using %2$s instead...",
dir.getAbsolutePath(), System.getProperty("java.io.tmpdir")));
LOGGER.severe("Please be aware this is a volatile directory and frameworks could go missing, " +
"please utilize --frame-path if the default storage directory is unavailable");
dir = new File(System.getProperty("java.io.tmpdir"));
}
}
mFrameworkDirectory = dir;
return dir;
}
private File getAaptBinaryFile() throws AndrolibException {
try {
@ -1044,10 +836,6 @@ final public class AndrolibResources {
return config.isAapt2() ? 2 : 1;
}
public InputStream getAndroidFrameworkResourcesAsStream() {
return Jar.class.getResourceAsStream("/brut/androlib/android-framework.jar");
}
public void close() throws IOException {
if (mFramework != null) {
mFramework.close();

View File

@ -0,0 +1,244 @@
package brut.androlib.res;
import brut.androlib.Config;
import brut.androlib.exceptions.AndrolibException;
import brut.androlib.exceptions.CantFindFrameworkResException;
import brut.androlib.res.decoder.ARSCDecoder;
import brut.util.Jar;
import org.apache.commons.io.IOUtils;
import java.io.*;
import java.nio.file.Files;
import java.util.Objects;
import java.util.logging.Logger;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
public class Framework {
private final Config config;
private File mFrameworkDirectory = null;
private final static Logger LOGGER = Logger.getLogger(Framework.class.getName());
public Framework(Config config) {
this.config = config;
}
public void installFramework(File frameFile) throws AndrolibException {
installFramework(frameFile, config.frameworkTag);
}
public void installFramework(File frameFile, String tag)
throws AndrolibException {
InputStream in = null;
ZipOutputStream out = null;
try {
ZipFile zip = new ZipFile(frameFile);
ZipEntry entry = zip.getEntry("resources.arsc");
if (entry == null) {
throw new AndrolibException("Can't find resources.arsc file");
}
in = zip.getInputStream(entry);
byte[] data = IOUtils.toByteArray(in);
ARSCDecoder.ARSCData arsc = ARSCDecoder.decode(new ByteArrayInputStream(data), true, true);
publicizeResources(data, arsc.getFlagsOffsets());
File outFile = new File(getFrameworkDirectory(), arsc
.getOnePackage().getId()
+ (tag == null ? "" : '-' + tag)
+ ".apk");
out = new ZipOutputStream(Files.newOutputStream(outFile.toPath()));
out.setMethod(ZipOutputStream.STORED);
CRC32 crc = new CRC32();
crc.update(data);
entry = new ZipEntry("resources.arsc");
entry.setSize(data.length);
entry.setMethod(ZipOutputStream.STORED);
entry.setCrc(crc.getValue());
out.putNextEntry(entry);
out.write(data);
out.closeEntry();
//Write fake AndroidManifest.xml file to support original aapt
entry = zip.getEntry("AndroidManifest.xml");
if (entry != null) {
in = zip.getInputStream(entry);
byte[] manifest = IOUtils.toByteArray(in);
CRC32 manifestCrc = new CRC32();
manifestCrc.update(manifest);
entry.setSize(manifest.length);
entry.setCompressedSize(-1);
entry.setCrc(manifestCrc.getValue());
out.putNextEntry(entry);
out.write(manifest);
out.closeEntry();
}
zip.close();
LOGGER.info("Framework installed to: " + outFile);
} catch (IOException ex) {
throw new AndrolibException(ex);
} finally {
IOUtils.closeQuietly(in);
IOUtils.closeQuietly(out);
}
}
public void listFrameworkDirectory() throws AndrolibException {
File dir = getFrameworkDirectory();
if (dir == null) {
LOGGER.severe("No framework directory found. Nothing to list.");
return;
}
for (File file : Objects.requireNonNull(dir.listFiles())) {
if (file.isFile() && file.getName().endsWith(".apk")) {
LOGGER.info(file.getName());
}
}
}
public void publicizeResources(File arscFile) throws AndrolibException {
byte[] data = new byte[(int) arscFile.length()];
try(InputStream in = Files.newInputStream(arscFile.toPath());
OutputStream out = Files.newOutputStream(arscFile.toPath())) {
//noinspection ResultOfMethodCallIgnored
in.read(data);
publicizeResources(data);
out.write(data);
} catch (IOException ex){
throw new AndrolibException(ex);
}
}
public void publicizeResources(byte[] arsc) throws AndrolibException {
publicizeResources(arsc, ARSCDecoder.decode(new ByteArrayInputStream(arsc), true, true).getFlagsOffsets());
}
public void publicizeResources(byte[] arsc, ARSCDecoder.FlagsOffset[] flagsOffsets) {
for (ARSCDecoder.FlagsOffset flags : flagsOffsets) {
int offset = flags.offset + 3;
int end = offset + 4 * flags.count;
while (offset < end) {
arsc[offset] |= (byte) 0x40;
offset += 4;
}
}
}
public File getFrameworkDirectory() throws AndrolibException {
if (mFrameworkDirectory != null) {
return mFrameworkDirectory;
}
String path;
// use default framework path or specified on the command line
path = config.frameworkDirectory;
File dir = new File(path);
if (!dir.isDirectory() && dir.isFile()) {
throw new AndrolibException("--frame-path is set to a file, not a directory.");
}
if (dir.getParentFile() != null && dir.getParentFile().isFile()) {
throw new AndrolibException("Please remove file at " + dir.getParentFile());
}
if (! dir.exists()) {
if (! dir.mkdirs()) {
if (config.frameworkDirectory != null) {
LOGGER.severe("Can't create Framework directory: " + dir);
}
throw new AndrolibException(String.format(
"Can't create directory: (%s). Pass a writable path with --frame-path {DIR}. ", dir
));
}
}
if (config.frameworkDirectory == null) {
if (! dir.canWrite()) {
LOGGER.severe(String.format("WARNING: Could not write to (%1$s), using %2$s instead...",
dir.getAbsolutePath(), System.getProperty("java.io.tmpdir")));
LOGGER.severe("Please be aware this is a volatile directory and frameworks could go missing, " +
"please utilize --frame-path if the default storage directory is unavailable");
dir = new File(System.getProperty("java.io.tmpdir"));
}
}
mFrameworkDirectory = dir;
return dir;
}
public File getFrameworkApk(int id, String frameTag)
throws AndrolibException {
File dir = getFrameworkDirectory();
File apk;
if (frameTag != null) {
apk = new File(dir, String.valueOf(id) + '-' + frameTag + ".apk");
if (apk.exists()) {
return apk;
}
}
apk = new File(dir, id + ".apk");
if (apk.exists()) {
return apk;
}
if (id == 1) {
try (InputStream in = getAndroidFrameworkResourcesAsStream();
OutputStream out = Files.newOutputStream(apk.toPath())) {
IOUtils.copy(in, out);
return apk;
} catch (IOException ex) {
throw new AndrolibException(ex);
}
}
throw new CantFindFrameworkResException(id);
}
public void emptyFrameworkDirectory() throws AndrolibException {
File dir = getFrameworkDirectory();
File apk;
apk = new File(dir, "1.apk");
if (! apk.exists()) {
LOGGER.warning("Can't empty framework directory, no file found at: " + apk.getAbsolutePath());
} else {
try {
if (apk.exists() && Objects.requireNonNull(dir.listFiles()).length > 1 && ! config.forceDeleteFramework) {
LOGGER.warning("More than default framework detected. Please run command with `--force` parameter to wipe framework directory.");
} else {
for (File file : Objects.requireNonNull(dir.listFiles())) {
if (file.isFile() && file.getName().endsWith(".apk")) {
LOGGER.info("Removing " + file.getName() + " framework file...");
//noinspection ResultOfMethodCallIgnored
file.delete();
}
}
}
} catch (NullPointerException e) {
throw new AndrolibException(e);
}
}
}
private InputStream getAndroidFrameworkResourcesAsStream() {
return Jar.class.getResourceAsStream("/brut/androlib/android-framework.jar");
}
}

View File

@ -18,6 +18,7 @@ package brut.androlib;
import brut.androlib.exceptions.AndrolibException;
import brut.androlib.res.AndrolibResources;
import brut.androlib.res.Framework;
import brut.common.BrutException;
import brut.directory.DirUtil;
import brut.directory.Directory;
@ -138,8 +139,8 @@ public abstract class TestUtils {
static File getFrameworkDirectory() throws AndrolibException {
Config config = Config.getDefaultConfig();
AndrolibResources androlibResources = new AndrolibResources(config);
return androlibResources.getFrameworkDirectory();
Framework framework = new Framework(config);
return framework.getFrameworkDirectory();
}
public static class ResValueElementQualifier implements ElementQualifier {

View File

@ -19,6 +19,7 @@ package brut.androlib.aapt1;
import brut.androlib.*;
import brut.androlib.exceptions.AndrolibException;
import brut.androlib.res.AndrolibResources;
import brut.androlib.res.Framework;
import brut.directory.ExtFile;
import brut.common.BrutException;
import brut.util.OS;
@ -55,7 +56,7 @@ public class SharedLibraryTest extends BaseTest {
config.frameworkDirectory = sTmpDir.getAbsolutePath();
config.frameworkTag = "building";
new AndrolibResources(config).installFramework(new File(sTmpDir + File.separator + apkName));
new Framework(config).installFramework(new File(sTmpDir + File.separator + apkName));
assertTrue(fileExists("2-building.apk"));
}
@ -67,7 +68,7 @@ public class SharedLibraryTest extends BaseTest {
Config config = Config.getDefaultConfig();
config.frameworkDirectory = sTmpDir.getAbsolutePath();
new AndrolibResources(config).installFramework(new File(sTmpDir + File.separator + apkName));
new Framework(config).installFramework(new File(sTmpDir + File.separator + apkName));
assertTrue(fileExists("2.apk"));
}
@ -83,7 +84,7 @@ public class SharedLibraryTest extends BaseTest {
config.frameworkTag = "shared";
// install library/framework
new AndrolibResources(config).installFramework(new File(sTmpDir + File.separator + library));
new Framework(config).installFramework(new File(sTmpDir + File.separator + library));
assertTrue(fileExists("2-shared.apk"));
// decode client.apk