From 40d427e5bdf5c204c58022cef3b369f34e19b043 Mon Sep 17 00:00:00 2001 From: sv99 Date: Tue, 27 Jun 2023 17:51:26 +0300 Subject: [PATCH] Extract class Framework from AndrolibResources (#3105) --- .../src/main/java/brut/apktool/Main.java | 9 +- .../main/java/brut/androlib/ApkBuilder.java | 4 +- .../brut/androlib/res/AndrolibResources.java | 216 +--------------- .../java/brut/androlib/res/Framework.java | 244 ++++++++++++++++++ .../test/java/brut/androlib/TestUtils.java | 5 +- .../androlib/aapt1/SharedLibraryTest.java | 7 +- 6 files changed, 261 insertions(+), 224 deletions(-) create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/Framework.java diff --git a/brut.apktool/apktool-cli/src/main/java/brut/apktool/Main.java b/brut.apktool/apktool-cli/src/main/java/brut/apktool/Main.java index 3dc95d28d5..71e4f98dc4 100644 --- a/brut.apktool/apktool-cli/src/main/java/brut/apktool/Main.java +++ b/brut.apktool/apktool-cli/src/main/java/brut/apktool/Main.java @@ -22,6 +22,7 @@ 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 @@ private static void cmdBuild(CommandLine cli, Config config) { 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) { diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkBuilder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkBuilder.java index 4db35431da..e2c3013971 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkBuilder.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkBuilder.java @@ -20,6 +20,7 @@ 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 @@ private File[] parseUsesFramework(UsesFramework usesFramework) 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; } diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/AndrolibResources.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/AndrolibResources.java index 48039f791a..8d5ddcae02 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/AndrolibResources.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/AndrolibResources.java @@ -139,7 +139,8 @@ public ResPackage selectPkgWithMostResSpecs(ResPackage[] pkgs) { 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 @@ private ResPackage[] getResPackagesFromApk(ExtFile apkFile, ResTable resTable, b } } - 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 @@ private int getAaptVersion() { 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(); diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/Framework.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/Framework.java new file mode 100644 index 0000000000..ad3ab7e221 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/Framework.java @@ -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"); + } +} diff --git a/brut.apktool/apktool-lib/src/test/java/brut/androlib/TestUtils.java b/brut.apktool/apktool-lib/src/test/java/brut/androlib/TestUtils.java index 7e504aea80..727396daf7 100644 --- a/brut.apktool/apktool-lib/src/test/java/brut/androlib/TestUtils.java +++ b/brut.apktool/apktool-lib/src/test/java/brut/androlib/TestUtils.java @@ -18,6 +18,7 @@ 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 static byte[] readHeaderOfFile(File file, int size) throws IOException { 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 { diff --git a/brut.apktool/apktool-lib/src/test/java/brut/androlib/aapt1/SharedLibraryTest.java b/brut.apktool/apktool-lib/src/test/java/brut/androlib/aapt1/SharedLibraryTest.java index b2e258aef2..9e22e1b5ed 100644 --- a/brut.apktool/apktool-lib/src/test/java/brut/androlib/aapt1/SharedLibraryTest.java +++ b/brut.apktool/apktool-lib/src/test/java/brut/androlib/aapt1/SharedLibraryTest.java @@ -19,6 +19,7 @@ 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 void isFrameworkTaggingWorking() throws AndrolibException { 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 void isFrameworkInstallingWorking() throws AndrolibException { 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 void isSharedResourceDecodingAndRebuildingWorking() throws IOException, B 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