From fd823f07f39c58f1995cec1ad8c910344dee1643 Mon Sep 17 00:00:00 2001 From: Slava Volkov Date: Fri, 30 Jun 2023 13:03:50 +0300 Subject: [PATCH 1/5] extract AaptInvoker and rename MetaFile to ApkInfo, all decode methods from AndrolibResources moved to the ApkDecoder --- .../src/main/java/brut/apktool/Main.java | 1 - .../main/java/brut/androlib/AaptInvoker.java | 374 ++++++++ .../main/java/brut/androlib/ApkBuilder.java | 152 ++-- .../main/java/brut/androlib/ApkDecoder.java | 341 +++++-- .../src/main/java/brut/androlib/Config.java | 3 - .../main/java/brut/androlib/apk/ApkInfo.java | 172 ++++ .../{meta => apk}/ClassSafeConstructor.java | 4 +- .../EscapedStringRepresenter.java | 2 +- .../androlib/{meta => apk}/PackageInfo.java | 2 +- .../androlib/{meta => apk}/UsesFramework.java | 2 +- .../androlib/{meta => apk}/VersionInfo.java | 2 +- .../{meta => apk}/YamlStringEscapeUtils.java | 2 +- .../java/brut/androlib/meta/MetaInfo.java | 90 -- .../brut/androlib/res/AndrolibResources.java | 835 ------------------ .../java/brut/androlib/res/Framework.java | 2 +- .../brut/androlib/res/ResourcesDecoder.java | 39 + .../java/brut/androlib/res/data/ResTable.java | 220 +++-- .../src/test/java/brut/androlib/BaseTest.java | 6 +- .../test/java/brut/androlib/TestUtils.java | 1 - .../androlib/aapt1/BuildAndDecodeTest.java | 10 +- .../aapt1/ReferenceVersionCodeTest.java | 7 +- .../androlib/aapt1/SharedLibraryTest.java | 1 - .../androlib/aapt2/BuildAndDecodeTest.java | 11 +- .../androlib/InvalidSdkBoundingTest.java | 46 +- .../DoubleExtensionUnknownFileTest.java | 7 +- .../decode/MissingVersionManifestTest.java | 7 +- 26 files changed, 1145 insertions(+), 1194 deletions(-) create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/AaptInvoker.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/ApkInfo.java rename brut.apktool/apktool-lib/src/main/java/brut/androlib/{meta => apk}/ClassSafeConstructor.java (96%) rename brut.apktool/apktool-lib/src/main/java/brut/androlib/{meta => apk}/EscapedStringRepresenter.java (98%) rename brut.apktool/apktool-lib/src/main/java/brut/androlib/{meta => apk}/PackageInfo.java (96%) rename brut.apktool/apktool-lib/src/main/java/brut/androlib/{meta => apk}/UsesFramework.java (96%) rename brut.apktool/apktool-lib/src/main/java/brut/androlib/{meta => apk}/VersionInfo.java (96%) rename brut.apktool/apktool-lib/src/main/java/brut/androlib/{meta => apk}/YamlStringEscapeUtils.java (99%) delete mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/meta/MetaInfo.java delete mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/AndrolibResources.java create mode 100644 brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResourcesDecoder.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 71e4f98dc4..4e400a1cd0 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 @@ -21,7 +21,6 @@ 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; diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/AaptInvoker.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/AaptInvoker.java new file mode 100644 index 0000000000..08e63a050a --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/AaptInvoker.java @@ -0,0 +1,374 @@ +package brut.androlib; + +import brut.androlib.exceptions.AndrolibException; +import brut.androlib.apk.ApkInfo; +import brut.common.BrutException; +import brut.util.AaptManager; +import brut.util.OS; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.logging.Logger; + +public class AaptInvoker { + + private final Config mConfig; + private final ApkInfo mApkInfo; + private final File mApkDir; + + private final static Logger LOGGER = Logger.getLogger(AaptInvoker.class.getName()); + + public AaptInvoker(Config config, ApkInfo apkInfo, File apkDir) { + mConfig = config; + mApkInfo = apkInfo; + mApkDir = apkDir; + } + + private File getAaptBinaryFile() throws AndrolibException { + try { + if (getAaptVersion() == 2) { + return AaptManager.getAapt2(); + } + return AaptManager.getAapt1(); + } catch (BrutException ex) { + throw new AndrolibException(ex); + } + } + + private int getAaptVersion() { + return mConfig.isAapt2() ? 2 : 1; + } + + private File createDoNotCompressExtensionsFile(ApkInfo apkInfo) throws AndrolibException { + if (apkInfo.doNotCompress == null || apkInfo.doNotCompress.isEmpty()) { + return null; + } + + File doNotCompressFile; + try { + doNotCompressFile = File.createTempFile("APKTOOL", null); + doNotCompressFile.deleteOnExit(); + + BufferedWriter fileWriter = new BufferedWriter(new FileWriter(doNotCompressFile)); + for (String extension : apkInfo.doNotCompress) { + fileWriter.write(extension); + fileWriter.newLine(); + } + fileWriter.close(); + + return doNotCompressFile; + } catch (IOException ex) { + throw new AndrolibException(ex); + } + } + + private void invokeAapt2(File apkFile, File manifest, File resDir, File rawDir, File assetDir, File[] include, + List cmd, boolean customAapt) + throws AndrolibException { + + List compileCommand = new ArrayList<>(cmd); + File resourcesZip = null; + + if (resDir != null) { + File buildDir = new File(resDir.getParent(), "build"); + resourcesZip = new File(buildDir, "resources.zip"); + } + + if (resDir != null && !resourcesZip.exists()) { + + // Compile the files into flat arsc files + cmd.add("compile"); + + cmd.add("--dir"); + cmd.add(resDir.getAbsolutePath()); + + // Treats error that used to be valid in aapt1 as warnings in aapt2 + cmd.add("--legacy"); + + File buildDir = new File(resDir.getParent(), "build"); + resourcesZip = new File(buildDir, "resources.zip"); + + cmd.add("-o"); + cmd.add(resourcesZip.getAbsolutePath()); + + if (mConfig.verbose) { + cmd.add("-v"); + } + + if (mConfig.noCrunch) { + cmd.add("--no-crunch"); + } + + try { + OS.exec(cmd.toArray(new String[0])); + LOGGER.fine("aapt2 compile command ran: "); + LOGGER.fine(cmd.toString()); + } catch (BrutException ex) { + throw new AndrolibException(ex); + } + } + + if (manifest == null) { + return; + } + + // Link them into the final apk, reusing our old command after clearing for the aapt2 binary + cmd = new ArrayList<>(compileCommand); + cmd.add("link"); + + cmd.add("-o"); + cmd.add(apkFile.getAbsolutePath()); + + if (mApkInfo.packageInfo.forcedPackageId != null && ! mApkInfo.sharedLibrary) { + cmd.add("--package-id"); + cmd.add(mApkInfo.packageInfo.forcedPackageId); + } + + if (mApkInfo.sharedLibrary) { + cmd.add("--shared-lib"); + } + + if (mApkInfo.getMinSdkVersion() != null) { + cmd.add("--min-sdk-version"); + cmd.add(mApkInfo.getMinSdkVersion() ); + } + + if (mApkInfo.getTargetSdkVersion() != null) { + cmd.add("--target-sdk-version"); + cmd.add(mApkInfo.checkTargetSdkVersionBounds()); + } + + if (mApkInfo.packageInfo.renameManifestPackage != null) { + cmd.add("--rename-manifest-package"); + cmd.add(mApkInfo.packageInfo.renameManifestPackage); + + cmd.add("--rename-instrumentation-target-package"); + cmd.add(mApkInfo.packageInfo.renameManifestPackage); + } + + if (mApkInfo.versionInfo.versionCode != null) { + cmd.add("--version-code"); + cmd.add(mApkInfo.versionInfo.versionCode); + } + + if (mApkInfo.versionInfo.versionName != null) { + cmd.add("--version-name"); + cmd.add(mApkInfo.versionInfo.versionName); + } + + // Disable automatic changes + cmd.add("--no-auto-version"); + cmd.add("--no-version-vectors"); + cmd.add("--no-version-transitions"); + cmd.add("--no-resource-deduping"); + + cmd.add("--allow-reserved-package-id"); + + if (mApkInfo.sparseResources) { + cmd.add("--enable-sparse-encoding"); + } + + if (mApkInfo.isFrameworkApk) { + cmd.add("-x"); + } + + if (mApkInfo.doNotCompress != null && !customAapt) { + // Use custom -e option to avoid limits on commandline length. + // Can only be used when custom aapt binary is not used. + String extensionsFilePath = + Objects.requireNonNull(createDoNotCompressExtensionsFile(mApkInfo)).getAbsolutePath(); + cmd.add("-e"); + cmd.add(extensionsFilePath); + } else if (mApkInfo.doNotCompress != null) { + for (String file : mApkInfo.doNotCompress) { + cmd.add("-0"); + cmd.add(file); + } + } + + if (!mApkInfo.resourcesAreCompressed) { + cmd.add("-0"); + cmd.add("arsc"); + } + + if (include != null) { + for (File file : include) { + cmd.add("-I"); + cmd.add(file.getPath()); + } + } + + cmd.add("--manifest"); + cmd.add(manifest.getAbsolutePath()); + + if (assetDir != null) { + cmd.add("-A"); + cmd.add(assetDir.getAbsolutePath()); + } + + if (rawDir != null) { + cmd.add("-R"); + cmd.add(rawDir.getAbsolutePath()); + } + + if (mConfig.verbose) { + cmd.add("-v"); + } + + if (resourcesZip != null) { + cmd.add(resourcesZip.getAbsolutePath()); + } + + try { + OS.exec(cmd.toArray(new String[0])); + LOGGER.fine("aapt2 link command ran: "); + LOGGER.fine(cmd.toString()); + } catch (BrutException ex) { + throw new AndrolibException(ex); + } + } + + private void invokeAapt1(File apkFile, File manifest, File resDir, File rawDir, File assetDir, File[] include, + List cmd, boolean customAapt) + throws AndrolibException { + + cmd.add("p"); + + if (mConfig.verbose) { // output aapt verbose + cmd.add("-v"); + } + if (mConfig.updateFiles) { + cmd.add("-u"); + } + if (mConfig.debugMode) { // inject debuggable="true" into manifest + cmd.add("--debug-mode"); + } + if (mConfig.noCrunch) { + cmd.add("--no-crunch"); + } + // force package id so that some frameworks build with correct id + // disable if user adds own aapt (can't know if they have this feature) + if (mApkInfo.packageInfo.forcedPackageId != null && ! customAapt && ! mApkInfo.sharedLibrary) { + cmd.add("--forced-package-id"); + cmd.add(mApkInfo.packageInfo.forcedPackageId); + } + if (mApkInfo.sharedLibrary) { + cmd.add("--shared-lib"); + } + if (mApkInfo.getMinSdkVersion() != null) { + cmd.add("--min-sdk-version"); + cmd.add(mApkInfo.getMinSdkVersion()); + } + if (mApkInfo.getTargetSdkVersion() != null) { + cmd.add("--target-sdk-version"); + + // Ensure that targetSdkVersion is between minSdkVersion/maxSdkVersion if + // they are specified. + cmd.add(mApkInfo.checkTargetSdkVersionBounds()); + } + if (mApkInfo.getMaxSdkVersion() != null) { + cmd.add("--max-sdk-version"); + cmd.add(mApkInfo.getMaxSdkVersion()); + + // if we have max sdk version, set --max-res-version, + // so we can ignore anything over that during build. + cmd.add("--max-res-version"); + cmd.add(mApkInfo.getMaxSdkVersion()); + } + if (mApkInfo.packageInfo.renameManifestPackage != null) { + cmd.add("--rename-manifest-package"); + cmd.add(mApkInfo.packageInfo.renameManifestPackage); + } + if (mApkInfo.versionInfo.versionCode != null) { + cmd.add("--version-code"); + cmd.add(mApkInfo.versionInfo.versionCode); + } + if (mApkInfo.versionInfo.versionName != null) { + cmd.add("--version-name"); + cmd.add(mApkInfo.versionInfo.versionName); + } + cmd.add("--no-version-vectors"); + cmd.add("-F"); + cmd.add(apkFile.getAbsolutePath()); + + if (mApkInfo.isFrameworkApk) { + cmd.add("-x"); + } + + if (mApkInfo.doNotCompress != null && !customAapt) { + // Use custom -e option to avoid limits on commandline length. + // Can only be used when custom aapt binary is not used. + String extensionsFilePath = + Objects.requireNonNull(createDoNotCompressExtensionsFile(mApkInfo)).getAbsolutePath(); + cmd.add("-e"); + cmd.add(extensionsFilePath); + } else if (mApkInfo.doNotCompress != null) { + for (String file : mApkInfo.doNotCompress) { + cmd.add("-0"); + cmd.add(file); + } + } + + if (!mApkInfo.resourcesAreCompressed) { + cmd.add("-0"); + cmd.add("arsc"); + } + + if (include != null) { + for (File file : include) { + cmd.add("-I"); + cmd.add(file.getPath()); + } + } + if (resDir != null) { + cmd.add("-S"); + cmd.add(resDir.getAbsolutePath()); + } + if (manifest != null) { + cmd.add("-M"); + cmd.add(manifest.getAbsolutePath()); + } + if (assetDir != null) { + cmd.add("-A"); + cmd.add(assetDir.getAbsolutePath()); + } + if (rawDir != null) { + cmd.add(rawDir.getAbsolutePath()); + } + try { + OS.exec(cmd.toArray(new String[0])); + LOGGER.fine("command ran: "); + LOGGER.fine(cmd.toString()); + } catch (BrutException ex) { + throw new AndrolibException(ex); + } + } + + public void invokeAapt(File apkFile, File manifest, File resDir, File rawDir, File assetDir, File[] include) + throws AndrolibException { + + String aaptPath = mConfig.aaptPath; + boolean customAapt = !aaptPath.isEmpty(); + List cmd = new ArrayList<>(); + + try { + String aaptCommand = AaptManager.getAaptExecutionCommand(aaptPath, getAaptBinaryFile()); + cmd.add(aaptCommand); + } catch (BrutException ex) { + LOGGER.warning("aapt: " + ex.getMessage() + " (defaulting to $PATH binary)"); + cmd.add(AaptManager.getAaptBinaryName(getAaptVersion())); + } + + if (mConfig.isAapt2()) { + invokeAapt2(apkFile, manifest, resDir, rawDir, assetDir, include, cmd, customAapt); + return; + } + invokeAapt1(apkFile, manifest, resDir, rawDir, assetDir, include, cmd, customAapt); + } + +} 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 e2c3013971..b252cbd183 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 @@ -17,9 +17,9 @@ package brut.androlib; import brut.androlib.exceptions.AndrolibException; -import brut.androlib.meta.MetaInfo; -import brut.androlib.meta.UsesFramework; -import brut.androlib.res.AndrolibResources; +import brut.androlib.apk.ApkInfo; +import brut.androlib.apk.UsesFramework; +import brut.androlib.res.ResourcesDecoder; import brut.androlib.res.Framework; import brut.androlib.res.data.ResConfigFlags; import brut.androlib.res.xml.ResXmlPatcher; @@ -31,16 +31,16 @@ import brut.directory.Directory; import brut.directory.DirectoryException; import brut.directory.ExtFile; +import brut.directory.ZipUtils; import brut.util.BrutIO; import brut.util.OS; import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; import org.xml.sax.SAXException; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; -import java.io.BufferedInputStream; -import java.io.File; -import java.io.IOException; +import java.io.*; import java.nio.file.Files; import java.util.*; import java.util.logging.Logger; @@ -52,8 +52,8 @@ public class ApkBuilder { private final static Logger LOGGER = Logger.getLogger(ApkBuilder.class.getName()); - private final AndrolibResources mAndRes; - private final Config config; + private final Config mConfig; + private ApkInfo mApkInfo; private int mMinSdkVersion = 0; private final ExtFile mApkDir; @@ -73,34 +73,22 @@ public ApkBuilder(ExtFile apkDir) { } public ApkBuilder(Config config, ExtFile apkDir) { - this.config = config; - mAndRes = new AndrolibResources(config); + mConfig = config; mApkDir = apkDir; } - public void build(File outFile) - throws BrutException { + public void build(File outFile) throws BrutException { LOGGER.info("Using Apktool " + ApktoolProperties.getVersion()); - MetaInfo meta = MetaInfo.readMetaFile(mApkDir); - config.isFramework = meta.isFrameworkApk; - config.resourcesAreCompressed = meta.compressionType; - config.doNotCompress = meta.doNotCompress; - - mAndRes.setSdkInfo(meta.sdkInfo); - mAndRes.setPackageId(meta.packageInfo); - mAndRes.setPackageRenamed(meta.packageInfo); - mAndRes.setVersionInfo(meta.versionInfo); - mAndRes.setSharedLibrary(meta.sharedLibrary); - mAndRes.setSparseResources(meta.sparseResources); + mApkInfo = ApkInfo.load(mApkDir); - if (meta.sdkInfo != null && meta.sdkInfo.get("minSdkVersion") != null) { - String minSdkVersion = meta.sdkInfo.get("minSdkVersion"); - mMinSdkVersion = mAndRes.getMinSdkVersionFromAndroidCodename(meta, minSdkVersion); + if (mApkInfo.getSdkInfo() != null && mApkInfo.getSdkInfo().get("minSdkVersion") != null) { + String minSdkVersion = mApkInfo.getSdkInfo().get("minSdkVersion"); + mMinSdkVersion = mApkInfo.getMinSdkVersionFromAndroidCodename(minSdkVersion); } if (outFile == null) { - String outFileName = meta.apkFileName; + String outFileName = mApkInfo.getApkFileName(); outFile = new File(mApkDir, "dist" + File.separator + (outFileName == null ? "out.apk" : outFileName)); } @@ -112,14 +100,14 @@ public void build(File outFile) buildSources(mApkDir); buildNonDefaultSources(mApkDir); buildManifestFile(mApkDir, manifest, manifestOriginal); - buildResources(mApkDir, meta.usesFramework); + buildResources(mApkDir, mApkInfo.usesFramework); buildLibs(mApkDir); buildCopyOriginalFiles(mApkDir); buildApk(mApkDir, outFile); // we must go after the Apk is built, and copy the files in via Zip // this is because Aapt won't add files it doesn't know (ex unknown files) - buildUnknownFiles(mApkDir, outFile, meta); + buildUnknownFiles(mApkDir, outFile, mApkInfo); // we copied the AndroidManifest.xml to AndroidManifest.xml.orig so we can edit it // lets restore the unedited one, to not change the original @@ -156,14 +144,14 @@ private void buildManifestFile(File appDir, File manifest, File manifestOriginal } } - public void buildSources(File appDir) + private void buildSources(File appDir) throws AndrolibException { if (!buildSourcesRaw(appDir, "classes.dex") && !buildSourcesSmali(appDir, "smali", "classes.dex")) { LOGGER.warning("Could not find sources"); } } - public void buildNonDefaultSources(ExtFile appDir) + private void buildNonDefaultSources(ExtFile appDir) throws AndrolibException { try { // loop through any smali_ directories for multi-dex apks @@ -195,14 +183,14 @@ public void buildNonDefaultSources(ExtFile appDir) } } - public boolean buildSourcesRaw(File appDir, String filename) + private boolean buildSourcesRaw(File appDir, String filename) throws AndrolibException { File working = new File(appDir, filename); if (!working.exists()) { return false; } File stored = new File(appDir, APK_DIRNAME + "/" + filename); - if (config.forceBuildAll || isModified(working, stored)) { + if (mConfig.forceBuildAll || isModified(working, stored)) { LOGGER.info("Copying " + appDir.toString() + " " + filename + " file..."); try { BrutIO.copyAndClose(Files.newInputStream(working.toPath()), Files.newOutputStream(stored.toPath())); @@ -214,26 +202,26 @@ public boolean buildSourcesRaw(File appDir, String filename) return true; } - public boolean buildSourcesSmali(File appDir, String folder, String filename) + private boolean buildSourcesSmali(File appDir, String folder, String filename) throws AndrolibException { ExtFile smaliDir = new ExtFile(appDir, folder); if (!smaliDir.exists()) { return false; } File dex = new File(appDir, APK_DIRNAME + "/" + filename); - if (! config.forceBuildAll) { + if (! mConfig.forceBuildAll) { LOGGER.info("Checking whether sources has changed..."); } - if (config.forceBuildAll || isModified(smaliDir, dex)) { + if (mConfig.forceBuildAll || isModified(smaliDir, dex)) { LOGGER.info("Smaling " + folder + " folder into " + filename + "..."); //noinspection ResultOfMethodCallIgnored dex.delete(); - SmaliBuilder.build(smaliDir, dex, config.forceApi > 0 ? config.forceApi : mMinSdkVersion); + SmaliBuilder.build(smaliDir, dex, mConfig.forceApi > 0 ? mConfig.forceApi : mMinSdkVersion); } return true; } - public void buildResources(ExtFile appDir, UsesFramework usesFramework) + private void buildResources(ExtFile appDir, UsesFramework usesFramework) throws BrutException { if (!buildResourcesRaw(appDir) && !buildResourcesFull(appDir, usesFramework) && !buildManifest(appDir, usesFramework)) { @@ -241,17 +229,17 @@ public void buildResources(ExtFile appDir, UsesFramework usesFramework) } } - public boolean buildResourcesRaw(ExtFile appDir) + private boolean buildResourcesRaw(ExtFile appDir) throws AndrolibException { try { if (!new File(appDir, "resources.arsc").exists()) { return false; } File apkDir = new File(appDir, APK_DIRNAME); - if (! config.forceBuildAll) { + if (! mConfig.forceBuildAll) { LOGGER.info("Checking whether resources has changed..."); } - if (config.forceBuildAll || isModified(newFiles(APK_RESOURCES_FILENAMES, appDir), + if (mConfig.forceBuildAll || isModified(newFiles(APK_RESOURCES_FILENAMES, appDir), newFiles(APK_RESOURCES_FILENAMES, apkDir))) { LOGGER.info("Copying raw resources..."); appDir.getDirectory().copyToDir(apkDir, APK_RESOURCES_FILENAMES); @@ -262,24 +250,24 @@ public boolean buildResourcesRaw(ExtFile appDir) } } - public boolean buildResourcesFull(File appDir, UsesFramework usesFramework) + private boolean buildResourcesFull(File appDir, UsesFramework usesFramework) throws AndrolibException { try { if (!new File(appDir, "res").exists()) { return false; } - if (! config.forceBuildAll) { + if (! mConfig.forceBuildAll) { LOGGER.info("Checking whether resources has changed..."); } File apkDir = new File(appDir, APK_DIRNAME); File resourceFile = new File(apkDir.getParent(), "resources.zip"); - if (config.forceBuildAll || isModified(newFiles(APP_RESOURCES_FILENAMES, appDir), - newFiles(APK_RESOURCES_FILENAMES, apkDir)) || (config.isAapt2() && !isFile(resourceFile))) { + if (mConfig.forceBuildAll || isModified(newFiles(APP_RESOURCES_FILENAMES, appDir), + newFiles(APK_RESOURCES_FILENAMES, apkDir)) || (mConfig.isAapt2() && !isFile(resourceFile))) { LOGGER.info("Building resources..."); - if (config.debugMode) { - if (config.isAapt2()) { + if (mConfig.debugMode) { + if (mConfig.isAapt2()) { LOGGER.info("Using aapt2 - setting 'debuggable' attribute to 'true' in AndroidManifest.xml"); ResXmlPatcher.setApplicationDebugTagTrue(new File(appDir, "AndroidManifest.xml")); } else { @@ -287,10 +275,10 @@ public boolean buildResourcesFull(File appDir, UsesFramework usesFramework) } } - if (config.netSecConf) { - MetaInfo meta = MetaInfo.readMetaFile(new ExtFile(appDir)); - if (meta.sdkInfo != null && meta.sdkInfo.get("targetSdkVersion") != null) { - if (Integer.parseInt(meta.sdkInfo.get("targetSdkVersion")) < ResConfigFlags.SDK_NOUGAT) { + if (mConfig.netSecConf) { + ApkInfo meta = ApkInfo.load(new ExtFile(appDir)); + if (meta.getSdkInfo() != null && meta.getSdkInfo().get("targetSdkVersion") != null) { + if (Integer.parseInt(meta.getSdkInfo().get("targetSdkVersion")) < ResConfigFlags.SDK_NOUGAT) { LOGGER.warning("Target SDK version is lower than 24! Network Security Configuration might be ignored!"); } } @@ -315,7 +303,8 @@ public boolean buildResourcesFull(File appDir, UsesFramework usesFramework) if (!ninePatch.exists()) { ninePatch = null; } - mAndRes.aaptPackage(apkFile, new File(appDir, + AaptInvoker invoker = new AaptInvoker(mConfig, mApkInfo, apkDir); + invoker.invokeAapt(apkFile, new File(appDir, "AndroidManifest.xml"), new File(appDir, "res"), ninePatch, null, parseUsesFramework(usesFramework)); @@ -345,7 +334,7 @@ public boolean buildResourcesFull(File appDir, UsesFramework usesFramework) } } - public boolean buildManifestRaw(ExtFile appDir) + private boolean buildManifestRaw(ExtFile appDir) throws AndrolibException { try { File apkDir = new File(appDir, APK_DIRNAME); @@ -357,19 +346,19 @@ public boolean buildManifestRaw(ExtFile appDir) } } - public boolean buildManifest(ExtFile appDir, UsesFramework usesFramework) + private boolean buildManifest(ExtFile appDir, UsesFramework usesFramework) throws BrutException { try { if (!new File(appDir, "AndroidManifest.xml").exists()) { return false; } - if (! config.forceBuildAll) { + if (! mConfig.forceBuildAll) { LOGGER.info("Checking whether resources has changed..."); } File apkDir = new File(appDir, APK_DIRNAME); - if (config.forceBuildAll || isModified(newFiles(APK_MANIFEST_FILENAMES, appDir), + if (mConfig.forceBuildAll || isModified(newFiles(APK_MANIFEST_FILENAMES, appDir), newFiles(APK_MANIFEST_FILENAMES, apkDir))) { LOGGER.info("Building AndroidManifest.xml..."); @@ -382,7 +371,8 @@ public boolean buildManifest(ExtFile appDir, UsesFramework usesFramework) ninePatch = null; } - mAndRes.aaptPackage(apkFile, new File(appDir, + AaptInvoker invoker = new AaptInvoker(mConfig, mApkInfo, apkDir); + invoker.invokeAapt(apkFile, new File(appDir, "AndroidManifest.xml"), null, ninePatch, null, parseUsesFramework(usesFramework)); @@ -401,14 +391,14 @@ public boolean buildManifest(ExtFile appDir, UsesFramework usesFramework) } } - public void buildLibs(File appDir) throws AndrolibException { + private void buildLibs(File appDir) throws AndrolibException { buildLibrary(appDir, "lib"); buildLibrary(appDir, "libs"); buildLibrary(appDir, "kotlin"); buildLibrary(appDir, "META-INF/services"); } - public void buildLibrary(File appDir, String folder) throws AndrolibException { + private void buildLibrary(File appDir, String folder) throws AndrolibException { File working = new File(appDir, folder); if (! working.exists()) { @@ -416,7 +406,7 @@ public void buildLibrary(File appDir, String folder) throws AndrolibException { } File stored = new File(appDir, APK_DIRNAME + "/" + folder); - if (config.forceBuildAll || isModified(working, stored)) { + if (mConfig.forceBuildAll || isModified(working, stored)) { LOGGER.info("Copying libs... (/" + folder + ")"); try { OS.rmdir(stored); @@ -427,9 +417,9 @@ public void buildLibrary(File appDir, String folder) throws AndrolibException { } } - public void buildCopyOriginalFiles(File appDir) + private void buildCopyOriginalFiles(File appDir) throws AndrolibException { - if (config.copyOriginalFiles) { + if (mConfig.copyOriginalFiles) { File originalDir = new File(appDir, "original"); if (originalDir.exists()) { try { @@ -454,7 +444,7 @@ public void buildCopyOriginalFiles(File appDir) } } - public void buildUnknownFiles(File appDir, File outFile, MetaInfo meta) + private void buildUnknownFiles(File appDir, File outFile, ApkInfo meta) throws AndrolibException { if (meta.unknownFiles != null) { LOGGER.info("Copying unknown files/dir..."); @@ -541,7 +531,7 @@ private void copyUnknownFiles(File appDir, ZipOutputStream outputFile, Map it; + try { + it = IOUtils.lineIterator(new FileReader(new File(appDir, + "res/values/public.xml"))); + } catch (FileNotFoundException ex) { + throw new AndrolibException( + "Could not detect whether app is framework one", ex); + } + it.next(); + it.next(); + return it.next().contains("0x01"); } } diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkDecoder.java index 6e6faf73a3..e503f21650 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkDecoder.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkDecoder.java @@ -19,21 +19,28 @@ import brut.androlib.exceptions.AndrolibException; import brut.androlib.exceptions.InFileNotFoundException; import brut.androlib.exceptions.OutDirExistsException; -import brut.androlib.meta.MetaInfo; -import brut.androlib.res.AndrolibResources; -import brut.androlib.res.data.ResTable; -import brut.androlib.res.data.ResUnknownFiles; +import brut.androlib.apk.ApkInfo; +import brut.androlib.res.data.*; +import brut.androlib.res.decoder.*; +import brut.androlib.res.util.ExtMXSerializer; +import brut.androlib.res.util.ExtXmlSerializer; +import brut.androlib.res.xml.ResValuesXmlSerializable; +import brut.androlib.res.xml.ResXmlPatcher; import brut.androlib.src.SmaliDecoder; import brut.directory.Directory; import brut.directory.ExtFile; import brut.common.BrutException; import brut.directory.DirectoryException; +import brut.directory.FileDirectory; +import brut.util.Duo; import brut.util.OS; import com.android.tools.smali.dexlib2.iface.DexFile; import org.apache.commons.io.FilenameUtils; +import org.xmlpull.v1.XmlSerializer; import java.io.File; import java.io.IOException; +import java.io.OutputStream; import java.util.*; import java.util.logging.Logger; import java.util.regex.Pattern; @@ -42,13 +49,14 @@ public class ApkDecoder { private final static Logger LOGGER = Logger.getLogger(ApkDecoder.class.getName()); - private final Config config; - private final AndrolibResources mAndRes; + private final Config mConfig; private final ExtFile mApkFile; - private ResTable mResTable; + private final ApkInfo mApkInfo; + private final ResTable mResTable; protected final ResUnknownFiles mResUnknownFiles; - private Collection mUncompressedFiles; private int mMinSdkVersion = 0; + private final Map mResFileMapping = new HashMap<>(); + private final static String SMALI_DIRNAME = "smali"; private final static String UNK_DIRNAME = "unknown"; @@ -63,15 +71,22 @@ public class ApkDecoder { "jpg|jpeg|png|gif|wav|mp2|mp3|ogg|aac|mpg|mpeg|mid|midi|smf|jet|rtttl|imy|xmf|mp4|" + "m4a|m4v|3gp|3gpp|3g2|3gpp2|amr|awb|wma|wmv|webm|webp|mkv)$"); + private final static String[] IGNORED_PACKAGES = new String[] { + "android", "com.htc", "com.lge", "com.lge.internal", "yi", "flyme", "air.com.adobe.appentry", + "FFFFFFFFFFFFFFFFFFFFFF" }; + public ApkDecoder(ExtFile apkFile) { this(Config.getDefaultConfig(), apkFile); } public ApkDecoder(Config config, ExtFile apkFile) { - this.config = config; - mAndRes = new AndrolibResources(config); + mConfig = config; + //mAndRes = new AndrolibResources(config); mResUnknownFiles = new ResUnknownFiles(); mApkFile = apkFile; + mApkInfo = new ApkInfo(); + mApkInfo.setApkFileName(apkFile.getName()); + mResTable = new ResTable(mConfig, mApkInfo); } public ApkDecoder(File apkFile) { @@ -84,7 +99,7 @@ public ApkDecoder(Config config, File apkFile) { public void decode(File outDir) throws AndrolibException, IOException, DirectoryException { try { - if (!config.forceDelete && outDir.exists()) { + if (!mConfig.forceDelete && outDir.exists()) { throw new OutDirExistsException(); } @@ -104,8 +119,20 @@ public void decode(File outDir) throws AndrolibException, IOException, Directory decodeManifest(outDir); decodeResources(outDir); + // update apk info + if (mResTable != null) { + mResTable.initApkInfo(mApkInfo, outDir); + if (mConfig.frameworkTag != null) { + mApkInfo.usesFramework.tag = mConfig.frameworkTag; + } + } else { + if (mMinSdkVersion > 0) { + mApkInfo.setSdkInfo(getMinSdkInfo()); + } + } + if (hasSources()) { - switch (config.decodeSources) { + switch (mConfig.decodeSources) { case Config.DECODE_SOURCES_NONE: copySourcesRaw(outDir, "classes.dex"); break; @@ -122,7 +149,7 @@ public void decode(File outDir) throws AndrolibException, IOException, Directory for (String file : files) { if (file.endsWith(".dex")) { if (! file.equalsIgnoreCase("classes.dex")) { - switch(config.decodeSources) { + switch(mConfig.decodeSources) { case Config.DECODE_SOURCES_NONE: copySourcesRaw(outDir, file); break; @@ -144,10 +171,10 @@ public void decode(File outDir) throws AndrolibException, IOException, Directory copyRawFiles(outDir); copyUnknownFiles(outDir); - mUncompressedFiles = new ArrayList<>(); + Collection mUncompressedFiles = new ArrayList<>(); recordUncompressedFiles(mUncompressedFiles); copyOriginalFiles(outDir); - writeMetaFile(outDir); + writeApkInfo(outDir); } finally { try { mApkFile.close(); @@ -156,20 +183,17 @@ public void decode(File outDir) throws AndrolibException, IOException, Directory } public ResTable getResTable() throws AndrolibException { - if (mResTable == null) { - boolean hasResources = hasResources(); - boolean hasManifest = hasManifest(); - if (! (hasManifest || hasResources)) { - throw new AndrolibException( - "Apk doesn't contain either AndroidManifest.xml file or resources.arsc file"); - } - mResTable = mAndRes.getResTable(mApkFile, hasResources); - mResTable.setAnalysisMode(config.analysisMode); + if (! (hasManifest() || hasResources())) { + throw new AndrolibException( + "Apk doesn't contain either AndroidManifest.xml file or resources.arsc file"); + } + if (hasResources() && !mResTable.isMainPkgLoaded()) { + mResTable.loadMainPkg(mApkFile); } return mResTable; } - public boolean hasSources() throws AndrolibException { + private boolean hasSources() throws AndrolibException { try { return mApkFile.getDirectory().containsFile("classes.dex"); } catch (DirectoryException ex) { @@ -177,7 +201,7 @@ public boolean hasSources() throws AndrolibException { } } - public boolean hasMultipleSources() throws AndrolibException { + private boolean hasMultipleSources() throws AndrolibException { try { Set files = mApkFile.getDirectory().getFiles(false); for (String file : files) { @@ -194,7 +218,7 @@ public boolean hasMultipleSources() throws AndrolibException { } } - public boolean hasManifest() throws AndrolibException { + private boolean hasManifest() throws AndrolibException { try { return mApkFile.getDirectory().containsFile("AndroidManifest.xml"); } catch (DirectoryException ex) { @@ -202,7 +226,7 @@ public boolean hasManifest() throws AndrolibException { } } - public boolean hasResources() throws AndrolibException { + private boolean hasResources() throws AndrolibException { try { return mApkFile.getDirectory().containsFile("resources.arsc"); } catch (DirectoryException ex) { @@ -211,31 +235,12 @@ public boolean hasResources() throws AndrolibException { } public void close() throws IOException { - mAndRes.close(); + //mAndRes.close(); } - private void writeMetaFile(File outDir) throws AndrolibException { - MetaInfo meta = new MetaInfo(); - meta.version = ApktoolProperties.getVersion(); - meta.apkFileName = mApkFile.getName(); - - if (mResTable != null) { - mResTable.initMetaInfo(meta, outDir); - if (config.frameworkTag != null) { - meta.usesFramework.tag = config.frameworkTag; - } - } else { - if (mMinSdkVersion > 0) { - meta.sdkInfo = getMinSdkInfo(); - } - } - meta.unknownFiles = mResUnknownFiles.getUnknownFiles(); - if (mUncompressedFiles != null && !mUncompressedFiles.isEmpty()) { - meta.doNotCompress = mUncompressedFiles; - } - + private void writeApkInfo(File outDir) throws AndrolibException { try { - meta.save(new File(outDir, "apktool.yml")); + mApkInfo.save(new File(outDir, "apktool.yml")); } catch (IOException ex) { throw new AndrolibException(ex); } @@ -271,7 +276,7 @@ private void decodeSourcesSmali(File outDir, String filename) smaliDir.mkdirs(); LOGGER.info("Baksmaling " + filename + "..."); DexFile dexFile = SmaliDecoder.decode(mApkFile, smaliDir, filename, - config.baksmaliDebugMode, config.apiLevel); + mConfig.baksmaliDebugMode, mConfig.apiLevel); int minSdkVersion = dexFile.getOpcodes().api; if (mMinSdkVersion == 0 || mMinSdkVersion > minSdkVersion) { mMinSdkVersion = minSdkVersion; @@ -283,14 +288,18 @@ private void decodeSourcesSmali(File outDir, String filename) private void decodeManifest(File outDir) throws AndrolibException { if (hasManifest()) { - if (config.decodeResources == Config.DECODE_RESOURCES_FULL || - config.forceDecodeManifest == Config.FORCE_DECODE_MANIFEST_FULL) { + if (mConfig.decodeResources == Config.DECODE_RESOURCES_FULL || + mConfig.forceDecodeManifest == Config.FORCE_DECODE_MANIFEST_FULL) { if (hasResources()) { - mAndRes.decodeManifestWithResources(getResTable(), mApkFile, outDir); + decodeManifestWithResources(getResTable(), mApkFile, outDir); + if (!mConfig.analysisMode) { + // update apk info + mApkInfo.packageInfo.forcedPackageId = String.valueOf(mResTable.getPackageId()); + } } else { // if there's no resources.arsc, decode the manifest without looking // up attribute references - mAndRes.decodeManifest(getResTable(), mApkFile, outDir); + decodeManifest(getResTable(), mApkFile, outDir); } } else { @@ -304,9 +313,113 @@ private void decodeManifest(File outDir) throws AndrolibException { } } + private void decodeManifest(ResTable resTable, ExtFile apkFile, File outDir) + throws AndrolibException { + + Duo duo = getManifestFileDecoder(false); + ResFileDecoder fileDecoder = duo.m1; + + // Set ResAttrDecoder + duo.m2.setAttrDecoder(new ResAttrDecoder()); + ResAttrDecoder attrDecoder = duo.m2.getAttrDecoder(); + + // Fake ResPackage + attrDecoder.setCurrentPackage(new ResPackage(resTable, 0, null)); + + Directory inApk, out; + try { + inApk = apkFile.getDirectory(); + out = new FileDirectory(outDir); + + LOGGER.info("Decoding AndroidManifest.xml with only framework resources..."); + fileDecoder.decodeManifest(inApk, "AndroidManifest.xml", out, "AndroidManifest.xml"); + + } catch (DirectoryException ex) { + throw new AndrolibException(ex); + } + } + + private void decodeManifestWithResources(ResTable resTable, ExtFile apkFile, File outDir) + throws AndrolibException { + + Duo duo = getManifestFileDecoder(true); + ResFileDecoder fileDecoder = duo.m1; + ResAttrDecoder attrDecoder = duo.m2.getAttrDecoder(); + + attrDecoder.setCurrentPackage(resTable.listMainPackages().iterator().next()); + + Directory inApk, out; + try { + inApk = apkFile.getDirectory(); + out = new FileDirectory(outDir); + LOGGER.info("Decoding AndroidManifest.xml with resources..."); + + fileDecoder.decodeManifest(inApk, "AndroidManifest.xml", out, "AndroidManifest.xml"); + + // Remove versionName / versionCode (aapt API 16) + if (!mConfig.analysisMode) { + + // check for a mismatch between resources.arsc package and the package listed in AndroidManifest + // also remove the android::versionCode / versionName from manifest for rebuild + // this is a required change to prevent aapt warning about conflicting versions + // it will be passed as a parameter to aapt like "--min-sdk-version" via apktool.yml + adjustPackageManifest(resTable, outDir.getAbsolutePath() + File.separator + "AndroidManifest.xml"); + + ResXmlPatcher.removeManifestVersions(new File( + outDir.getAbsolutePath() + File.separator + "AndroidManifest.xml")); + } + } catch (DirectoryException ex) { + throw new AndrolibException(ex); + } + } + + private void adjustPackageManifest(ResTable resTable, String filePath) + throws AndrolibException { + + // compare resources.arsc package name to the one present in AndroidManifest + ResPackage resPackage = resTable.getCurrentResPackage(); + String pkgOriginal = resPackage.getName(); + String packageRenamed = resTable.getPackageRenamed(); + + resTable.setPackageId(resPackage.getId()); + resTable.setPackageOriginal(pkgOriginal); + + // 1) Check if pkgOriginal is null (empty resources.arsc) + // 2) Check if pkgOriginal === mPackageRenamed + // 3) Check if pkgOriginal is ignored via IGNORED_PACKAGES + if (pkgOriginal == null || pkgOriginal.equalsIgnoreCase(packageRenamed) + || (Arrays.asList(IGNORED_PACKAGES).contains(pkgOriginal))) { + LOGGER.info("Regular manifest package..."); + } else { + LOGGER.info("Renamed manifest package found! Replacing " + packageRenamed + " with " + pkgOriginal); + ResXmlPatcher.renameManifestPackage(new File(filePath), pkgOriginal); + } + } + + private Duo getManifestFileDecoder(boolean withResources) { + ResStreamDecoderContainer decoders = new ResStreamDecoderContainer(); + + AXmlResourceParser axmlParser = new AndroidManifestResourceParser(); + if (withResources) { + axmlParser.setAttrDecoder(new ResAttrDecoder()); + } + decoders.setDecoder("xml", new XmlPullStreamDecoder(axmlParser, getResXmlSerializer())); + + return new Duo<>(new ResFileDecoder(decoders), axmlParser); + } + + private ExtMXSerializer getResXmlSerializer() { + ExtMXSerializer serial = new ExtMXSerializer(); + serial.setProperty(ExtXmlSerializer.PROPERTY_SERIALIZER_INDENTATION, " "); + serial.setProperty(ExtXmlSerializer.PROPERTY_SERIALIZER_LINE_SEPARATOR, System.getProperty("line.separator")); + serial.setProperty(ExtXmlSerializer.PROPERTY_DEFAULT_ENCODING, "utf-8"); + serial.setDisabledAttrEscape(true); + return serial; + } + private void decodeResources(File outDir) throws AndrolibException { if (hasResources()) { - switch (config.decodeResources) { + switch (mConfig.decodeResources) { case Config.DECODE_RESOURCES_NONE: try { LOGGER.info("Copying raw resources..."); @@ -316,19 +429,120 @@ private void decodeResources(File outDir) throws AndrolibException { } break; case Config.DECODE_RESOURCES_FULL: - mAndRes.decode(getResTable(), mApkFile, outDir); + decodeResources(getResTable(), mApkFile, outDir); break; } } } + private void decodeResources(ResTable resTable, ExtFile apkFile, File outDir) + throws AndrolibException { + Duo duo = getResFileDecoder(); + ResFileDecoder fileDecoder = duo.m1; + ResAttrDecoder attrDecoder = duo.m2.getAttrDecoder(); + + attrDecoder.setCurrentPackage(resTable.listMainPackages().iterator().next()); + Directory in, out; + + try { + out = new FileDirectory(outDir); + in = apkFile.getDirectory(); + out = out.createDir("res"); + } catch (DirectoryException ex) { + throw new AndrolibException(ex); + } + + ExtMXSerializer xmlSerializer = getResXmlSerializer(); + for (ResPackage pkg : resTable.listMainPackages()) { + attrDecoder.setCurrentPackage(pkg); + + LOGGER.info("Decoding file-resources..."); + for (ResResource res : pkg.listFiles()) { + fileDecoder.decode(res, in, out, mResFileMapping); + } + + LOGGER.info("Decoding values */* XMLs..."); + for (ResValuesFile valuesFile : pkg.listValuesFiles()) { + generateValuesFile(valuesFile, out, xmlSerializer); + } + generatePublicXml(pkg, out, xmlSerializer); + } + + AndrolibException decodeError = duo.m2.getFirstError(); + if (decodeError != null) { + throw decodeError; + } + } + + private Duo getResFileDecoder() { + ResStreamDecoderContainer decoders = new ResStreamDecoderContainer(); + decoders.setDecoder("raw", new ResRawStreamDecoder()); + decoders.setDecoder("9patch", new Res9patchStreamDecoder()); + + AXmlResourceParser axmlParser = new AXmlResourceParser(); + axmlParser.setAttrDecoder(new ResAttrDecoder()); + decoders.setDecoder("xml", new XmlPullStreamDecoder(axmlParser, getResXmlSerializer())); + + return new Duo<>(new ResFileDecoder(decoders), axmlParser); + } + + private void generateValuesFile(ResValuesFile valuesFile, Directory out, + ExtXmlSerializer serial) throws AndrolibException { + try { + OutputStream outStream = out.getFileOutput(valuesFile.getPath()); + serial.setOutput((outStream), null); + serial.startDocument(null, null); + serial.startTag(null, "resources"); + + for (ResResource res : valuesFile.listResources()) { + if (valuesFile.isSynthesized(res)) { + continue; + } + ((ResValuesXmlSerializable) res.getValue()).serializeToResValuesXml(serial, res); + } + + serial.endTag(null, "resources"); + serial.newLine(); + serial.endDocument(); + serial.flush(); + outStream.close(); + } catch (IOException | DirectoryException ex) { + throw new AndrolibException("Could not generate: " + valuesFile.getPath(), ex); + } + } + + private void generatePublicXml(ResPackage pkg, Directory out, + XmlSerializer serial) throws AndrolibException { + try { + OutputStream outStream = out.getFileOutput("values/public.xml"); + serial.setOutput(outStream, null); + serial.startDocument(null, null); + serial.startTag(null, "resources"); + + for (ResResSpec spec : pkg.listResSpecs()) { + serial.startTag(null, "public"); + serial.attribute(null, "type", spec.getType().getName()); + serial.attribute(null, "name", spec.getName()); + serial.attribute(null, "id", String.format("0x%08x", spec.getId().id)); + serial.endTag(null, "public"); + } + + serial.endTag(null, "resources"); + serial.endDocument(); + serial.flush(); + outStream.close(); + } catch (IOException | DirectoryException ex) { + throw new AndrolibException("Could not generate public.xml file", ex); + } + } + private void copyRawFiles(File outDir) throws AndrolibException { LOGGER.info("Copying assets and libs..."); try { Directory in = mApkFile.getDirectory(); - if (config.decodeAssets == Config.DECODE_ASSETS_FULL) { + if (mConfig.decodeAssets == Config.DECODE_ASSETS_FULL) { if (in.containsDir("assets")) { in.copyToDir(outDir, "assets"); } @@ -375,6 +589,8 @@ private void copyUnknownFiles(File outDir) mResUnknownFiles.addUnknownFileInfo(file, String.valueOf(unk.getCompressionLevel(file))); } } + // update apk info + mApkInfo.unknownFiles = mResUnknownFiles.getUnknownFiles(); } catch (DirectoryException ex) { throw new AndrolibException(ex); } @@ -427,8 +643,8 @@ private void recordUncompressedFiles(Collection uncompressedFilesOrExts) if (extOrFile.isEmpty() || !NO_COMPRESS_PATTERN.matcher(extOrFile).find()) { extOrFile = file; - if (mAndRes.mResFileMapping.containsKey(extOrFile)) { - extOrFile = mAndRes.mResFileMapping.get(extOrFile); + if (mResFileMapping.containsKey(extOrFile)) { + extOrFile = mResFileMapping.get(extOrFile); } } if (!uncompressedFilesOrExts.contains(extOrFile)) { @@ -436,6 +652,11 @@ private void recordUncompressedFiles(Collection uncompressedFilesOrExts) } } } + // update apk info + if (!uncompressedFilesOrExts.isEmpty()) { + mApkInfo.doNotCompress = uncompressedFilesOrExts; + } + } catch (DirectoryException ex) { throw new AndrolibException(ex); } diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/Config.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/Config.java index 6b1069ee34..229b91925d 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/Config.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/Config.java @@ -48,12 +48,9 @@ public class Config { public boolean verbose = false; public boolean copyOriginalFiles = false; public boolean updateFiles = false; - public boolean isFramework = false; - public boolean resourcesAreCompressed = false; public boolean useAapt2 = false; public boolean noCrunch = false; public int forceApi = 0; - public Collection doNotCompress; // Decode options public short decodeSources = DECODE_SOURCES_SMALI; diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/ApkInfo.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/ApkInfo.java new file mode 100644 index 0000000000..ad423f7768 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/ApkInfo.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2010 Ryszard Wiśniewski + * Copyright (C) 2010 Connor Tumbleson + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package brut.androlib.apk; + +import brut.androlib.ApktoolProperties; +import brut.androlib.exceptions.AndrolibException; +import brut.androlib.res.data.ResConfigFlags; +import brut.directory.DirectoryException; +import brut.directory.FileDirectory; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.LoaderOptions; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.introspector.PropertyUtils; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +public class ApkInfo { + public String version; + + private String mApkFileName; + public boolean isFrameworkApk; + public UsesFramework usesFramework; + private Map mSdkInfo = new LinkedHashMap<>(); + public PackageInfo packageInfo = new PackageInfo(); + public VersionInfo versionInfo = new VersionInfo(); + public boolean resourcesAreCompressed; + public boolean sharedLibrary; + public boolean sparseResources; + public Map unknownFiles; + public Collection doNotCompress; + + public ApkInfo() { + this.version = ApktoolProperties.getVersion(); + } + + private static Yaml getYaml() { + DumperOptions dumpOptions = new DumperOptions(); + dumpOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + + EscapedStringRepresenter representer = new EscapedStringRepresenter(); + PropertyUtils propertyUtils = representer.getPropertyUtils(); + propertyUtils.setSkipMissingProperties(true); + + LoaderOptions loaderOptions = new LoaderOptions(); + loaderOptions.setCodePointLimit(10 * 1024 * 1024); // 10mb + + return new Yaml(new ClassSafeConstructor(), representer, dumpOptions, loaderOptions); + } + + public void save(Writer output) { + DumperOptions options = new DumperOptions(); + options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + getYaml().dump(this, output); + } + + public String checkTargetSdkVersionBounds() { + int target = mapSdkShorthandToVersion(getTargetSdkVersion()); + + int min = (getMinSdkVersion() != null) ? mapSdkShorthandToVersion(getMinSdkVersion()) : 0; + int max = (getMaxSdkVersion() != null) ? mapSdkShorthandToVersion(getMaxSdkVersion()) : target; + + target = Math.min(max, target); + target = Math.max(min, target); + return Integer.toString(target); + } + + public String getApkFileName() { + return mApkFileName; + } + + public void setApkFileName(String apkFileName) { + mApkFileName = apkFileName; + } + + public Map getSdkInfo() { + return mSdkInfo; + } + + public void setSdkInfo(Map sdkInfo) { + mSdkInfo = sdkInfo; + } + + public String getMinSdkVersion() { + return mSdkInfo.get("minSdkVersion"); + } + + public String getMaxSdkVersion() { + return mSdkInfo.get("maxSdkVersion"); + } + + public String getTargetSdkVersion() { + return mSdkInfo.get("targetSdkVersion"); + } + + public int getMinSdkVersionFromAndroidCodename(String sdkVersion) { + int sdkNumber = mapSdkShorthandToVersion(sdkVersion); + + if (sdkNumber == ResConfigFlags.SDK_BASE) { + return Integer.parseInt(mSdkInfo.get("minSdkVersion")); + } + return sdkNumber; + } + + private int mapSdkShorthandToVersion(String sdkVersion) { + switch (sdkVersion.toUpperCase()) { + case "M": + return ResConfigFlags.SDK_MNC; + case "N": + return ResConfigFlags.SDK_NOUGAT; + case "O": + return ResConfigFlags.SDK_OREO; + case "P": + return ResConfigFlags.SDK_P; + case "Q": + return ResConfigFlags.SDK_Q; + case "R": + return ResConfigFlags.SDK_R; + case "S": + return ResConfigFlags.SDK_S; + case "SV2": + return ResConfigFlags.SDK_S_V2; + case "T": + case "TIRAMISU": + return ResConfigFlags.SDK_DEVELOPMENT; + default: + return Integer.parseInt(sdkVersion); + } + } + + public void save(File file) throws IOException { + try( + FileOutputStream fos = new FileOutputStream(file); + OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fos, StandardCharsets.UTF_8); + Writer writer = new BufferedWriter(outputStreamWriter) + ) { + save(writer); + } + } + + public static ApkInfo load(InputStream is) { + return getYaml().loadAs(is, ApkInfo.class); + } + + public static ApkInfo load(File appDir) + throws AndrolibException { + try( + InputStream in = new FileDirectory(appDir).getFileInput("apktool.yml") + ) { + return ApkInfo.load(in); + } catch (DirectoryException | IOException ex) { + throw new AndrolibException(ex); + } + } +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/meta/ClassSafeConstructor.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/ClassSafeConstructor.java similarity index 96% rename from brut.apktool/apktool-lib/src/main/java/brut/androlib/meta/ClassSafeConstructor.java rename to brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/ClassSafeConstructor.java index 017325b92b..d1849ec05b 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/meta/ClassSafeConstructor.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/ClassSafeConstructor.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package brut.androlib.meta; +package brut.androlib.apk; import org.yaml.snakeyaml.constructor.AbstractConstruct; import org.yaml.snakeyaml.constructor.Constructor; @@ -33,7 +33,7 @@ public ClassSafeConstructor() { super(new LoaderOptions()); this.yamlConstructors.put(Tag.STR, new ConstructStringEx()); - this.allowableClasses.add(MetaInfo.class); + this.allowableClasses.add(ApkInfo.class); this.allowableClasses.add(PackageInfo.class); this.allowableClasses.add(UsesFramework.class); this.allowableClasses.add(VersionInfo.class); diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/meta/EscapedStringRepresenter.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/EscapedStringRepresenter.java similarity index 98% rename from brut.apktool/apktool-lib/src/main/java/brut/androlib/meta/EscapedStringRepresenter.java rename to brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/EscapedStringRepresenter.java index b2e84042d6..c2b13f9e00 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/meta/EscapedStringRepresenter.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/EscapedStringRepresenter.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package brut.androlib.meta; +package brut.androlib.apk; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.nodes.Node; diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/meta/PackageInfo.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/PackageInfo.java similarity index 96% rename from brut.apktool/apktool-lib/src/main/java/brut/androlib/meta/PackageInfo.java rename to brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/PackageInfo.java index ff07a96435..364a9afc30 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/meta/PackageInfo.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/PackageInfo.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package brut.androlib.meta; +package brut.androlib.apk; public class PackageInfo { public String forcedPackageId; diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/meta/UsesFramework.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/UsesFramework.java similarity index 96% rename from brut.apktool/apktool-lib/src/main/java/brut/androlib/meta/UsesFramework.java rename to brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/UsesFramework.java index 361fbbd986..c3bfb2da17 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/meta/UsesFramework.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/UsesFramework.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package brut.androlib.meta; +package brut.androlib.apk; import java.util.List; diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/meta/VersionInfo.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/VersionInfo.java similarity index 96% rename from brut.apktool/apktool-lib/src/main/java/brut/androlib/meta/VersionInfo.java rename to brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/VersionInfo.java index 8109369f0c..3fd488bf52 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/meta/VersionInfo.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/VersionInfo.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package brut.androlib.meta; +package brut.androlib.apk; public class VersionInfo { public String versionCode; diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/meta/YamlStringEscapeUtils.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlStringEscapeUtils.java similarity index 99% rename from brut.apktool/apktool-lib/src/main/java/brut/androlib/meta/YamlStringEscapeUtils.java rename to brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlStringEscapeUtils.java index 513db6ab3a..0b33880772 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/meta/YamlStringEscapeUtils.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/YamlStringEscapeUtils.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package brut.androlib.meta; +package brut.androlib.apk; import org.apache.commons.text.StringEscapeUtils; import org.apache.commons.text.translate.CharSequenceTranslator; diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/meta/MetaInfo.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/meta/MetaInfo.java deleted file mode 100644 index d41a8cae84..0000000000 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/meta/MetaInfo.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2010 Ryszard Wiśniewski - * Copyright (C) 2010 Connor Tumbleson - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package brut.androlib.meta; - -import brut.androlib.exceptions.AndrolibException; -import brut.directory.DirectoryException; -import brut.directory.ExtFile; -import org.yaml.snakeyaml.DumperOptions; -import org.yaml.snakeyaml.LoaderOptions; -import org.yaml.snakeyaml.Yaml; -import org.yaml.snakeyaml.introspector.PropertyUtils; - -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.util.Collection; -import java.util.Map; - -public class MetaInfo { - public String version; - public String apkFileName; - public boolean isFrameworkApk; - public UsesFramework usesFramework; - public Map sdkInfo; - public PackageInfo packageInfo; - public VersionInfo versionInfo; - public boolean compressionType; - public boolean sharedLibrary; - public boolean sparseResources; - public Map unknownFiles; - public Collection doNotCompress; - - private static Yaml getYaml() { - DumperOptions dumpOptions = new DumperOptions(); - dumpOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); - - EscapedStringRepresenter representer = new EscapedStringRepresenter(); - PropertyUtils propertyUtils = representer.getPropertyUtils(); - propertyUtils.setSkipMissingProperties(true); - - LoaderOptions loaderOptions = new LoaderOptions(); - loaderOptions.setCodePointLimit(10 * 1024 * 1024); // 10mb - - return new Yaml(new ClassSafeConstructor(), representer, dumpOptions, loaderOptions); - } - - public void save(Writer output) { - DumperOptions options = new DumperOptions(); - options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); - getYaml().dump(this, output); - } - - public void save(File file) throws IOException { - try( - FileOutputStream fos = new FileOutputStream(file); - OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fos, StandardCharsets.UTF_8); - Writer writer = new BufferedWriter(outputStreamWriter) - ) { - save(writer); - } - } - - public static MetaInfo load(InputStream is) { - return getYaml().loadAs(is, MetaInfo.class); - } - - public static MetaInfo readMetaFile(ExtFile appDir) - throws AndrolibException { - try( - InputStream in = appDir.getDirectory().getFileInput("apktool.yml") - ) { - return MetaInfo.load(in); - } catch (DirectoryException | IOException ex) { - throw new AndrolibException(ex); - } - } -} 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 deleted file mode 100644 index 90573f18a3..0000000000 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/AndrolibResources.java +++ /dev/null @@ -1,835 +0,0 @@ -/* - * Copyright (C) 2010 Ryszard Wiśniewski - * Copyright (C) 2010 Connor Tumbleson - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package brut.androlib.res; - -import brut.androlib.exceptions.AndrolibException; -import brut.androlib.Config; -import brut.androlib.meta.MetaInfo; -import brut.androlib.meta.PackageInfo; -import brut.androlib.meta.VersionInfo; -import brut.androlib.res.data.*; -import brut.androlib.res.decoder.*; -import brut.androlib.res.util.ExtMXSerializer; -import brut.androlib.res.util.ExtXmlSerializer; -import brut.androlib.res.xml.ResValuesXmlSerializable; -import brut.androlib.res.xml.ResXmlPatcher; -import brut.common.BrutException; -import brut.directory.*; -import brut.util.*; -import org.apache.commons.io.IOUtils; -import org.xmlpull.v1.XmlSerializer; - -import java.io.*; -import java.util.*; -import java.util.logging.Logger; - -final public class AndrolibResources { - - private final Config config; - - public Map mResFileMapping = new HashMap<>(); - - private final static Logger LOGGER = Logger.getLogger(AndrolibResources.class.getName()); - - private ExtFile mFramework = null; - - private String mMinSdkVersion = null; - private String mMaxSdkVersion = null; - private String mTargetSdkVersion = null; - private String mVersionCode = null; - private String mVersionName = null; - private String mPackageRenamed = null; - private String mPackageId = null; - - private boolean mSharedLibrary = false; - private boolean mSparseResources = false; - - private final static String[] IGNORED_PACKAGES = new String[] { - "android", "com.htc", "com.lge", "com.lge.internal", "yi", "flyme", "air.com.adobe.appentry", - "FFFFFFFFFFFFFFFFFFFFFF" }; - - public AndrolibResources(Config config) { - this.config = config; - } - - public AndrolibResources() { - this.config = Config.getDefaultConfig(); - } - - public ResTable getResTable(ExtFile apkFile, boolean loadMainPkg) - throws AndrolibException { - ResTable resTable = new ResTable(this); - if (loadMainPkg) { - loadMainPkg(resTable, apkFile); - } - return resTable; - } - - public ResPackage loadMainPkg(ResTable resTable, ExtFile apkFile) - throws AndrolibException { - LOGGER.info("Loading resource table..."); - ResPackage[] pkgs = getResPackagesFromApk(apkFile, resTable, config.keepBrokenResources); - ResPackage pkg; - - switch (pkgs.length) { - case 0: - pkg = new ResPackage(resTable, 0, null); - break; - case 1: - pkg = pkgs[0]; - break; - case 2: - LOGGER.warning("Skipping package group: " + pkgs[0].getName()); - pkg = pkgs[1]; - break; - default: - pkg = selectPkgWithMostResSpecs(pkgs); - break; - } - - resTable.addPackage(pkg, true); - return pkg; - } - - public ResPackage selectPkgWithMostResSpecs(ResPackage[] pkgs) { - int id = 0; - int value = 0; - int index = 0; - - for (int i = 0; i < pkgs.length; i++) { - ResPackage resPackage = pkgs[i]; - if (resPackage.getResSpecCount() > value && ! resPackage.getName().equalsIgnoreCase("android")) { - value = resPackage.getResSpecCount(); - id = resPackage.getId(); - index = i; - } - } - - // if id is still 0, we only have one pkgId which is "android" -> 1 - return (id == 0) ? pkgs[0] : pkgs[index]; - } - - public ResPackage loadFrameworkPkg(ResTable resTable, int id) - throws AndrolibException { - Framework framework = new Framework(config); - File apk = framework.getFrameworkApk(id, config.frameworkTag); - - LOGGER.info("Loading resource table from file: " + apk); - mFramework = new ExtFile(apk); - ResPackage[] pkgs = getResPackagesFromApk(mFramework, resTable, true); - - ResPackage pkg; - if (pkgs.length > 1) { - pkg = selectPkgWithMostResSpecs(pkgs); - } else if (pkgs.length == 0) { - throw new AndrolibException("Arsc files with zero or multiple packages"); - } else { - pkg = pkgs[0]; - } - - if (pkg.getId() != id) { - throw new AndrolibException("Expected pkg of id: " + id + ", got: " + pkg.getId()); - } - - resTable.addPackage(pkg, false); - return pkg; - } - - public void decodeManifest(ResTable resTable, ExtFile apkFile, File outDir) - throws AndrolibException { - - Duo duo = getManifestFileDecoder(false); - ResFileDecoder fileDecoder = duo.m1; - - // Set ResAttrDecoder - duo.m2.setAttrDecoder(new ResAttrDecoder()); - ResAttrDecoder attrDecoder = duo.m2.getAttrDecoder(); - - // Fake ResPackage - attrDecoder.setCurrentPackage(new ResPackage(resTable, 0, null)); - - Directory inApk, out; - try { - inApk = apkFile.getDirectory(); - out = new FileDirectory(outDir); - - LOGGER.info("Decoding AndroidManifest.xml with only framework resources..."); - fileDecoder.decodeManifest(inApk, "AndroidManifest.xml", out, "AndroidManifest.xml"); - - } catch (DirectoryException ex) { - throw new AndrolibException(ex); - } - } - - public void adjustPackageManifest(ResTable resTable, String filePath) - throws AndrolibException { - - // compare resources.arsc package name to the one present in AndroidManifest - ResPackage resPackage = resTable.getCurrentResPackage(); - String pkgOriginal = resPackage.getName(); - mPackageRenamed = resTable.getPackageRenamed(); - - resTable.setPackageId(resPackage.getId()); - resTable.setPackageOriginal(pkgOriginal); - - // 1) Check if pkgOriginal is null (empty resources.arsc) - // 2) Check if pkgOriginal === mPackageRenamed - // 3) Check if pkgOriginal is ignored via IGNORED_PACKAGES - if (pkgOriginal == null || pkgOriginal.equalsIgnoreCase(mPackageRenamed) - || (Arrays.asList(IGNORED_PACKAGES).contains(pkgOriginal))) { - LOGGER.info("Regular manifest package..."); - } else { - LOGGER.info("Renamed manifest package found! Replacing " + mPackageRenamed + " with " + pkgOriginal); - ResXmlPatcher.renameManifestPackage(new File(filePath), pkgOriginal); - } - } - - public void decodeManifestWithResources(ResTable resTable, ExtFile apkFile, File outDir) - throws AndrolibException { - - Duo duo = getManifestFileDecoder(true); - ResFileDecoder fileDecoder = duo.m1; - ResAttrDecoder attrDecoder = duo.m2.getAttrDecoder(); - - attrDecoder.setCurrentPackage(resTable.listMainPackages().iterator().next()); - - Directory inApk, out; - try { - inApk = apkFile.getDirectory(); - out = new FileDirectory(outDir); - LOGGER.info("Decoding AndroidManifest.xml with resources..."); - - fileDecoder.decodeManifest(inApk, "AndroidManifest.xml", out, "AndroidManifest.xml"); - - // Remove versionName / versionCode (aapt API 16) - if (!config.analysisMode) { - - // check for a mismatch between resources.arsc package and the package listed in AndroidManifest - // also remove the android::versionCode / versionName from manifest for rebuild - // this is a required change to prevent aapt warning about conflicting versions - // it will be passed as a parameter to aapt like "--min-sdk-version" via apktool.yml - adjustPackageManifest(resTable, outDir.getAbsolutePath() + File.separator + "AndroidManifest.xml"); - - ResXmlPatcher.removeManifestVersions(new File( - outDir.getAbsolutePath() + File.separator + "AndroidManifest.xml")); - - mPackageId = String.valueOf(resTable.getPackageId()); - } - } catch (DirectoryException ex) { - throw new AndrolibException(ex); - } - } - - public void decode(ResTable resTable, ExtFile apkFile, File outDir) - throws AndrolibException { - Duo duo = getResFileDecoder(); - ResFileDecoder fileDecoder = duo.m1; - ResAttrDecoder attrDecoder = duo.m2.getAttrDecoder(); - - attrDecoder.setCurrentPackage(resTable.listMainPackages().iterator().next()); - Directory in, out; - - try { - out = new FileDirectory(outDir); - in = apkFile.getDirectory(); - out = out.createDir("res"); - } catch (DirectoryException ex) { - throw new AndrolibException(ex); - } - - ExtMXSerializer xmlSerializer = getResXmlSerializer(); - for (ResPackage pkg : resTable.listMainPackages()) { - attrDecoder.setCurrentPackage(pkg); - - LOGGER.info("Decoding file-resources..."); - for (ResResource res : pkg.listFiles()) { - fileDecoder.decode(res, in, out, mResFileMapping); - } - - LOGGER.info("Decoding values */* XMLs..."); - for (ResValuesFile valuesFile : pkg.listValuesFiles()) { - generateValuesFile(valuesFile, out, xmlSerializer); - } - generatePublicXml(pkg, out, xmlSerializer); - } - - AndrolibException decodeError = duo.m2.getFirstError(); - if (decodeError != null) { - throw decodeError; - } - } - - public void setSdkInfo(Map map) { - if (map != null) { - mMinSdkVersion = map.get("minSdkVersion"); - mTargetSdkVersion = map.get("targetSdkVersion"); - mMaxSdkVersion = map.get("maxSdkVersion"); - } - } - - public void setVersionInfo(VersionInfo versionInfo) { - if (versionInfo != null) { - mVersionCode = versionInfo.versionCode; - mVersionName = versionInfo.versionName; - } - } - - public void setPackageRenamed(PackageInfo packageInfo) { - if (packageInfo != null) { - mPackageRenamed = packageInfo.renameManifestPackage; - } - } - - public void setPackageId(PackageInfo packageInfo) { - if (packageInfo != null) { - mPackageId = packageInfo.forcedPackageId; - } - } - - public void setSharedLibrary(boolean flag) { - mSharedLibrary = flag; - } - - public void setSparseResources(boolean flag) { - mSparseResources = flag; - } - - public String checkTargetSdkVersionBounds() { - int target = mapSdkShorthandToVersion(mTargetSdkVersion); - - int min = (mMinSdkVersion != null) ? mapSdkShorthandToVersion(mMinSdkVersion) : 0; - int max = (mMaxSdkVersion != null) ? mapSdkShorthandToVersion(mMaxSdkVersion) : target; - - target = Math.min(max, target); - target = Math.max(min, target); - return Integer.toString(target); - } - - private File createDoNotCompressExtensionsFile(Config config) throws AndrolibException { - if (config.doNotCompress == null || config.doNotCompress.isEmpty()) { - return null; - } - - File doNotCompressFile; - try { - doNotCompressFile = File.createTempFile("APKTOOL", null); - doNotCompressFile.deleteOnExit(); - - BufferedWriter fileWriter = new BufferedWriter(new FileWriter(doNotCompressFile)); - for (String extension : config.doNotCompress) { - fileWriter.write(extension); - fileWriter.newLine(); - } - fileWriter.close(); - - return doNotCompressFile; - } catch (IOException ex) { - throw new AndrolibException(ex); - } - } - - private void aapt2Package(File apkFile, File manifest, File resDir, File rawDir, File assetDir, File[] include, - List cmd, boolean customAapt) - throws AndrolibException { - - List compileCommand = new ArrayList<>(cmd); - File resourcesZip = null; - - if (resDir != null) { - File buildDir = new File(resDir.getParent(), "build"); - resourcesZip = new File(buildDir, "resources.zip"); - } - - if (resDir != null && !resourcesZip.exists()) { - - // Compile the files into flat arsc files - cmd.add("compile"); - - cmd.add("--dir"); - cmd.add(resDir.getAbsolutePath()); - - // Treats error that used to be valid in aapt1 as warnings in aapt2 - cmd.add("--legacy"); - - File buildDir = new File(resDir.getParent(), "build"); - resourcesZip = new File(buildDir, "resources.zip"); - - cmd.add("-o"); - cmd.add(resourcesZip.getAbsolutePath()); - - if (config.verbose) { - cmd.add("-v"); - } - - if (config.noCrunch) { - cmd.add("--no-crunch"); - } - - try { - OS.exec(cmd.toArray(new String[0])); - LOGGER.fine("aapt2 compile command ran: "); - LOGGER.fine(cmd.toString()); - } catch (BrutException ex) { - throw new AndrolibException(ex); - } - } - - if (manifest == null) { - return; - } - - // Link them into the final apk, reusing our old command after clearing for the aapt2 binary - cmd = new ArrayList<>(compileCommand); - cmd.add("link"); - - cmd.add("-o"); - cmd.add(apkFile.getAbsolutePath()); - - if (mPackageId != null && ! mSharedLibrary) { - cmd.add("--package-id"); - cmd.add(mPackageId); - } - - if (mSharedLibrary) { - cmd.add("--shared-lib"); - } - - if (mMinSdkVersion != null) { - cmd.add("--min-sdk-version"); - cmd.add(mMinSdkVersion); - } - - if (mTargetSdkVersion != null) { - cmd.add("--target-sdk-version"); - cmd.add(checkTargetSdkVersionBounds()); - } - - if (mPackageRenamed != null) { - cmd.add("--rename-manifest-package"); - cmd.add(mPackageRenamed); - - cmd.add("--rename-instrumentation-target-package"); - cmd.add(mPackageRenamed); - } - - if (mVersionCode != null) { - cmd.add("--version-code"); - cmd.add(mVersionCode); - } - - if (mVersionName != null) { - cmd.add("--version-name"); - cmd.add(mVersionName); - } - - // Disable automatic changes - cmd.add("--no-auto-version"); - cmd.add("--no-version-vectors"); - cmd.add("--no-version-transitions"); - cmd.add("--no-resource-deduping"); - - cmd.add("--allow-reserved-package-id"); - - if (mSparseResources) { - cmd.add("--enable-sparse-encoding"); - } - - if (config.isFramework) { - cmd.add("-x"); - } - - if (config.doNotCompress != null && !customAapt) { - // Use custom -e option to avoid limits on commandline length. - // Can only be used when custom aapt binary is not used. - String extensionsFilePath = - Objects.requireNonNull(createDoNotCompressExtensionsFile(config)).getAbsolutePath(); - cmd.add("-e"); - cmd.add(extensionsFilePath); - } else if (config.doNotCompress != null) { - for (String file : config.doNotCompress) { - cmd.add("-0"); - cmd.add(file); - } - } - - if (!config.resourcesAreCompressed) { - cmd.add("-0"); - cmd.add("arsc"); - } - - if (include != null) { - for (File file : include) { - cmd.add("-I"); - cmd.add(file.getPath()); - } - } - - cmd.add("--manifest"); - cmd.add(manifest.getAbsolutePath()); - - if (assetDir != null) { - cmd.add("-A"); - cmd.add(assetDir.getAbsolutePath()); - } - - if (rawDir != null) { - cmd.add("-R"); - cmd.add(rawDir.getAbsolutePath()); - } - - if (config.verbose) { - cmd.add("-v"); - } - - if (resourcesZip != null) { - cmd.add(resourcesZip.getAbsolutePath()); - } - - try { - OS.exec(cmd.toArray(new String[0])); - LOGGER.fine("aapt2 link command ran: "); - LOGGER.fine(cmd.toString()); - } catch (BrutException ex) { - throw new AndrolibException(ex); - } - } - - private void aapt1Package(File apkFile, File manifest, File resDir, File rawDir, File assetDir, File[] include, - List cmd, boolean customAapt) - throws AndrolibException { - - cmd.add("p"); - - if (config.verbose) { // output aapt verbose - cmd.add("-v"); - } - if (config.updateFiles) { - cmd.add("-u"); - } - if (config.debugMode) { // inject debuggable="true" into manifest - cmd.add("--debug-mode"); - } - if (config.noCrunch) { - cmd.add("--no-crunch"); - } - // force package id so that some frameworks build with correct id - // disable if user adds own aapt (can't know if they have this feature) - if (mPackageId != null && ! customAapt && ! mSharedLibrary) { - cmd.add("--forced-package-id"); - cmd.add(mPackageId); - } - if (mSharedLibrary) { - cmd.add("--shared-lib"); - } - if (mMinSdkVersion != null) { - cmd.add("--min-sdk-version"); - cmd.add(mMinSdkVersion); - } - if (mTargetSdkVersion != null) { - cmd.add("--target-sdk-version"); - - // Ensure that targetSdkVersion is between minSdkVersion/maxSdkVersion if - // they are specified. - cmd.add(checkTargetSdkVersionBounds()); - } - if (mMaxSdkVersion != null) { - cmd.add("--max-sdk-version"); - cmd.add(mMaxSdkVersion); - - // if we have max sdk version, set --max-res-version - // so we can ignore anything over that during build. - cmd.add("--max-res-version"); - cmd.add(mMaxSdkVersion); - } - if (mPackageRenamed != null) { - cmd.add("--rename-manifest-package"); - cmd.add(mPackageRenamed); - } - if (mVersionCode != null) { - cmd.add("--version-code"); - cmd.add(mVersionCode); - } - if (mVersionName != null) { - cmd.add("--version-name"); - cmd.add(mVersionName); - } - cmd.add("--no-version-vectors"); - cmd.add("-F"); - cmd.add(apkFile.getAbsolutePath()); - - if (config.isFramework) { - cmd.add("-x"); - } - - if (config.doNotCompress != null && !customAapt) { - // Use custom -e option to avoid limits on commandline length. - // Can only be used when custom aapt binary is not used. - String extensionsFilePath = - Objects.requireNonNull(createDoNotCompressExtensionsFile(config)).getAbsolutePath(); - cmd.add("-e"); - cmd.add(extensionsFilePath); - } else if (config.doNotCompress != null) { - for (String file : config.doNotCompress) { - cmd.add("-0"); - cmd.add(file); - } - } - - if (!config.resourcesAreCompressed) { - cmd.add("-0"); - cmd.add("arsc"); - } - - if (include != null) { - for (File file : include) { - cmd.add("-I"); - cmd.add(file.getPath()); - } - } - if (resDir != null) { - cmd.add("-S"); - cmd.add(resDir.getAbsolutePath()); - } - if (manifest != null) { - cmd.add("-M"); - cmd.add(manifest.getAbsolutePath()); - } - if (assetDir != null) { - cmd.add("-A"); - cmd.add(assetDir.getAbsolutePath()); - } - if (rawDir != null) { - cmd.add(rawDir.getAbsolutePath()); - } - try { - OS.exec(cmd.toArray(new String[0])); - LOGGER.fine("command ran: "); - LOGGER.fine(cmd.toString()); - } catch (BrutException ex) { - throw new AndrolibException(ex); - } - } - - public void aaptPackage(File apkFile, File manifest, File resDir, File rawDir, File assetDir, File[] include) - throws AndrolibException { - - String aaptPath = config.aaptPath; - boolean customAapt = !aaptPath.isEmpty(); - List cmd = new ArrayList<>(); - - try { - String aaptCommand = AaptManager.getAaptExecutionCommand(aaptPath, getAaptBinaryFile()); - cmd.add(aaptCommand); - } catch (BrutException ex) { - LOGGER.warning("aapt: " + ex.getMessage() + " (defaulting to $PATH binary)"); - cmd.add(AaptManager.getAaptBinaryName(getAaptVersion())); - } - - if (config.isAapt2()) { - aapt2Package(apkFile, manifest, resDir, rawDir, assetDir, include, cmd, customAapt); - return; - } - aapt1Package(apkFile, manifest, resDir, rawDir, assetDir, include, cmd, customAapt); - } - - public void zipPackage(File apkFile, File rawDir, File assetDir) - throws AndrolibException { - - try { - ZipUtils.zipFolders(rawDir, apkFile, assetDir, config.doNotCompress); - } catch (IOException | BrutException ex) { - throw new AndrolibException(ex); - } - } - - public int getMinSdkVersionFromAndroidCodename(MetaInfo meta, String sdkVersion) { - int sdkNumber = mapSdkShorthandToVersion(sdkVersion); - - if (sdkNumber == ResConfigFlags.SDK_BASE) { - return Integer.parseInt(meta.sdkInfo.get("minSdkVersion")); - } - return sdkNumber; - } - - private int mapSdkShorthandToVersion(String sdkVersion) { - switch (sdkVersion.toUpperCase()) { - case "M": - return ResConfigFlags.SDK_MNC; - case "N": - return ResConfigFlags.SDK_NOUGAT; - case "O": - return ResConfigFlags.SDK_OREO; - case "P": - return ResConfigFlags.SDK_P; - case "Q": - return ResConfigFlags.SDK_Q; - case "R": - return ResConfigFlags.SDK_R; - case "S": - return ResConfigFlags.SDK_S; - case "SV2": - return ResConfigFlags.SDK_S_V2; - case "T": - case "TIRAMISU": - return ResConfigFlags.SDK_TIRAMISU; - case "UPSIDEDOWNCAKE": - case "UPSIDE_DOWN_CAKE": - case "VANILLAICECREAM": - case "VANILLA_ICE_CREAM": - return ResConfigFlags.SDK_DEVELOPMENT; - default: - return Integer.parseInt(sdkVersion); - } - } - - public boolean detectWhetherAppIsFramework(File appDir) - throws AndrolibException { - File publicXml = new File(appDir, "res/values/public.xml"); - if (! publicXml.exists()) { - return false; - } - - Iterator it; - try { - it = IOUtils.lineIterator(new FileReader(new File(appDir, - "res/values/public.xml"))); - } catch (FileNotFoundException ex) { - throw new AndrolibException( - "Could not detect whether app is framework one", ex); - } - it.next(); - it.next(); - return it.next().contains("0x01"); - } - - public Duo getResFileDecoder() { - ResStreamDecoderContainer decoders = new ResStreamDecoderContainer(); - decoders.setDecoder("raw", new ResRawStreamDecoder()); - decoders.setDecoder("9patch", new Res9patchStreamDecoder()); - - AXmlResourceParser axmlParser = new AXmlResourceParser(); - axmlParser.setAttrDecoder(new ResAttrDecoder()); - decoders.setDecoder("xml", new XmlPullStreamDecoder(axmlParser, getResXmlSerializer())); - - return new Duo<>(new ResFileDecoder(decoders), axmlParser); - } - - public Duo getManifestFileDecoder(boolean withResources) { - ResStreamDecoderContainer decoders = new ResStreamDecoderContainer(); - - AXmlResourceParser axmlParser = new AndroidManifestResourceParser(); - if (withResources) { - axmlParser.setAttrDecoder(new ResAttrDecoder()); - } - decoders.setDecoder("xml", new XmlPullStreamDecoder(axmlParser, getResXmlSerializer())); - - return new Duo<>(new ResFileDecoder(decoders), axmlParser); - } - - public ExtMXSerializer getResXmlSerializer() { - ExtMXSerializer serial = new ExtMXSerializer(); - serial.setProperty(ExtXmlSerializer.PROPERTY_SERIALIZER_INDENTATION, " "); - serial.setProperty(ExtXmlSerializer.PROPERTY_SERIALIZER_LINE_SEPARATOR, System.getProperty("line.separator")); - serial.setProperty(ExtXmlSerializer.PROPERTY_DEFAULT_ENCODING, "utf-8"); - serial.setDisabledAttrEscape(true); - return serial; - } - - private void generateValuesFile(ResValuesFile valuesFile, Directory out, - ExtXmlSerializer serial) throws AndrolibException { - try { - OutputStream outStream = out.getFileOutput(valuesFile.getPath()); - serial.setOutput((outStream), null); - serial.startDocument(null, null); - serial.startTag(null, "resources"); - - for (ResResource res : valuesFile.listResources()) { - if (valuesFile.isSynthesized(res)) { - continue; - } - ((ResValuesXmlSerializable) res.getValue()).serializeToResValuesXml(serial, res); - } - - serial.endTag(null, "resources"); - serial.newLine(); - serial.endDocument(); - serial.flush(); - outStream.close(); - } catch (IOException | DirectoryException ex) { - throw new AndrolibException("Could not generate: " + valuesFile.getPath(), ex); - } - } - - private void generatePublicXml(ResPackage pkg, Directory out, - XmlSerializer serial) throws AndrolibException { - try { - OutputStream outStream = out.getFileOutput("values/public.xml"); - serial.setOutput(outStream, null); - serial.startDocument(null, null); - serial.startTag(null, "resources"); - - for (ResResSpec spec : pkg.listResSpecs()) { - serial.startTag(null, "public"); - serial.attribute(null, "type", spec.getType().getName()); - serial.attribute(null, "name", spec.getName()); - serial.attribute(null, "id", String.format("0x%08x", spec.getId().id)); - serial.endTag(null, "public"); - } - - serial.endTag(null, "resources"); - serial.endDocument(); - serial.flush(); - outStream.close(); - } catch (IOException | DirectoryException ex) { - throw new AndrolibException("Could not generate public.xml file", ex); - } - } - - private ResPackage[] getResPackagesFromApk(ExtFile apkFile, ResTable resTable, boolean keepBrokenResources) - throws AndrolibException { - try { - Directory dir = apkFile.getDirectory(); - try (BufferedInputStream bfi = new BufferedInputStream(dir.getFileInput("resources.arsc"))) { - return ARSCDecoder.decode(bfi, false, keepBrokenResources, resTable).getPackages(); - } - } catch (DirectoryException | IOException ex) { - throw new AndrolibException("Could not load resources.arsc from file: " + apkFile, ex); - } - } - - - private File getAaptBinaryFile() throws AndrolibException { - try { - if (getAaptVersion() == 2) { - return AaptManager.getAapt2(); - } - return AaptManager.getAapt1(); - } catch (BrutException ex) { - throw new AndrolibException(ex); - } - } - - private int getAaptVersion() { - return config.isAapt2() ? 2 : 1; - } - - 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 index 10e88a13a1..19d52b5963 100644 --- 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 @@ -137,7 +137,7 @@ public void publicizeResources(File arscFile) throws AndrolibException { } } - public void publicizeResources(byte[] arsc) throws AndrolibException { + private void publicizeResources(byte[] arsc) throws AndrolibException { publicizeResources(arsc, ARSCDecoder.decode(new ByteArrayInputStream(arsc), true, true).getFlagsOffsets()); } diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResourcesDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResourcesDecoder.java new file mode 100644 index 0000000000..ef4ab70d98 --- /dev/null +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResourcesDecoder.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2010 Ryszard Wiśniewski + * Copyright (C) 2010 Connor Tumbleson + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package brut.androlib.res; + +import brut.androlib.exceptions.AndrolibException; +import brut.androlib.Config; +import brut.directory.*; +import org.apache.commons.io.IOUtils; + +import java.io.*; +import java.util.*; +import java.util.logging.Logger; + +final public class ResourcesDecoder { + + private final static Logger LOGGER = Logger.getLogger(ResourcesDecoder.class.getName()); + + private final Config mConfig; + + public ResourcesDecoder(Config config) { + mConfig = config; + } + + +} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResTable.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResTable.java index fdb6ff1385..7ed16ffd1c 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResTable.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResTable.java @@ -16,23 +16,32 @@ */ package brut.androlib.res.data; +import brut.androlib.ApkDecoder; +import brut.androlib.Config; import brut.androlib.exceptions.AndrolibException; import brut.androlib.exceptions.UndefinedResObjectException; -import brut.androlib.meta.MetaInfo; -import brut.androlib.meta.PackageInfo; -import brut.androlib.meta.UsesFramework; -import brut.androlib.meta.VersionInfo; -import brut.androlib.res.AndrolibResources; +import brut.androlib.apk.ApkInfo; +import brut.androlib.apk.UsesFramework; +import brut.androlib.res.Framework; import brut.androlib.res.data.value.ResValue; +import brut.androlib.res.decoder.ARSCDecoder; import brut.androlib.res.xml.ResXmlPatcher; +import brut.directory.Directory; +import brut.directory.DirectoryException; +import brut.directory.ExtFile; import com.google.common.base.Strings; +import java.io.BufferedInputStream; import java.io.File; +import java.io.IOException; import java.util.*; +import java.util.logging.Logger; public class ResTable { - private final AndrolibResources mAndRes; + private final static Logger LOGGER = Logger.getLogger(ApkDecoder.class.getName()); + private final Config mConfig; + private final ApkInfo mApkInfo; private final Map mPackagesById = new HashMap<>(); private final Map mPackagesByName = new HashMap<>(); private final Set mMainPackages = new LinkedHashSet<>(); @@ -41,19 +50,24 @@ public class ResTable { private String mPackageRenamed; private String mPackageOriginal; private int mPackageId; - private boolean mAnalysisMode = false; - private boolean mSharedLibrary = false; - private boolean mSparseResources = false; - private final Map mSdkInfo = new LinkedHashMap<>(); - private final VersionInfo mVersionInfo = new VersionInfo(); + private boolean mMainPkgLoaded = false; public ResTable() { - mAndRes = null; + this(Config.getDefaultConfig(), new ApkInfo()); } - public ResTable(AndrolibResources andRes) { - mAndRes = andRes; + public ResTable(Config config, ApkInfo apkInfo) { + mConfig = config; + mApkInfo = apkInfo; + } + + public boolean getAnalysisMode() { + return mConfig.analysisMode; + } + + public boolean isMainPkgLoaded() { + return mMainPkgLoaded; } public ResResSpec getResSpec(int resID) throws AndrolibException { @@ -84,10 +98,86 @@ public ResPackage getPackage(int id) throws AndrolibException { if (pkg != null) { return pkg; } - if (mAndRes != null) { - return mAndRes.loadFrameworkPkg(this, id); + pkg = loadFrameworkPkg(id); + addPackage(pkg, false); + return pkg; + } + + private ResPackage selectPkgWithMostResSpecs(ResPackage[] pkgs) { + int id = 0; + int value = 0; + int index = 0; + + for (int i = 0; i < pkgs.length; i++) { + ResPackage resPackage = pkgs[i]; + if (resPackage.getResSpecCount() > value && ! resPackage.getName().equalsIgnoreCase("android")) { + value = resPackage.getResSpecCount(); + id = resPackage.getId(); + index = i; + } + } + + // if id is still 0, we only have one pkgId which is "android" -> 1 + return (id == 0) ? pkgs[0] : pkgs[index]; + } + + public void loadMainPkg(ExtFile apkFile) throws AndrolibException { + LOGGER.info("Loading resource table..."); + ResPackage[] pkgs = loadResPackagesFromApk(apkFile, mConfig.keepBrokenResources); + ResPackage pkg; + + switch (pkgs.length) { + case 0: + pkg = new ResPackage(this, 0, null); + break; + case 1: + pkg = pkgs[0]; + break; + case 2: + LOGGER.warning("Skipping package group: " + pkgs[0].getName()); + pkg = pkgs[1]; + break; + default: + pkg = selectPkgWithMostResSpecs(pkgs); + break; + } + addPackage(pkg, true); + mMainPkgLoaded = true; + } + + private ResPackage loadFrameworkPkg(int id) + throws AndrolibException { + Framework framework = new Framework(mConfig); + File frameworkApk = framework.getFrameworkApk(id, mConfig.frameworkTag); + + LOGGER.info("Loading resource table from file: " + frameworkApk); + ResPackage[] pkgs = loadResPackagesFromApk(new ExtFile(frameworkApk), true); + + ResPackage pkg; + if (pkgs.length > 1) { + pkg = selectPkgWithMostResSpecs(pkgs); + } else if (pkgs.length == 0) { + throw new AndrolibException("Arsc files with zero or multiple packages"); + } else { + pkg = pkgs[0]; + } + + if (pkg.getId() != id) { + throw new AndrolibException("Expected pkg of id: " + id + ", got: " + pkg.getId()); + } + return pkg; + } + + private ResPackage[] loadResPackagesFromApk(ExtFile apkFile, boolean keepBrokenResources) + throws AndrolibException { + try { + Directory dir = apkFile.getDirectory(); + try (BufferedInputStream bfi = new BufferedInputStream(dir.getFileInput("resources.arsc"))) { + return ARSCDecoder.decode(bfi, false, keepBrokenResources, this).getPackages(); + } + } catch (DirectoryException | IOException ex) { + throw new AndrolibException("Could not load resources.arsc from file: " + apkFile, ex); } - throw new UndefinedResObjectException(String.format("package: id=%d", id)); } public ResPackage getHighestSpecPackage() throws AndrolibException { @@ -147,10 +237,6 @@ public void addPackage(ResPackage pkg, boolean main) throws AndrolibException { } } - public void setAnalysisMode(boolean mode) { - mAnalysisMode = mode; - } - public void setPackageRenamed(String pkg) { mPackageRenamed = pkg; } @@ -164,39 +250,28 @@ public void setPackageId(int id) { } public void setSharedLibrary(boolean flag) { - mSharedLibrary = flag; + mApkInfo.sharedLibrary = flag; } public void setSparseResources(boolean flag) { - mSparseResources = flag; + mApkInfo.sparseResources = flag; + } public void clearSdkInfo() { - mSdkInfo.clear(); + mApkInfo.getSdkInfo().clear(); } public void addSdkInfo(String key, String value) { - mSdkInfo.put(key, value); + mApkInfo.getSdkInfo().put(key, value); } public void setVersionName(String versionName) { - mVersionInfo.versionName = versionName; + mApkInfo.versionInfo.versionName = versionName; } public void setVersionCode(String versionCode) { - mVersionInfo.versionCode = versionCode; - } - - public VersionInfo getVersionInfo() { - return mVersionInfo; - } - - public Map getSdkInfo() { - return mSdkInfo; - } - - public boolean getAnalysisMode() { - return mAnalysisMode; + mApkInfo.versionInfo.versionCode = versionCode; } public String getPackageRenamed() { @@ -211,15 +286,11 @@ public int getPackageId() { return mPackageId; } - public boolean getSharedLibrary() { - return mSharedLibrary; - } - public boolean getSparseResources() { - return mSparseResources; + return mApkInfo.sparseResources; } - public boolean isFrameworkApk() { + private boolean isFrameworkApk() { for (ResPackage pkg : listMainPackages()) { if (pkg.getId() > 0 && pkg.getId() < 64) { return true; @@ -228,19 +299,16 @@ public boolean isFrameworkApk() { return false; } - public void initMetaInfo(MetaInfo meta, File outDir) throws AndrolibException { - meta.isFrameworkApk = isFrameworkApk(); + public void initApkInfo(ApkInfo apkInfo, File outDir) throws AndrolibException { + apkInfo.isFrameworkApk = isFrameworkApk(); if (!listFramePackages().isEmpty()) { - meta.usesFramework = getUsesFramework(); + apkInfo.usesFramework = getUsesFramework(); } - if (!getSdkInfo().isEmpty()) { - initSdkInfo(outDir); - meta.sdkInfo = getSdkInfo(); + if (!mApkInfo.getSdkInfo().isEmpty()) { + updateSdkInfoFromResources(outDir); } - meta.packageInfo = getPackageInfo(); - meta.versionInfo = getVersionInfoWithName(outDir); - meta.sharedLibrary = getSharedLibrary(); - meta.sparseResources = getSparseResources(); + initPackageInfo(); + loadVersionName(outDir); } private UsesFramework getUsesFramework() { @@ -259,30 +327,30 @@ private UsesFramework getUsesFramework() { return info; } - private void initSdkInfo(File outDir) { - Map info = mSdkInfo; + private void updateSdkInfoFromResources(File outDir) { String refValue; - if (info.get("minSdkVersion") != null) { - refValue = ResXmlPatcher.pullValueFromIntegers(outDir, info.get("minSdkVersion")); + Map sdkInfo = mApkInfo.getSdkInfo(); + if (sdkInfo.get("minSdkVersion") != null) { + refValue = ResXmlPatcher.pullValueFromIntegers(outDir, sdkInfo.get("minSdkVersion")); if (refValue != null) { - info.put("minSdkVersion", refValue); + sdkInfo.put("minSdkVersion", refValue); } } - if (info.get("targetSdkVersion") != null) { - refValue = ResXmlPatcher.pullValueFromIntegers(outDir, info.get("targetSdkVersion")); + if (sdkInfo.get("targetSdkVersion") != null) { + refValue = ResXmlPatcher.pullValueFromIntegers(outDir, sdkInfo.get("targetSdkVersion")); if (refValue != null) { - info.put("targetSdkVersion", refValue); + sdkInfo.put("targetSdkVersion", refValue); } } - if (info.get("maxSdkVersion") != null) { - refValue = ResXmlPatcher.pullValueFromIntegers(outDir, info.get("maxSdkVersion")); + if (sdkInfo.get("maxSdkVersion") != null) { + refValue = ResXmlPatcher.pullValueFromIntegers(outDir, sdkInfo.get("maxSdkVersion")); if (refValue != null) { - info.put("maxSdkVersion", refValue); + sdkInfo.put("maxSdkVersion", refValue); } } } - private PackageInfo getPackageInfo() throws AndrolibException { + private void initPackageInfo() throws AndrolibException { String renamed = getPackageRenamed(); String original = getPackageOriginal(); @@ -292,25 +360,21 @@ private PackageInfo getPackageInfo() throws AndrolibException { } catch (UndefinedResObjectException ignored) {} if (Strings.isNullOrEmpty(original)) { - return null; + return; } - PackageInfo info = new PackageInfo(); - // only put rename-manifest-package into apktool.yml, if the change will be required if (!renamed.equalsIgnoreCase(original)) { - info.renameManifestPackage = renamed; + mApkInfo.packageInfo.renameManifestPackage = renamed; } - info.forcedPackageId = String.valueOf(id); - return info; + mApkInfo.packageInfo.forcedPackageId = String.valueOf(id); } - private VersionInfo getVersionInfoWithName(File outDir) { - VersionInfo info = getVersionInfo(); - String refValue = ResXmlPatcher.pullValueFromStrings(outDir, info.versionName); + private void loadVersionName(File outDir) { + String versionName = mApkInfo.versionInfo.versionName; + String refValue = ResXmlPatcher.pullValueFromStrings(outDir, versionName); if (refValue != null) { - info.versionName = refValue; + mApkInfo.versionInfo.versionName = refValue; } - return info; } } diff --git a/brut.apktool/apktool-lib/src/test/java/brut/androlib/BaseTest.java b/brut.apktool/apktool-lib/src/test/java/brut/androlib/BaseTest.java index 7fcca7ec0a..577aa94f48 100644 --- a/brut.apktool/apktool-lib/src/test/java/brut/androlib/BaseTest.java +++ b/brut.apktool/apktool-lib/src/test/java/brut/androlib/BaseTest.java @@ -16,7 +16,7 @@ */ package brut.androlib; -import brut.androlib.meta.MetaInfo; +import brut.androlib.apk.ApkInfo; import brut.common.BrutException; import brut.directory.ExtFile; import brut.directory.FileDirectory; @@ -37,8 +37,8 @@ public class BaseTest { protected void compareUnknownFiles() throws BrutException { - MetaInfo control = MetaInfo.readMetaFile(sTestOrigDir); - MetaInfo test = MetaInfo.readMetaFile(sTestNewDir); + ApkInfo control = ApkInfo.load(sTestOrigDir); + ApkInfo test = ApkInfo.load(sTestNewDir); assertNotNull(control.unknownFiles); assertNotNull(test.unknownFiles); 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 727396daf7..95fe6a2d6d 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 @@ -17,7 +17,6 @@ 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; diff --git a/brut.apktool/apktool-lib/src/test/java/brut/androlib/aapt1/BuildAndDecodeTest.java b/brut.apktool/apktool-lib/src/test/java/brut/androlib/aapt1/BuildAndDecodeTest.java index e33eafdd4a..99117e21c7 100644 --- a/brut.apktool/apktool-lib/src/test/java/brut/androlib/aapt1/BuildAndDecodeTest.java +++ b/brut.apktool/apktool-lib/src/test/java/brut/androlib/aapt1/BuildAndDecodeTest.java @@ -20,7 +20,7 @@ import brut.androlib.ApkDecoder; import brut.androlib.BaseTest; import brut.androlib.TestUtils; -import brut.androlib.meta.MetaInfo; +import brut.androlib.apk.ApkInfo; import brut.common.BrutException; import brut.directory.ExtFile; import brut.util.OS; @@ -491,17 +491,17 @@ public void robust9patchTest() throws IOException { @Test public void confirmZeroByteFileExtensionIsNotStored() throws BrutException { - MetaInfo metaInfo = MetaInfo.readMetaFile(sTestNewDir); + ApkInfo apkInfo = ApkInfo.load(sTestNewDir); - for (String item : metaInfo.doNotCompress) { + for (String item : apkInfo.doNotCompress) { assertNotEquals("jpg", item); } } @Test public void confirmZeroByteFileIsStored() throws BrutException { - MetaInfo metaInfo = MetaInfo.readMetaFile(sTestNewDir); - assertTrue(metaInfo.doNotCompress.contains("assets/0byte_file.jpg")); + ApkInfo apkInfo = ApkInfo.load(sTestNewDir); + assertTrue(apkInfo.doNotCompress.contains("assets/0byte_file.jpg")); } @Test diff --git a/brut.apktool/apktool-lib/src/test/java/brut/androlib/aapt1/ReferenceVersionCodeTest.java b/brut.apktool/apktool-lib/src/test/java/brut/androlib/aapt1/ReferenceVersionCodeTest.java index 947a91ffef..8d9ef4b7a4 100644 --- a/brut.apktool/apktool-lib/src/test/java/brut/androlib/aapt1/ReferenceVersionCodeTest.java +++ b/brut.apktool/apktool-lib/src/test/java/brut/androlib/aapt1/ReferenceVersionCodeTest.java @@ -16,11 +16,10 @@ */ package brut.androlib.aapt1; -import brut.androlib.ApkBuilder; import brut.androlib.ApkDecoder; import brut.androlib.BaseTest; import brut.androlib.TestUtils; -import brut.androlib.meta.MetaInfo; +import brut.androlib.apk.ApkInfo; import brut.directory.ExtFile; import brut.common.BrutException; import brut.util.OS; @@ -57,7 +56,7 @@ public void referenceBecomesLiteralTest() throws BrutException, IOException { File outDir = new File(sTmpDir + File.separator + apk + ".out"); apkDecoder.decode(outDir); - MetaInfo metaInfo = MetaInfo.readMetaFile(decodedApk); - assertEquals("v1.0.0", metaInfo.versionInfo.versionName); + ApkInfo apkInfo = ApkInfo.load(decodedApk); + assertEquals("v1.0.0", apkInfo.versionInfo.versionName); } } 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 9e22e1b5ed..bc59d963e8 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 @@ -18,7 +18,6 @@ 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; diff --git a/brut.apktool/apktool-lib/src/test/java/brut/androlib/aapt2/BuildAndDecodeTest.java b/brut.apktool/apktool-lib/src/test/java/brut/androlib/aapt2/BuildAndDecodeTest.java index 8dba639cf5..97c98290ca 100644 --- a/brut.apktool/apktool-lib/src/test/java/brut/androlib/aapt2/BuildAndDecodeTest.java +++ b/brut.apktool/apktool-lib/src/test/java/brut/androlib/aapt2/BuildAndDecodeTest.java @@ -17,9 +17,8 @@ package brut.androlib.aapt2; import brut.androlib.*; -import brut.androlib.meta.MetaInfo; +import brut.androlib.apk.ApkInfo; import brut.androlib.Config; -import brut.androlib.res.AndrolibResources; import brut.common.BrutException; import brut.directory.ExtFile; import brut.util.OS; @@ -79,14 +78,14 @@ public void valuesMaxLengthTest() throws BrutException { @Test public void confirmZeroByteFileExtensionIsNotStored() throws BrutException { - MetaInfo metaInfo = MetaInfo.readMetaFile(sTestNewDir); - assertFalse(metaInfo.doNotCompress.contains("jpg")); + ApkInfo apkInfo = ApkInfo.load(sTestNewDir); + assertFalse(apkInfo.doNotCompress.contains("jpg")); } @Test public void confirmZeroByteFileIsStored() throws BrutException { - MetaInfo metaInfo = MetaInfo.readMetaFile(sTestNewDir); - assertTrue(metaInfo.doNotCompress.contains("assets/0byte_file.jpg")); + ApkInfo apkInfo = ApkInfo.load(sTestNewDir); + assertTrue(apkInfo.doNotCompress.contains("assets/0byte_file.jpg")); } @Test diff --git a/brut.apktool/apktool-lib/src/test/java/brut/androlib/androlib/InvalidSdkBoundingTest.java b/brut.apktool/apktool-lib/src/test/java/brut/androlib/androlib/InvalidSdkBoundingTest.java index 5cd15744d6..f9130f44bf 100644 --- a/brut.apktool/apktool-lib/src/test/java/brut/androlib/androlib/InvalidSdkBoundingTest.java +++ b/brut.apktool/apktool-lib/src/test/java/brut/androlib/androlib/InvalidSdkBoundingTest.java @@ -17,9 +17,9 @@ package brut.androlib.androlib; import brut.androlib.BaseTest; -import brut.androlib.Config; -import brut.androlib.res.AndrolibResources; +import brut.androlib.apk.ApkInfo; import org.junit.Test; + import java.util.LinkedHashMap; import java.util.Map; import static org.junit.Assert.assertEquals; @@ -28,82 +28,82 @@ public class InvalidSdkBoundingTest extends BaseTest { @Test public void checkIfInvalidValuesPass() { - AndrolibResources androlibResources = new AndrolibResources(); + ApkInfo apkInfo = new ApkInfo(); Map sdkInfo = new LinkedHashMap<>(); sdkInfo.put("minSdkVersion", "15"); sdkInfo.put("targetSdkVersion", "25"); sdkInfo.put("maxSdkVersion", "19"); - androlibResources.setSdkInfo(sdkInfo); - assertEquals("19", androlibResources.checkTargetSdkVersionBounds()); + apkInfo.setSdkInfo(sdkInfo); + assertEquals("19", apkInfo.checkTargetSdkVersionBounds()); } @Test public void checkIfMissingMinPasses() { - AndrolibResources androlibResources = new AndrolibResources(); + ApkInfo apkInfo = new ApkInfo(); Map sdkInfo = new LinkedHashMap<>(); sdkInfo.put("targetSdkVersion", "25"); sdkInfo.put("maxSdkVersion", "19"); - androlibResources.setSdkInfo(sdkInfo); - assertEquals("19", androlibResources.checkTargetSdkVersionBounds()); + apkInfo.setSdkInfo(sdkInfo); + assertEquals("19", apkInfo.checkTargetSdkVersionBounds()); } @Test public void checkIfMissingMaxPasses() { - AndrolibResources androlibResources = new AndrolibResources(); + ApkInfo apkInfo = new ApkInfo(); Map sdkInfo = new LinkedHashMap<>(); sdkInfo.put("minSdkVersion", "15"); sdkInfo.put("targetSdkVersion", "25"); - androlibResources.setSdkInfo(sdkInfo); - assertEquals("25", androlibResources.checkTargetSdkVersionBounds()); + apkInfo.setSdkInfo(sdkInfo); + assertEquals("25", apkInfo.checkTargetSdkVersionBounds()); } @Test public void checkIfMissingBothPasses() { - AndrolibResources androlibResources = new AndrolibResources(); + ApkInfo apkInfo = new ApkInfo(); Map sdkInfo = new LinkedHashMap<>(); sdkInfo.put("targetSdkVersion", "25"); - androlibResources.setSdkInfo(sdkInfo); - assertEquals("25", androlibResources.checkTargetSdkVersionBounds()); + apkInfo.setSdkInfo(sdkInfo); + assertEquals("25", apkInfo.checkTargetSdkVersionBounds()); } @Test public void checkForShortHandSTag() { - AndrolibResources androlibResources = new AndrolibResources(); + ApkInfo apkInfo = new ApkInfo(); Map sdkInfo = new LinkedHashMap<>(); sdkInfo.put("targetSdkVersion", "S"); - androlibResources.setSdkInfo(sdkInfo); - assertEquals("31", androlibResources.checkTargetSdkVersionBounds()); + apkInfo.setSdkInfo(sdkInfo); + assertEquals("31", apkInfo.checkTargetSdkVersionBounds()); } @Test public void checkForShortHandSdkTag() { - AndrolibResources androlibResources = new AndrolibResources(); + ApkInfo apkInfo = new ApkInfo(); Map sdkInfo = new LinkedHashMap<>(); sdkInfo.put("targetSdkVersion", "O"); - androlibResources.setSdkInfo(sdkInfo); - assertEquals("26", androlibResources.checkTargetSdkVersionBounds()); + apkInfo.setSdkInfo(sdkInfo); + assertEquals("26", apkInfo.checkTargetSdkVersionBounds()); } @Test public void checkForSdkDevelopmentInsaneTestValue() { - AndrolibResources androlibResources = new AndrolibResources(); + ApkInfo apkInfo = new ApkInfo(); Map sdkInfo = new LinkedHashMap<>(); sdkInfo.put("targetSdkVersion", "VANILLAICECREAM"); - androlibResources.setSdkInfo(sdkInfo); - assertEquals("10000", androlibResources.checkTargetSdkVersionBounds()); + apkInfo.setSdkInfo(sdkInfo); + assertEquals("10000", apkInfo.checkTargetSdkVersionBounds()); } } diff --git a/brut.apktool/apktool-lib/src/test/java/brut/androlib/decode/DoubleExtensionUnknownFileTest.java b/brut.apktool/apktool-lib/src/test/java/brut/androlib/decode/DoubleExtensionUnknownFileTest.java index 9eb9ff23e8..29c68a1b9b 100644 --- a/brut.apktool/apktool-lib/src/test/java/brut/androlib/decode/DoubleExtensionUnknownFileTest.java +++ b/brut.apktool/apktool-lib/src/test/java/brut/androlib/decode/DoubleExtensionUnknownFileTest.java @@ -16,11 +16,10 @@ */ package brut.androlib.decode; -import brut.androlib.ApkBuilder; import brut.androlib.ApkDecoder; import brut.androlib.BaseTest; import brut.androlib.TestUtils; -import brut.androlib.meta.MetaInfo; +import brut.androlib.apk.ApkInfo; import brut.directory.ExtFile; import brut.common.BrutException; import brut.util.OS; @@ -58,8 +57,8 @@ public void multipleExtensionUnknownFileTest() throws BrutException, IOException File outDir = new File(sTmpDir + File.separator + apk + ".out"); apkDecoder.decode(outDir); - MetaInfo metaInfo = MetaInfo.readMetaFile(decodedApk); - for (String string : metaInfo.doNotCompress) { + ApkInfo apkInfo = ApkInfo.load(decodedApk); + for (String string : apkInfo.doNotCompress) { if (StringUtils.countMatches(string, ".") > 1) { assertTrue(string.equalsIgnoreCase("assets/bin/Data/sharedassets1.assets.split0")); } diff --git a/brut.apktool/apktool-lib/src/test/java/brut/androlib/decode/MissingVersionManifestTest.java b/brut.apktool/apktool-lib/src/test/java/brut/androlib/decode/MissingVersionManifestTest.java index 7da4cd4e99..55eaf149a4 100644 --- a/brut.apktool/apktool-lib/src/test/java/brut/androlib/decode/MissingVersionManifestTest.java +++ b/brut.apktool/apktool-lib/src/test/java/brut/androlib/decode/MissingVersionManifestTest.java @@ -16,11 +16,10 @@ */ package brut.androlib.decode; -import brut.androlib.ApkBuilder; import brut.androlib.ApkDecoder; import brut.androlib.BaseTest; import brut.androlib.TestUtils; -import brut.androlib.meta.MetaInfo; +import brut.androlib.apk.ApkInfo; import brut.common.BrutException; import brut.directory.ExtFile; import brut.util.OS; @@ -57,7 +56,7 @@ public void missingVersionParsesCorrectlyTest() throws BrutException, IOExceptio File outDir = new File(sTmpDir + File.separator + apk + ".out"); apkDecoder.decode(outDir); - MetaInfo metaInfo = MetaInfo.readMetaFile(decodedApk); - assertNull(metaInfo.versionInfo.versionName); + ApkInfo apkInfo = ApkInfo.load(decodedApk); + assertNull(apkInfo.versionInfo.versionName); } } From b19fd9135d1ad25c7a03c6b4871e096aab703d6d Mon Sep 17 00:00:00 2001 From: Slava Volkov Date: Mon, 3 Jul 2023 08:58:55 +0300 Subject: [PATCH 2/5] extract ARSCData and FlagsOffset from ARSCDecoder and remove unused imports --- .../apktool-lib/src/main/java/brut/androlib/ApkBuilder.java | 1 - .../apktool-lib/src/main/java/brut/androlib/Config.java | 1 - .../src/main/java/brut/androlib/res/ResourcesDecoder.java | 5 ----- 3 files changed, 7 deletions(-) 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 b252cbd183..299e2e3fa5 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 @@ -19,7 +19,6 @@ import brut.androlib.exceptions.AndrolibException; import brut.androlib.apk.ApkInfo; import brut.androlib.apk.UsesFramework; -import brut.androlib.res.ResourcesDecoder; import brut.androlib.res.Framework; import brut.androlib.res.data.ResConfigFlags; import brut.androlib.res.xml.ResXmlPatcher; diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/Config.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/Config.java index 229b91925d..68ba9728cf 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/Config.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/Config.java @@ -20,7 +20,6 @@ import brut.util.OSDetection; import java.io.File; -import java.util.Collection; import java.util.logging.Logger; public class Config { diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResourcesDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResourcesDecoder.java index ef4ab70d98..ef5423517f 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResourcesDecoder.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResourcesDecoder.java @@ -16,13 +16,8 @@ */ package brut.androlib.res; -import brut.androlib.exceptions.AndrolibException; import brut.androlib.Config; -import brut.directory.*; -import org.apache.commons.io.IOUtils; -import java.io.*; -import java.util.*; import java.util.logging.Logger; final public class ResourcesDecoder { From 3257f6af1107960ac5fd50f929032d3e556c0946 Mon Sep 17 00:00:00 2001 From: Slava Volkov Date: Mon, 3 Jul 2023 15:45:17 +0300 Subject: [PATCH 3/5] rebase to master --- .../src/main/java/brut/androlib/AaptInvoker.java | 16 ++++++++++++++++ .../src/main/java/brut/androlib/apk/ApkInfo.java | 5 +++++ .../main/java/brut/androlib/res/Framework.java | 4 ++-- .../res/{decoder => data}/arsc/ARSCData.java | 2 +- .../res/{decoder => data}/arsc/ARSCHeader.java | 2 +- .../res/{decoder => data}/arsc/EntryData.java | 2 +- .../res/{decoder => data}/arsc/FlagsOffset.java | 2 +- .../{decoder => data}/axml/NamespaceStack.java | 2 +- .../ninepatch/NinePatchData.java | 2 +- .../ninepatch/OpticalInset.java | 2 +- .../brut/androlib/res/decoder/ARSCDecoder.java | 8 ++++---- .../androlib/res/decoder/AXmlResourceParser.java | 2 +- .../res/decoder/Res9patchStreamDecoder.java | 4 ++-- 13 files changed, 37 insertions(+), 16 deletions(-) rename brut.apktool/apktool-lib/src/main/java/brut/androlib/res/{decoder => data}/arsc/ARSCData.java (98%) rename brut.apktool/apktool-lib/src/main/java/brut/androlib/res/{decoder => data}/arsc/ARSCHeader.java (98%) rename brut.apktool/apktool-lib/src/main/java/brut/androlib/res/{decoder => data}/arsc/EntryData.java (95%) rename brut.apktool/apktool-lib/src/main/java/brut/androlib/res/{decoder => data}/arsc/FlagsOffset.java (95%) rename brut.apktool/apktool-lib/src/main/java/brut/androlib/res/{decoder => data}/axml/NamespaceStack.java (99%) rename brut.apktool/apktool-lib/src/main/java/brut/androlib/res/{decoder => data}/ninepatch/NinePatchData.java (97%) rename brut.apktool/apktool-lib/src/main/java/brut/androlib/res/{decoder => data}/ninepatch/OpticalInset.java (97%) diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/AaptInvoker.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/AaptInvoker.java index 08e63a050a..18567ed3da 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/AaptInvoker.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/AaptInvoker.java @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2010 Ryszard Wiśniewski + * Copyright (C) 2010 Connor Tumbleson + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package brut.androlib; import brut.androlib.exceptions.AndrolibException; diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/ApkInfo.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/ApkInfo.java index ad423f7768..03c7c96cb0 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/ApkInfo.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/ApkInfo.java @@ -139,6 +139,11 @@ private int mapSdkShorthandToVersion(String sdkVersion) { return ResConfigFlags.SDK_S_V2; case "T": case "TIRAMISU": + return ResConfigFlags.SDK_TIRAMISU; + case "UPSIDEDOWNCAKE": + case "UPSIDE_DOWN_CAKE": + case "VANILLAICECREAM": + case "VANILLA_ICE_CREAM": return ResConfigFlags.SDK_DEVELOPMENT; default: return Integer.parseInt(sdkVersion); 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 index 19d52b5963..37379af2af 100644 --- 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 @@ -20,8 +20,8 @@ import brut.androlib.exceptions.AndrolibException; import brut.androlib.exceptions.CantFindFrameworkResException; import brut.androlib.res.decoder.ARSCDecoder; -import brut.androlib.res.decoder.arsc.ARSCData; -import brut.androlib.res.decoder.arsc.FlagsOffset; +import brut.androlib.res.data.arsc.ARSCData; +import brut.androlib.res.data.arsc.FlagsOffset; import brut.util.Jar; import org.apache.commons.io.IOUtils; diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/arsc/ARSCData.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/arsc/ARSCData.java similarity index 98% rename from brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/arsc/ARSCData.java rename to brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/arsc/ARSCData.java index cca63faf11..aad1a24c26 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/arsc/ARSCData.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/arsc/ARSCData.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package brut.androlib.res.decoder.arsc; +package brut.androlib.res.data.arsc; import brut.androlib.exceptions.AndrolibException; import brut.androlib.res.data.ResPackage; diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/arsc/ARSCHeader.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/arsc/ARSCHeader.java similarity index 98% rename from brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/arsc/ARSCHeader.java rename to brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/arsc/ARSCHeader.java index 50a35c3a95..cabea005bc 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/arsc/ARSCHeader.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/arsc/ARSCHeader.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package brut.androlib.res.decoder.arsc; +package brut.androlib.res.data.arsc; import brut.util.ExtDataInput; import org.apache.commons.io.input.CountingInputStream; diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/arsc/EntryData.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/arsc/EntryData.java similarity index 95% rename from brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/arsc/EntryData.java rename to brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/arsc/EntryData.java index 5aaef9426a..66fe78b630 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/arsc/EntryData.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/arsc/EntryData.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package brut.androlib.res.decoder.arsc; +package brut.androlib.res.data.arsc; import brut.androlib.res.data.value.ResValue; diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/arsc/FlagsOffset.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/arsc/FlagsOffset.java similarity index 95% rename from brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/arsc/FlagsOffset.java rename to brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/arsc/FlagsOffset.java index 281d68ee82..380ab4b6ce 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/arsc/FlagsOffset.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/arsc/FlagsOffset.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package brut.androlib.res.decoder.arsc; +package brut.androlib.res.data.arsc; public class FlagsOffset { public final int offset; diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/axml/NamespaceStack.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/axml/NamespaceStack.java similarity index 99% rename from brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/axml/NamespaceStack.java rename to brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/axml/NamespaceStack.java index 407f584bfb..c07731c71b 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/axml/NamespaceStack.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/axml/NamespaceStack.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package brut.androlib.res.decoder.axml; +package brut.androlib.res.data.axml; /** * Namespace stack, holds prefix+uri pairs, as well as depth information. diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ninepatch/NinePatchData.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ninepatch/NinePatchData.java similarity index 97% rename from brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ninepatch/NinePatchData.java rename to brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ninepatch/NinePatchData.java index c34a546e90..4cde7304d9 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ninepatch/NinePatchData.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ninepatch/NinePatchData.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package brut.androlib.res.decoder.ninepatch; +package brut.androlib.res.data.ninepatch; import brut.util.ExtDataInput; import java.io.IOException; diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ninepatch/OpticalInset.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ninepatch/OpticalInset.java similarity index 97% rename from brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ninepatch/OpticalInset.java rename to brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ninepatch/OpticalInset.java index 8cf8a00b55..49ddee8ff3 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ninepatch/OpticalInset.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ninepatch/OpticalInset.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package brut.androlib.res.decoder.ninepatch; +package brut.androlib.res.data.ninepatch; import brut.util.ExtDataInput; import java.io.IOException; diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ARSCDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ARSCDecoder.java index ea8c775a50..ce316d6a7a 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ARSCDecoder.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ARSCDecoder.java @@ -20,10 +20,10 @@ import brut.androlib.exceptions.AndrolibException; import brut.androlib.res.data.*; import brut.androlib.res.data.value.*; -import brut.androlib.res.decoder.arsc.ARSCData; -import brut.androlib.res.decoder.arsc.ARSCHeader; -import brut.androlib.res.decoder.arsc.EntryData; -import brut.androlib.res.decoder.arsc.FlagsOffset; +import brut.androlib.res.data.arsc.ARSCData; +import brut.androlib.res.data.arsc.ARSCHeader; +import brut.androlib.res.data.arsc.EntryData; +import brut.androlib.res.data.arsc.FlagsOffset; import brut.util.Duo; import brut.util.ExtDataInput; import com.google.common.io.LittleEndianDataInputStream; diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/AXmlResourceParser.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/AXmlResourceParser.java index 877fa50412..075a6c090f 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/AXmlResourceParser.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/AXmlResourceParser.java @@ -20,7 +20,7 @@ import android.util.TypedValue; import brut.androlib.exceptions.AndrolibException; import brut.androlib.res.data.ResID; -import brut.androlib.res.decoder.axml.NamespaceStack; +import brut.androlib.res.data.axml.NamespaceStack; import brut.androlib.res.xml.ResXmlEncoders; import brut.util.ExtDataInput; import com.google.common.io.LittleEndianDataInputStream; diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/Res9patchStreamDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/Res9patchStreamDecoder.java index 0d717d9628..5dfad02ee0 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/Res9patchStreamDecoder.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/Res9patchStreamDecoder.java @@ -18,8 +18,8 @@ import brut.androlib.exceptions.AndrolibException; import brut.androlib.exceptions.CantFind9PatchChunkException; -import brut.androlib.res.decoder.ninepatch.NinePatchData; -import brut.androlib.res.decoder.ninepatch.OpticalInset; +import brut.androlib.res.data.ninepatch.NinePatchData; +import brut.androlib.res.data.ninepatch.OpticalInset; import brut.util.ExtDataInput; import org.apache.commons.io.IOUtils; From 32710ff006393af2d0ec57d743020de7be3ccef8 Mon Sep 17 00:00:00 2001 From: Slava Volkov Date: Mon, 3 Jul 2023 12:55:24 +0300 Subject: [PATCH 4/5] move decodeManifest and decodeResources to the ResourceDecoder --- .../main/java/brut/androlib/ApkDecoder.java | 347 ++---------------- .../brut/androlib/res/ResourcesDecoder.java | 328 ++++++++++++++++- .../androlib/aapt2/NonStandardPkgIdTest.java | 10 +- .../brut/androlib/decode/DecodeArrayTest.java | 15 +- 4 files changed, 367 insertions(+), 333 deletions(-) diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkDecoder.java index e503f21650..3cf2bd4795 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkDecoder.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkDecoder.java @@ -20,27 +20,19 @@ import brut.androlib.exceptions.InFileNotFoundException; import brut.androlib.exceptions.OutDirExistsException; import brut.androlib.apk.ApkInfo; +import brut.androlib.res.ResourcesDecoder; import brut.androlib.res.data.*; -import brut.androlib.res.decoder.*; -import brut.androlib.res.util.ExtMXSerializer; -import brut.androlib.res.util.ExtXmlSerializer; -import brut.androlib.res.xml.ResValuesXmlSerializable; -import brut.androlib.res.xml.ResXmlPatcher; import brut.androlib.src.SmaliDecoder; import brut.directory.Directory; import brut.directory.ExtFile; import brut.common.BrutException; import brut.directory.DirectoryException; -import brut.directory.FileDirectory; -import brut.util.Duo; import brut.util.OS; import com.android.tools.smali.dexlib2.iface.DexFile; import org.apache.commons.io.FilenameUtils; -import org.xmlpull.v1.XmlSerializer; import java.io.File; import java.io.IOException; -import java.io.OutputStream; import java.util.*; import java.util.logging.Logger; import java.util.regex.Pattern; @@ -51,19 +43,12 @@ public class ApkDecoder { private final Config mConfig; private final ExtFile mApkFile; - private final ApkInfo mApkInfo; - private final ResTable mResTable; protected final ResUnknownFiles mResUnknownFiles; private int mMinSdkVersion = 0; - private final Map mResFileMapping = new HashMap<>(); private final static String SMALI_DIRNAME = "smali"; private final static String UNK_DIRNAME = "unknown"; - private final static String[] APK_RESOURCES_FILENAMES = new String[] { - "resources.arsc", "res", "r", "R" }; - private final static String[] APK_MANIFEST_FILENAMES = new String[] { - "AndroidManifest.xml" }; private final static String[] APK_STANDARD_ALL_FILENAMES = new String[] { "classes.dex", "AndroidManifest.xml", "resources.arsc", "res", "r", "R", "lib", "libs", "assets", "META-INF", "kotlin" }; @@ -71,10 +56,6 @@ public class ApkDecoder { "jpg|jpeg|png|gif|wav|mp2|mp3|ogg|aac|mpg|mpeg|mid|midi|smf|jet|rtttl|imy|xmf|mp4|" + "m4a|m4v|3gp|3gpp|3g2|3gpp2|amr|awb|wma|wmv|webm|webp|mkv)$"); - private final static String[] IGNORED_PACKAGES = new String[] { - "android", "com.htc", "com.lge", "com.lge.internal", "yi", "flyme", "air.com.adobe.appentry", - "FFFFFFFFFFFFFFFFFFFFFF" }; - public ApkDecoder(ExtFile apkFile) { this(Config.getDefaultConfig(), apkFile); } @@ -84,9 +65,6 @@ public ApkDecoder(Config config, ExtFile apkFile) { //mAndRes = new AndrolibResources(config); mResUnknownFiles = new ResUnknownFiles(); mApkFile = apkFile; - mApkInfo = new ApkInfo(); - mApkInfo.setApkFileName(apkFile.getName()); - mResTable = new ResTable(mConfig, mApkInfo); } public ApkDecoder(File apkFile) { @@ -116,20 +94,9 @@ public void decode(File outDir) throws AndrolibException, IOException, Directory LOGGER.info("Using Apktool " + ApktoolProperties.getVersion() + " on " + mApkFile.getName()); - decodeManifest(outDir); - decodeResources(outDir); - - // update apk info - if (mResTable != null) { - mResTable.initApkInfo(mApkInfo, outDir); - if (mConfig.frameworkTag != null) { - mApkInfo.usesFramework.tag = mConfig.frameworkTag; - } - } else { - if (mMinSdkVersion > 0) { - mApkInfo.setSdkInfo(getMinSdkInfo()); - } - } + ResourcesDecoder resourcesDecoder = new ResourcesDecoder(mConfig, mApkFile); + resourcesDecoder.decodeManifest(outDir); + resourcesDecoder.decodeResources(outDir); if (hasSources()) { switch (mConfig.decodeSources) { @@ -168,13 +135,17 @@ public void decode(File outDir) throws AndrolibException, IOException, Directory } } } + ApkInfo apkInfo = resourcesDecoder.getApkInfo(); + if (mMinSdkVersion > 0) { + apkInfo.setSdkInfo(getMinSdkInfo()); + } copyRawFiles(outDir); - copyUnknownFiles(outDir); + copyUnknownFiles(apkInfo, outDir); Collection mUncompressedFiles = new ArrayList<>(); - recordUncompressedFiles(mUncompressedFiles); + recordUncompressedFiles(apkInfo, resourcesDecoder.getResFileMapping(), mUncompressedFiles); copyOriginalFiles(outDir); - writeApkInfo(outDir); + writeApkInfo(apkInfo, outDir); } finally { try { mApkFile.close(); @@ -182,17 +153,6 @@ public void decode(File outDir) throws AndrolibException, IOException, Directory } } - public ResTable getResTable() throws AndrolibException { - if (! (hasManifest() || hasResources())) { - throw new AndrolibException( - "Apk doesn't contain either AndroidManifest.xml file or resources.arsc file"); - } - if (hasResources() && !mResTable.isMainPkgLoaded()) { - mResTable.loadMainPkg(mApkFile); - } - return mResTable; - } - private boolean hasSources() throws AndrolibException { try { return mApkFile.getDirectory().containsFile("classes.dex"); @@ -218,29 +178,13 @@ private boolean hasMultipleSources() throws AndrolibException { } } - private boolean hasManifest() throws AndrolibException { - try { - return mApkFile.getDirectory().containsFile("AndroidManifest.xml"); - } catch (DirectoryException ex) { - throw new AndrolibException(ex); - } - } - - private boolean hasResources() throws AndrolibException { - try { - return mApkFile.getDirectory().containsFile("resources.arsc"); - } catch (DirectoryException ex) { - throw new AndrolibException(ex); - } - } - public void close() throws IOException { //mAndRes.close(); } - private void writeApkInfo(File outDir) throws AndrolibException { + private void writeApkInfo(ApkInfo apkInfo, File outDir) throws AndrolibException { try { - mApkInfo.save(new File(outDir, "apktool.yml")); + apkInfo.save(new File(outDir, "apktool.yml")); } catch (IOException ex) { throw new AndrolibException(ex); } @@ -286,256 +230,6 @@ private void decodeSourcesSmali(File outDir, String filename) } } - private void decodeManifest(File outDir) throws AndrolibException { - if (hasManifest()) { - if (mConfig.decodeResources == Config.DECODE_RESOURCES_FULL || - mConfig.forceDecodeManifest == Config.FORCE_DECODE_MANIFEST_FULL) { - if (hasResources()) { - decodeManifestWithResources(getResTable(), mApkFile, outDir); - if (!mConfig.analysisMode) { - // update apk info - mApkInfo.packageInfo.forcedPackageId = String.valueOf(mResTable.getPackageId()); - } - } else { - // if there's no resources.arsc, decode the manifest without looking - // up attribute references - decodeManifest(getResTable(), mApkFile, outDir); - } - } - else { - try { - LOGGER.info("Copying raw manifest..."); - mApkFile.getDirectory().copyToDir(outDir, APK_MANIFEST_FILENAMES); - } catch (DirectoryException ex) { - throw new AndrolibException(ex); - } - } - } - } - - private void decodeManifest(ResTable resTable, ExtFile apkFile, File outDir) - throws AndrolibException { - - Duo duo = getManifestFileDecoder(false); - ResFileDecoder fileDecoder = duo.m1; - - // Set ResAttrDecoder - duo.m2.setAttrDecoder(new ResAttrDecoder()); - ResAttrDecoder attrDecoder = duo.m2.getAttrDecoder(); - - // Fake ResPackage - attrDecoder.setCurrentPackage(new ResPackage(resTable, 0, null)); - - Directory inApk, out; - try { - inApk = apkFile.getDirectory(); - out = new FileDirectory(outDir); - - LOGGER.info("Decoding AndroidManifest.xml with only framework resources..."); - fileDecoder.decodeManifest(inApk, "AndroidManifest.xml", out, "AndroidManifest.xml"); - - } catch (DirectoryException ex) { - throw new AndrolibException(ex); - } - } - - private void decodeManifestWithResources(ResTable resTable, ExtFile apkFile, File outDir) - throws AndrolibException { - - Duo duo = getManifestFileDecoder(true); - ResFileDecoder fileDecoder = duo.m1; - ResAttrDecoder attrDecoder = duo.m2.getAttrDecoder(); - - attrDecoder.setCurrentPackage(resTable.listMainPackages().iterator().next()); - - Directory inApk, out; - try { - inApk = apkFile.getDirectory(); - out = new FileDirectory(outDir); - LOGGER.info("Decoding AndroidManifest.xml with resources..."); - - fileDecoder.decodeManifest(inApk, "AndroidManifest.xml", out, "AndroidManifest.xml"); - - // Remove versionName / versionCode (aapt API 16) - if (!mConfig.analysisMode) { - - // check for a mismatch between resources.arsc package and the package listed in AndroidManifest - // also remove the android::versionCode / versionName from manifest for rebuild - // this is a required change to prevent aapt warning about conflicting versions - // it will be passed as a parameter to aapt like "--min-sdk-version" via apktool.yml - adjustPackageManifest(resTable, outDir.getAbsolutePath() + File.separator + "AndroidManifest.xml"); - - ResXmlPatcher.removeManifestVersions(new File( - outDir.getAbsolutePath() + File.separator + "AndroidManifest.xml")); - } - } catch (DirectoryException ex) { - throw new AndrolibException(ex); - } - } - - private void adjustPackageManifest(ResTable resTable, String filePath) - throws AndrolibException { - - // compare resources.arsc package name to the one present in AndroidManifest - ResPackage resPackage = resTable.getCurrentResPackage(); - String pkgOriginal = resPackage.getName(); - String packageRenamed = resTable.getPackageRenamed(); - - resTable.setPackageId(resPackage.getId()); - resTable.setPackageOriginal(pkgOriginal); - - // 1) Check if pkgOriginal is null (empty resources.arsc) - // 2) Check if pkgOriginal === mPackageRenamed - // 3) Check if pkgOriginal is ignored via IGNORED_PACKAGES - if (pkgOriginal == null || pkgOriginal.equalsIgnoreCase(packageRenamed) - || (Arrays.asList(IGNORED_PACKAGES).contains(pkgOriginal))) { - LOGGER.info("Regular manifest package..."); - } else { - LOGGER.info("Renamed manifest package found! Replacing " + packageRenamed + " with " + pkgOriginal); - ResXmlPatcher.renameManifestPackage(new File(filePath), pkgOriginal); - } - } - - private Duo getManifestFileDecoder(boolean withResources) { - ResStreamDecoderContainer decoders = new ResStreamDecoderContainer(); - - AXmlResourceParser axmlParser = new AndroidManifestResourceParser(); - if (withResources) { - axmlParser.setAttrDecoder(new ResAttrDecoder()); - } - decoders.setDecoder("xml", new XmlPullStreamDecoder(axmlParser, getResXmlSerializer())); - - return new Duo<>(new ResFileDecoder(decoders), axmlParser); - } - - private ExtMXSerializer getResXmlSerializer() { - ExtMXSerializer serial = new ExtMXSerializer(); - serial.setProperty(ExtXmlSerializer.PROPERTY_SERIALIZER_INDENTATION, " "); - serial.setProperty(ExtXmlSerializer.PROPERTY_SERIALIZER_LINE_SEPARATOR, System.getProperty("line.separator")); - serial.setProperty(ExtXmlSerializer.PROPERTY_DEFAULT_ENCODING, "utf-8"); - serial.setDisabledAttrEscape(true); - return serial; - } - - private void decodeResources(File outDir) throws AndrolibException { - if (hasResources()) { - switch (mConfig.decodeResources) { - case Config.DECODE_RESOURCES_NONE: - try { - LOGGER.info("Copying raw resources..."); - mApkFile.getDirectory().copyToDir(outDir, APK_RESOURCES_FILENAMES); - } catch (DirectoryException ex) { - throw new AndrolibException(ex); - } - break; - case Config.DECODE_RESOURCES_FULL: - decodeResources(getResTable(), mApkFile, outDir); - break; - } - } - } - - private void decodeResources(ResTable resTable, ExtFile apkFile, File outDir) - throws AndrolibException { - Duo duo = getResFileDecoder(); - ResFileDecoder fileDecoder = duo.m1; - ResAttrDecoder attrDecoder = duo.m2.getAttrDecoder(); - - attrDecoder.setCurrentPackage(resTable.listMainPackages().iterator().next()); - Directory in, out; - - try { - out = new FileDirectory(outDir); - in = apkFile.getDirectory(); - out = out.createDir("res"); - } catch (DirectoryException ex) { - throw new AndrolibException(ex); - } - - ExtMXSerializer xmlSerializer = getResXmlSerializer(); - for (ResPackage pkg : resTable.listMainPackages()) { - attrDecoder.setCurrentPackage(pkg); - - LOGGER.info("Decoding file-resources..."); - for (ResResource res : pkg.listFiles()) { - fileDecoder.decode(res, in, out, mResFileMapping); - } - - LOGGER.info("Decoding values */* XMLs..."); - for (ResValuesFile valuesFile : pkg.listValuesFiles()) { - generateValuesFile(valuesFile, out, xmlSerializer); - } - generatePublicXml(pkg, out, xmlSerializer); - } - - AndrolibException decodeError = duo.m2.getFirstError(); - if (decodeError != null) { - throw decodeError; - } - } - - private Duo getResFileDecoder() { - ResStreamDecoderContainer decoders = new ResStreamDecoderContainer(); - decoders.setDecoder("raw", new ResRawStreamDecoder()); - decoders.setDecoder("9patch", new Res9patchStreamDecoder()); - - AXmlResourceParser axmlParser = new AXmlResourceParser(); - axmlParser.setAttrDecoder(new ResAttrDecoder()); - decoders.setDecoder("xml", new XmlPullStreamDecoder(axmlParser, getResXmlSerializer())); - - return new Duo<>(new ResFileDecoder(decoders), axmlParser); - } - - private void generateValuesFile(ResValuesFile valuesFile, Directory out, - ExtXmlSerializer serial) throws AndrolibException { - try { - OutputStream outStream = out.getFileOutput(valuesFile.getPath()); - serial.setOutput((outStream), null); - serial.startDocument(null, null); - serial.startTag(null, "resources"); - - for (ResResource res : valuesFile.listResources()) { - if (valuesFile.isSynthesized(res)) { - continue; - } - ((ResValuesXmlSerializable) res.getValue()).serializeToResValuesXml(serial, res); - } - - serial.endTag(null, "resources"); - serial.newLine(); - serial.endDocument(); - serial.flush(); - outStream.close(); - } catch (IOException | DirectoryException ex) { - throw new AndrolibException("Could not generate: " + valuesFile.getPath(), ex); - } - } - - private void generatePublicXml(ResPackage pkg, Directory out, - XmlSerializer serial) throws AndrolibException { - try { - OutputStream outStream = out.getFileOutput("values/public.xml"); - serial.setOutput(outStream, null); - serial.startDocument(null, null); - serial.startTag(null, "resources"); - - for (ResResSpec spec : pkg.listResSpecs()) { - serial.startTag(null, "public"); - serial.attribute(null, "type", spec.getType().getName()); - serial.attribute(null, "name", spec.getName()); - serial.attribute(null, "id", String.format("0x%08x", spec.getId().id)); - serial.endTag(null, "public"); - } - - serial.endTag(null, "resources"); - serial.endDocument(); - serial.flush(); - outStream.close(); - } catch (IOException | DirectoryException ex) { - throw new AndrolibException("Could not generate public.xml file", ex); - } - } - private void copyRawFiles(File outDir) throws AndrolibException { LOGGER.info("Copying assets and libs..."); @@ -570,7 +264,7 @@ private boolean isAPKFileNames(String file) { return false; } - private void copyUnknownFiles(File outDir) + private void copyUnknownFiles(ApkInfo apkInfo, File outDir) throws AndrolibException { LOGGER.info("Copying unknown files..."); File unknownOut = new File(outDir, UNK_DIRNAME); @@ -590,7 +284,7 @@ private void copyUnknownFiles(File outDir) } } // update apk info - mApkInfo.unknownFiles = mResUnknownFiles.getUnknownFiles(); + apkInfo.unknownFiles = mResUnknownFiles.getUnknownFiles(); } catch (DirectoryException ex) { throw new AndrolibException(ex); } @@ -629,7 +323,10 @@ private void copyOriginalFiles(File outDir) } } - private void recordUncompressedFiles(Collection uncompressedFilesOrExts) throws AndrolibException { + private void recordUncompressedFiles(ApkInfo apkInfo, + Map resFileMapping, + Collection uncompressedFilesOrExts) + throws AndrolibException { try { Directory unk = mApkFile.getDirectory(); Set files = unk.getFiles(true); @@ -643,8 +340,8 @@ private void recordUncompressedFiles(Collection uncompressedFilesOrExts) if (extOrFile.isEmpty() || !NO_COMPRESS_PATTERN.matcher(extOrFile).find()) { extOrFile = file; - if (mResFileMapping.containsKey(extOrFile)) { - extOrFile = mResFileMapping.get(extOrFile); + if (resFileMapping.containsKey(extOrFile)) { + extOrFile = resFileMapping.get(extOrFile); } } if (!uncompressedFilesOrExts.contains(extOrFile)) { @@ -654,7 +351,7 @@ private void recordUncompressedFiles(Collection uncompressedFilesOrExts) } // update apk info if (!uncompressedFilesOrExts.isEmpty()) { - mApkInfo.doNotCompress = uncompressedFilesOrExts; + apkInfo.doNotCompress = uncompressedFilesOrExts; } } catch (DirectoryException ex) { diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResourcesDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResourcesDecoder.java index ef5423517f..0b8247c05c 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResourcesDecoder.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResourcesDecoder.java @@ -17,7 +17,27 @@ package brut.androlib.res; import brut.androlib.Config; +import brut.androlib.apk.ApkInfo; +import brut.androlib.exceptions.AndrolibException; +import brut.androlib.res.data.*; +import brut.androlib.res.decoder.*; +import brut.androlib.res.util.ExtMXSerializer; +import brut.androlib.res.util.ExtXmlSerializer; +import brut.androlib.res.xml.ResValuesXmlSerializable; +import brut.androlib.res.xml.ResXmlPatcher; +import brut.directory.Directory; +import brut.directory.DirectoryException; +import brut.directory.ExtFile; +import brut.directory.FileDirectory; +import brut.util.Duo; +import org.xmlpull.v1.XmlSerializer; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; import java.util.logging.Logger; final public class ResourcesDecoder { @@ -25,10 +45,316 @@ final public class ResourcesDecoder { private final static Logger LOGGER = Logger.getLogger(ResourcesDecoder.class.getName()); private final Config mConfig; + private final ExtFile mApkFile; + private final ResTable mResTable; + private final ApkInfo mApkInfo; + private final Map mResFileMapping = new HashMap<>(); - public ResourcesDecoder(Config config) { + private final static String[] APK_RESOURCES_FILENAMES = new String[] { + "resources.arsc", "res", "r", "R" }; + private final static String[] APK_MANIFEST_FILENAMES = new String[] { + "AndroidManifest.xml" }; + private final static String[] IGNORED_PACKAGES = new String[] { + "android", "com.htc", "com.lge", "com.lge.internal", "yi", "flyme", "air.com.adobe.appentry", + "FFFFFFFFFFFFFFFFFFFFFF" }; + + + public ResourcesDecoder(Config config, ExtFile apkFile) { mConfig = config; + mApkFile = apkFile; + mApkInfo = new ApkInfo(); + mApkInfo.setApkFileName(apkFile.getName()); + mResTable = new ResTable(mConfig, mApkInfo); + } + + private boolean hasManifest() throws AndrolibException { + try { + return mApkFile.getDirectory().containsFile("AndroidManifest.xml"); + } catch (DirectoryException ex) { + throw new AndrolibException(ex); + } + } + + private boolean hasResources() throws AndrolibException { + try { + return mApkFile.getDirectory().containsFile("resources.arsc"); + } catch (DirectoryException ex) { + throw new AndrolibException(ex); + } + } + + public ResTable getResTable() throws AndrolibException { + if (! (hasManifest() || hasResources())) { + throw new AndrolibException( + "Apk doesn't contain either AndroidManifest.xml file or resources.arsc file"); + } + if (hasResources() && !mResTable.isMainPkgLoaded()) { + mResTable.loadMainPkg(mApkFile); + } + return mResTable; + } + + public ApkInfo getApkInfo() { + return mApkInfo; + } + + public Map getResFileMapping() { + return mResFileMapping; + } + + public void decodeManifest(File outDir) throws AndrolibException { + if (hasManifest()) { + if (mConfig.decodeResources == Config.DECODE_RESOURCES_FULL || + mConfig.forceDecodeManifest == Config.FORCE_DECODE_MANIFEST_FULL) { + if (hasResources()) { + decodeManifestWithResources(getResTable(), mApkFile, outDir); + if (!mConfig.analysisMode) { + // update apk info + mApkInfo.packageInfo.forcedPackageId = String.valueOf(mResTable.getPackageId()); + } + } else { + // if there's no resources.arsc, decode the manifest without looking + // up attribute references + decodeManifest(getResTable(), mApkFile, outDir); + } + } + else { + try { + LOGGER.info("Copying raw manifest..."); + mApkFile.getDirectory().copyToDir(outDir, APK_MANIFEST_FILENAMES); + } catch (DirectoryException ex) { + throw new AndrolibException(ex); + } + } + } + } + + private void decodeManifest(ResTable resTable, ExtFile apkFile, File outDir) + throws AndrolibException { + + Duo duo = getManifestFileDecoder(false); + ResFileDecoder fileDecoder = duo.m1; + + // Set ResAttrDecoder + duo.m2.setAttrDecoder(new ResAttrDecoder()); + ResAttrDecoder attrDecoder = duo.m2.getAttrDecoder(); + + // Fake ResPackage + attrDecoder.setCurrentPackage(new ResPackage(resTable, 0, null)); + + Directory inApk, out; + try { + inApk = apkFile.getDirectory(); + out = new FileDirectory(outDir); + + LOGGER.info("Decoding AndroidManifest.xml with only framework resources..."); + fileDecoder.decodeManifest(inApk, "AndroidManifest.xml", out, "AndroidManifest.xml"); + + } catch (DirectoryException ex) { + throw new AndrolibException(ex); + } + } + + private void decodeManifestWithResources(ResTable resTable, ExtFile apkFile, File outDir) + throws AndrolibException { + + Duo duo = getManifestFileDecoder(true); + ResFileDecoder fileDecoder = duo.m1; + ResAttrDecoder attrDecoder = duo.m2.getAttrDecoder(); + + attrDecoder.setCurrentPackage(resTable.listMainPackages().iterator().next()); + + Directory inApk, out; + try { + inApk = apkFile.getDirectory(); + out = new FileDirectory(outDir); + LOGGER.info("Decoding AndroidManifest.xml with resources..."); + + fileDecoder.decodeManifest(inApk, "AndroidManifest.xml", out, "AndroidManifest.xml"); + + // Remove versionName / versionCode (aapt API 16) + if (!mConfig.analysisMode) { + + // check for a mismatch between resources.arsc package and the package listed in AndroidManifest + // also remove the android::versionCode / versionName from manifest for rebuild + // this is a required change to prevent aapt warning about conflicting versions + // it will be passed as a parameter to aapt like "--min-sdk-version" via apktool.yml + adjustPackageManifest(resTable, outDir.getAbsolutePath() + File.separator + "AndroidManifest.xml"); + + ResXmlPatcher.removeManifestVersions(new File( + outDir.getAbsolutePath() + File.separator + "AndroidManifest.xml")); + } + } catch (DirectoryException ex) { + throw new AndrolibException(ex); + } } + private void adjustPackageManifest(ResTable resTable, String filePath) + throws AndrolibException { + + // compare resources.arsc package name to the one present in AndroidManifest + ResPackage resPackage = resTable.getCurrentResPackage(); + String pkgOriginal = resPackage.getName(); + String packageRenamed = resTable.getPackageRenamed(); + + resTable.setPackageId(resPackage.getId()); + resTable.setPackageOriginal(pkgOriginal); + + // 1) Check if pkgOriginal is null (empty resources.arsc) + // 2) Check if pkgOriginal === mPackageRenamed + // 3) Check if pkgOriginal is ignored via IGNORED_PACKAGES + if (pkgOriginal == null || pkgOriginal.equalsIgnoreCase(packageRenamed) + || (Arrays.asList(IGNORED_PACKAGES).contains(pkgOriginal))) { + LOGGER.info("Regular manifest package..."); + } else { + LOGGER.info("Renamed manifest package found! Replacing " + packageRenamed + " with " + pkgOriginal); + ResXmlPatcher.renameManifestPackage(new File(filePath), pkgOriginal); + } + } + + private Duo getManifestFileDecoder(boolean withResources) { + ResStreamDecoderContainer decoders = new ResStreamDecoderContainer(); + + AXmlResourceParser axmlParser = new AndroidManifestResourceParser(); + if (withResources) { + axmlParser.setAttrDecoder(new ResAttrDecoder()); + } + decoders.setDecoder("xml", new XmlPullStreamDecoder(axmlParser, getResXmlSerializer())); + + return new Duo<>(new ResFileDecoder(decoders), axmlParser); + } + + private ExtMXSerializer getResXmlSerializer() { + ExtMXSerializer serial = new ExtMXSerializer(); + serial.setProperty(ExtXmlSerializer.PROPERTY_SERIALIZER_INDENTATION, " "); + serial.setProperty(ExtXmlSerializer.PROPERTY_SERIALIZER_LINE_SEPARATOR, System.getProperty("line.separator")); + serial.setProperty(ExtXmlSerializer.PROPERTY_DEFAULT_ENCODING, "utf-8"); + serial.setDisabledAttrEscape(true); + return serial; + } + + public ResTable decodeResources(File outDir) throws AndrolibException { + if (hasResources()) { + switch (mConfig.decodeResources) { + case Config.DECODE_RESOURCES_NONE: + try { + LOGGER.info("Copying raw resources..."); + mApkFile.getDirectory().copyToDir(outDir, APK_RESOURCES_FILENAMES); + } catch (DirectoryException ex) { + throw new AndrolibException(ex); + } + break; + case Config.DECODE_RESOURCES_FULL: + decodeResources(getResTable(), mApkFile, outDir); + break; + } + mResTable.initApkInfo(mApkInfo, outDir); + if (mConfig.frameworkTag != null) { + mApkInfo.usesFramework.tag = mConfig.frameworkTag; + } + } + return mResTable; + } + + private void decodeResources(ResTable resTable, ExtFile apkFile, File outDir) + throws AndrolibException { + Duo duo = getResFileDecoder(); + ResFileDecoder fileDecoder = duo.m1; + ResAttrDecoder attrDecoder = duo.m2.getAttrDecoder(); + + attrDecoder.setCurrentPackage(resTable.listMainPackages().iterator().next()); + Directory in, out; + + try { + out = new FileDirectory(outDir); + in = apkFile.getDirectory(); + out = out.createDir("res"); + } catch (DirectoryException ex) { + throw new AndrolibException(ex); + } + + ExtMXSerializer xmlSerializer = getResXmlSerializer(); + for (ResPackage pkg : resTable.listMainPackages()) { + attrDecoder.setCurrentPackage(pkg); + + LOGGER.info("Decoding file-resources..."); + for (ResResource res : pkg.listFiles()) { + fileDecoder.decode(res, in, out, mResFileMapping); + } + + LOGGER.info("Decoding values */* XMLs..."); + for (ResValuesFile valuesFile : pkg.listValuesFiles()) { + generateValuesFile(valuesFile, out, xmlSerializer); + } + generatePublicXml(pkg, out, xmlSerializer); + } + + AndrolibException decodeError = duo.m2.getFirstError(); + if (decodeError != null) { + throw decodeError; + } + } + + private Duo getResFileDecoder() { + ResStreamDecoderContainer decoders = new ResStreamDecoderContainer(); + decoders.setDecoder("raw", new ResRawStreamDecoder()); + decoders.setDecoder("9patch", new Res9patchStreamDecoder()); + + AXmlResourceParser axmlParser = new AXmlResourceParser(); + axmlParser.setAttrDecoder(new ResAttrDecoder()); + decoders.setDecoder("xml", new XmlPullStreamDecoder(axmlParser, getResXmlSerializer())); + + return new Duo<>(new ResFileDecoder(decoders), axmlParser); + } + + private void generateValuesFile(ResValuesFile valuesFile, Directory out, + ExtXmlSerializer serial) throws AndrolibException { + try { + OutputStream outStream = out.getFileOutput(valuesFile.getPath()); + serial.setOutput((outStream), null); + serial.startDocument(null, null); + serial.startTag(null, "resources"); + + for (ResResource res : valuesFile.listResources()) { + if (valuesFile.isSynthesized(res)) { + continue; + } + ((ResValuesXmlSerializable) res.getValue()).serializeToResValuesXml(serial, res); + } + + serial.endTag(null, "resources"); + serial.newLine(); + serial.endDocument(); + serial.flush(); + outStream.close(); + } catch (IOException | DirectoryException ex) { + throw new AndrolibException("Could not generate: " + valuesFile.getPath(), ex); + } + } + + private void generatePublicXml(ResPackage pkg, Directory out, + XmlSerializer serial) throws AndrolibException { + try { + OutputStream outStream = out.getFileOutput("values/public.xml"); + serial.setOutput(outStream, null); + serial.startDocument(null, null); + serial.startTag(null, "resources"); + + for (ResResSpec spec : pkg.listResSpecs()) { + serial.startTag(null, "public"); + serial.attribute(null, "type", spec.getType().getName()); + serial.attribute(null, "name", spec.getName()); + serial.attribute(null, "id", String.format("0x%08x", spec.getId().id)); + serial.endTag(null, "public"); + } + + serial.endTag(null, "resources"); + serial.endDocument(); + serial.flush(); + outStream.close(); + } catch (IOException | DirectoryException ex) { + throw new AndrolibException("Could not generate public.xml file", ex); + } + } } diff --git a/brut.apktool/apktool-lib/src/test/java/brut/androlib/aapt2/NonStandardPkgIdTest.java b/brut.apktool/apktool-lib/src/test/java/brut/androlib/aapt2/NonStandardPkgIdTest.java index 2288a6a3fc..a43e408e4a 100644 --- a/brut.apktool/apktool-lib/src/test/java/brut/androlib/aapt2/NonStandardPkgIdTest.java +++ b/brut.apktool/apktool-lib/src/test/java/brut/androlib/aapt2/NonStandardPkgIdTest.java @@ -18,6 +18,7 @@ import brut.androlib.*; import brut.androlib.exceptions.AndrolibException; +import brut.androlib.res.ResourcesDecoder; import brut.androlib.res.data.ResTable; import brut.common.BrutException; import brut.directory.ExtFile; @@ -53,9 +54,12 @@ public static void beforeClass() throws Exception { new ApkBuilder(config, sTestOrigDir).build(testApk); LOGGER.info("Decoding pkgid8.apk..."); - ApkDecoder apkDecoder = new ApkDecoder(testApk); - apkDecoder.decode(sTestNewDir); - mResTable = apkDecoder.getResTable(); + ResourcesDecoder resourcesDecoder = new ResourcesDecoder( + Config.getDefaultConfig(), new ExtFile(testApk)); + + sTestNewDir.mkdirs(); + resourcesDecoder.decodeManifest(sTestNewDir); + mResTable = resourcesDecoder.decodeResources(sTestNewDir); } @AfterClass diff --git a/brut.apktool/apktool-lib/src/test/java/brut/androlib/decode/DecodeArrayTest.java b/brut.apktool/apktool-lib/src/test/java/brut/androlib/decode/DecodeArrayTest.java index e90f6296b3..9c0741ac77 100644 --- a/brut.apktool/apktool-lib/src/test/java/brut/androlib/decode/DecodeArrayTest.java +++ b/brut.apktool/apktool-lib/src/test/java/brut/androlib/decode/DecodeArrayTest.java @@ -18,7 +18,9 @@ import brut.androlib.ApkDecoder; import brut.androlib.BaseTest; +import brut.androlib.Config; import brut.androlib.TestUtils; +import brut.androlib.res.ResourcesDecoder; import brut.androlib.res.data.ResTable; import brut.androlib.res.data.value.ResArrayValue; import brut.androlib.res.data.value.ResValue; @@ -50,9 +52,12 @@ public static void afterClass() throws BrutException { @Test public void decodeStringArray() throws BrutException { String apk = "issue1994.apk"; - ApkDecoder apkDecoder = new ApkDecoder(new File(sTmpDir + File.separator + apk)); + //ApkDecoder apkDecoder = new ApkDecoder(new File(sTmpDir + File.separator + apk)); + ResourcesDecoder resourcesDecoder = new ResourcesDecoder( + Config.getDefaultConfig(), + new ExtFile(sTmpDir + File.separator + apk)); - ResTable resTable = apkDecoder.getResTable(); + ResTable resTable = resourcesDecoder.getResTable(); ResValue value = resTable.getResSpec(0x7f020001).getDefaultResource().getValue(); assertTrue("Not a ResArrayValue. Found: " + value.getClass(), value instanceof ResArrayValue); @@ -61,9 +66,11 @@ public void decodeStringArray() throws BrutException { @Test public void decodeArray() throws BrutException { String apk = "issue1994.apk"; - ApkDecoder apkDecoder = new ApkDecoder(new File(sTmpDir + File.separator + apk)); + ResourcesDecoder resourcesDecoder = new ResourcesDecoder( + Config.getDefaultConfig(), + new ExtFile(sTmpDir + File.separator + apk)); - ResTable resTable = apkDecoder.getResTable(); + ResTable resTable = resourcesDecoder.getResTable(); ResValue value = resTable.getResSpec(0x7f020000).getDefaultResource().getValue(); assertTrue("Not a ResArrayValue. Found: " + value.getClass(), value instanceof ResArrayValue); From 78d8d9060c36f7cdc61fc5cbceb2e090270de500 Mon Sep 17 00:00:00 2001 From: Slava Volkov Date: Mon, 3 Jul 2023 22:13:37 +0300 Subject: [PATCH 5/5] remove commented old code --- .../apktool-cli/src/main/java/brut/apktool/Main.java | 4 ---- .../apktool-lib/src/main/java/brut/androlib/ApkDecoder.java | 5 ----- .../apktool-lib/src/main/java/brut/androlib/apk/ApkInfo.java | 2 +- .../src/main/java/brut/androlib/res/ResourcesDecoder.java | 3 +-- 4 files changed, 2 insertions(+), 12 deletions(-) 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 4e400a1cd0..420929a8b9 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 @@ -204,10 +204,6 @@ private static void cmdDecode(CommandLine cli, Config config) throws AndrolibExc } catch (DirectoryException ex) { System.err.println("Could not modify internal dex files. Please ensure you have permission."); System.exit(1); - } finally { - try { - decoder.close(); - } catch (IOException ignored) {} } } diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkDecoder.java index 3cf2bd4795..f5a33b8b27 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkDecoder.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkDecoder.java @@ -62,7 +62,6 @@ public ApkDecoder(ExtFile apkFile) { public ApkDecoder(Config config, ExtFile apkFile) { mConfig = config; - //mAndRes = new AndrolibResources(config); mResUnknownFiles = new ResUnknownFiles(); mApkFile = apkFile; } @@ -178,10 +177,6 @@ private boolean hasMultipleSources() throws AndrolibException { } } - public void close() throws IOException { - //mAndRes.close(); - } - private void writeApkInfo(ApkInfo apkInfo, File outDir) throws AndrolibException { try { apkInfo.save(new File(outDir, "apktool.yml")); diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/ApkInfo.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/ApkInfo.java index 03c7c96cb0..d2abbac687 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/ApkInfo.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/ApkInfo.java @@ -38,7 +38,7 @@ public class ApkInfo { private String mApkFileName; public boolean isFrameworkApk; public UsesFramework usesFramework; - private Map mSdkInfo = new LinkedHashMap<>(); + private Map mSdkInfo = new LinkedHashMap<>(); public PackageInfo packageInfo = new PackageInfo(); public VersionInfo versionInfo = new VersionInfo(); public boolean resourcesAreCompressed; diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResourcesDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResourcesDecoder.java index 0b8247c05c..5cda16442f 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResourcesDecoder.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResourcesDecoder.java @@ -40,7 +40,7 @@ import java.util.Map; import java.util.logging.Logger; -final public class ResourcesDecoder { +public class ResourcesDecoder { private final static Logger LOGGER = Logger.getLogger(ResourcesDecoder.class.getName()); @@ -58,7 +58,6 @@ final public class ResourcesDecoder { "android", "com.htc", "com.lge", "com.lge.internal", "yi", "flyme", "air.com.adobe.appentry", "FFFFFFFFFFFFFFFFFFFFFF" }; - public ResourcesDecoder(Config config, ExtFile apkFile) { mConfig = config; mApkFile = apkFile;