mirror of
https://github.com/revanced/Apktool.git
synced 2025-01-07 10:35:52 +01:00
Extract into ApkInvoker and split into data classes. (#3124)
* extract AaptInvoker and rename MetaFile to ApkInfo, all decode methods from AndrolibResources moved to the ApkDecoder * extract ARSCData and FlagsOffset from ARSCDecoder and remove unused imports * rebase to master * move decodeManifest and decodeResources to the ResourceDecoder * remove commented old code
This commit is contained in:
parent
e69b1ed7c0
commit
a7e2e3e5e1
@ -21,7 +21,6 @@ import brut.androlib.exceptions.AndrolibException;
|
|||||||
import brut.androlib.exceptions.CantFindFrameworkResException;
|
import brut.androlib.exceptions.CantFindFrameworkResException;
|
||||||
import brut.androlib.exceptions.InFileNotFoundException;
|
import brut.androlib.exceptions.InFileNotFoundException;
|
||||||
import brut.androlib.exceptions.OutDirExistsException;
|
import brut.androlib.exceptions.OutDirExistsException;
|
||||||
import brut.androlib.res.AndrolibResources;
|
|
||||||
import brut.androlib.res.Framework;
|
import brut.androlib.res.Framework;
|
||||||
import brut.common.BrutException;
|
import brut.common.BrutException;
|
||||||
import brut.directory.DirectoryException;
|
import brut.directory.DirectoryException;
|
||||||
@ -205,10 +204,6 @@ public class Main {
|
|||||||
} catch (DirectoryException ex) {
|
} catch (DirectoryException ex) {
|
||||||
System.err.println("Could not modify internal dex files. Please ensure you have permission.");
|
System.err.println("Could not modify internal dex files. Please ensure you have permission.");
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
decoder.close();
|
|
||||||
} catch (IOException ignored) {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,390 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 Ryszard Wiśniewski <brut.alll@gmail.com>
|
||||||
|
* Copyright (C) 2010 Connor Tumbleson <connor.tumbleson@gmail.com>
|
||||||
|
*
|
||||||
|
* 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;
|
||||||
|
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<String> cmd, boolean customAapt)
|
||||||
|
throws AndrolibException {
|
||||||
|
|
||||||
|
List<String> 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<String> 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<String> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -17,9 +17,8 @@
|
|||||||
package brut.androlib;
|
package brut.androlib;
|
||||||
|
|
||||||
import brut.androlib.exceptions.AndrolibException;
|
import brut.androlib.exceptions.AndrolibException;
|
||||||
import brut.androlib.meta.MetaInfo;
|
import brut.androlib.apk.ApkInfo;
|
||||||
import brut.androlib.meta.UsesFramework;
|
import brut.androlib.apk.UsesFramework;
|
||||||
import brut.androlib.res.AndrolibResources;
|
|
||||||
import brut.androlib.res.Framework;
|
import brut.androlib.res.Framework;
|
||||||
import brut.androlib.res.data.ResConfigFlags;
|
import brut.androlib.res.data.ResConfigFlags;
|
||||||
import brut.androlib.res.xml.ResXmlPatcher;
|
import brut.androlib.res.xml.ResXmlPatcher;
|
||||||
@ -31,16 +30,16 @@ import brut.common.TraversalUnknownFileException;
|
|||||||
import brut.directory.Directory;
|
import brut.directory.Directory;
|
||||||
import brut.directory.DirectoryException;
|
import brut.directory.DirectoryException;
|
||||||
import brut.directory.ExtFile;
|
import brut.directory.ExtFile;
|
||||||
|
import brut.directory.ZipUtils;
|
||||||
import brut.util.BrutIO;
|
import brut.util.BrutIO;
|
||||||
import brut.util.OS;
|
import brut.util.OS;
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.xml.sax.SAXException;
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
import javax.xml.transform.TransformerException;
|
import javax.xml.transform.TransformerException;
|
||||||
import java.io.BufferedInputStream;
|
import java.io.*;
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
@ -52,8 +51,8 @@ import java.util.zip.ZipOutputStream;
|
|||||||
public class ApkBuilder {
|
public class ApkBuilder {
|
||||||
private final static Logger LOGGER = Logger.getLogger(ApkBuilder.class.getName());
|
private final static Logger LOGGER = Logger.getLogger(ApkBuilder.class.getName());
|
||||||
|
|
||||||
private final AndrolibResources mAndRes;
|
private final Config mConfig;
|
||||||
private final Config config;
|
private ApkInfo mApkInfo;
|
||||||
private int mMinSdkVersion = 0;
|
private int mMinSdkVersion = 0;
|
||||||
private final ExtFile mApkDir;
|
private final ExtFile mApkDir;
|
||||||
|
|
||||||
@ -73,34 +72,22 @@ public class ApkBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ApkBuilder(Config config, ExtFile apkDir) {
|
public ApkBuilder(Config config, ExtFile apkDir) {
|
||||||
this.config = config;
|
mConfig = config;
|
||||||
mAndRes = new AndrolibResources(config);
|
|
||||||
mApkDir = apkDir;
|
mApkDir = apkDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void build(File outFile)
|
public void build(File outFile) throws BrutException {
|
||||||
throws BrutException {
|
|
||||||
LOGGER.info("Using Apktool " + ApktoolProperties.getVersion());
|
LOGGER.info("Using Apktool " + ApktoolProperties.getVersion());
|
||||||
|
|
||||||
MetaInfo meta = MetaInfo.readMetaFile(mApkDir);
|
mApkInfo = ApkInfo.load(mApkDir);
|
||||||
config.isFramework = meta.isFrameworkApk;
|
|
||||||
config.resourcesAreCompressed = meta.compressionType;
|
|
||||||
config.doNotCompress = meta.doNotCompress;
|
|
||||||
|
|
||||||
mAndRes.setSdkInfo(meta.sdkInfo);
|
if (mApkInfo.getSdkInfo() != null && mApkInfo.getSdkInfo().get("minSdkVersion") != null) {
|
||||||
mAndRes.setPackageId(meta.packageInfo);
|
String minSdkVersion = mApkInfo.getSdkInfo().get("minSdkVersion");
|
||||||
mAndRes.setPackageRenamed(meta.packageInfo);
|
mMinSdkVersion = mApkInfo.getMinSdkVersionFromAndroidCodename(minSdkVersion);
|
||||||
mAndRes.setVersionInfo(meta.versionInfo);
|
|
||||||
mAndRes.setSharedLibrary(meta.sharedLibrary);
|
|
||||||
mAndRes.setSparseResources(meta.sparseResources);
|
|
||||||
|
|
||||||
if (meta.sdkInfo != null && meta.sdkInfo.get("minSdkVersion") != null) {
|
|
||||||
String minSdkVersion = meta.sdkInfo.get("minSdkVersion");
|
|
||||||
mMinSdkVersion = mAndRes.getMinSdkVersionFromAndroidCodename(meta, minSdkVersion);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (outFile == null) {
|
if (outFile == null) {
|
||||||
String outFileName = meta.apkFileName;
|
String outFileName = mApkInfo.getApkFileName();
|
||||||
outFile = new File(mApkDir, "dist" + File.separator + (outFileName == null ? "out.apk" : outFileName));
|
outFile = new File(mApkDir, "dist" + File.separator + (outFileName == null ? "out.apk" : outFileName));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,14 +99,14 @@ public class ApkBuilder {
|
|||||||
buildSources(mApkDir);
|
buildSources(mApkDir);
|
||||||
buildNonDefaultSources(mApkDir);
|
buildNonDefaultSources(mApkDir);
|
||||||
buildManifestFile(mApkDir, manifest, manifestOriginal);
|
buildManifestFile(mApkDir, manifest, manifestOriginal);
|
||||||
buildResources(mApkDir, meta.usesFramework);
|
buildResources(mApkDir, mApkInfo.usesFramework);
|
||||||
buildLibs(mApkDir);
|
buildLibs(mApkDir);
|
||||||
buildCopyOriginalFiles(mApkDir);
|
buildCopyOriginalFiles(mApkDir);
|
||||||
buildApk(mApkDir, outFile);
|
buildApk(mApkDir, outFile);
|
||||||
|
|
||||||
// we must go after the Apk is built, and copy the files in via Zip
|
// 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)
|
// 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
|
// we copied the AndroidManifest.xml to AndroidManifest.xml.orig so we can edit it
|
||||||
// lets restore the unedited one, to not change the original
|
// lets restore the unedited one, to not change the original
|
||||||
@ -156,14 +143,14 @@ public class ApkBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void buildSources(File appDir)
|
private void buildSources(File appDir)
|
||||||
throws AndrolibException {
|
throws AndrolibException {
|
||||||
if (!buildSourcesRaw(appDir, "classes.dex") && !buildSourcesSmali(appDir, "smali", "classes.dex")) {
|
if (!buildSourcesRaw(appDir, "classes.dex") && !buildSourcesSmali(appDir, "smali", "classes.dex")) {
|
||||||
LOGGER.warning("Could not find sources");
|
LOGGER.warning("Could not find sources");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void buildNonDefaultSources(ExtFile appDir)
|
private void buildNonDefaultSources(ExtFile appDir)
|
||||||
throws AndrolibException {
|
throws AndrolibException {
|
||||||
try {
|
try {
|
||||||
// loop through any smali_ directories for multi-dex apks
|
// loop through any smali_ directories for multi-dex apks
|
||||||
@ -195,14 +182,14 @@ public class ApkBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean buildSourcesRaw(File appDir, String filename)
|
private boolean buildSourcesRaw(File appDir, String filename)
|
||||||
throws AndrolibException {
|
throws AndrolibException {
|
||||||
File working = new File(appDir, filename);
|
File working = new File(appDir, filename);
|
||||||
if (!working.exists()) {
|
if (!working.exists()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
File stored = new File(appDir, APK_DIRNAME + "/" + filename);
|
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...");
|
LOGGER.info("Copying " + appDir.toString() + " " + filename + " file...");
|
||||||
try {
|
try {
|
||||||
BrutIO.copyAndClose(Files.newInputStream(working.toPath()), Files.newOutputStream(stored.toPath()));
|
BrutIO.copyAndClose(Files.newInputStream(working.toPath()), Files.newOutputStream(stored.toPath()));
|
||||||
@ -214,26 +201,26 @@ public class ApkBuilder {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean buildSourcesSmali(File appDir, String folder, String filename)
|
private boolean buildSourcesSmali(File appDir, String folder, String filename)
|
||||||
throws AndrolibException {
|
throws AndrolibException {
|
||||||
ExtFile smaliDir = new ExtFile(appDir, folder);
|
ExtFile smaliDir = new ExtFile(appDir, folder);
|
||||||
if (!smaliDir.exists()) {
|
if (!smaliDir.exists()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
File dex = new File(appDir, APK_DIRNAME + "/" + filename);
|
File dex = new File(appDir, APK_DIRNAME + "/" + filename);
|
||||||
if (! config.forceBuildAll) {
|
if (! mConfig.forceBuildAll) {
|
||||||
LOGGER.info("Checking whether sources has changed...");
|
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 + "...");
|
LOGGER.info("Smaling " + folder + " folder into " + filename + "...");
|
||||||
//noinspection ResultOfMethodCallIgnored
|
//noinspection ResultOfMethodCallIgnored
|
||||||
dex.delete();
|
dex.delete();
|
||||||
SmaliBuilder.build(smaliDir, dex, config.forceApi > 0 ? config.forceApi : mMinSdkVersion);
|
SmaliBuilder.build(smaliDir, dex, mConfig.forceApi > 0 ? mConfig.forceApi : mMinSdkVersion);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void buildResources(ExtFile appDir, UsesFramework usesFramework)
|
private void buildResources(ExtFile appDir, UsesFramework usesFramework)
|
||||||
throws BrutException {
|
throws BrutException {
|
||||||
if (!buildResourcesRaw(appDir) && !buildResourcesFull(appDir, usesFramework)
|
if (!buildResourcesRaw(appDir) && !buildResourcesFull(appDir, usesFramework)
|
||||||
&& !buildManifest(appDir, usesFramework)) {
|
&& !buildManifest(appDir, usesFramework)) {
|
||||||
@ -241,17 +228,17 @@ public class ApkBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean buildResourcesRaw(ExtFile appDir)
|
private boolean buildResourcesRaw(ExtFile appDir)
|
||||||
throws AndrolibException {
|
throws AndrolibException {
|
||||||
try {
|
try {
|
||||||
if (!new File(appDir, "resources.arsc").exists()) {
|
if (!new File(appDir, "resources.arsc").exists()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
File apkDir = new File(appDir, APK_DIRNAME);
|
File apkDir = new File(appDir, APK_DIRNAME);
|
||||||
if (! config.forceBuildAll) {
|
if (! mConfig.forceBuildAll) {
|
||||||
LOGGER.info("Checking whether resources has changed...");
|
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))) {
|
newFiles(APK_RESOURCES_FILENAMES, apkDir))) {
|
||||||
LOGGER.info("Copying raw resources...");
|
LOGGER.info("Copying raw resources...");
|
||||||
appDir.getDirectory().copyToDir(apkDir, APK_RESOURCES_FILENAMES);
|
appDir.getDirectory().copyToDir(apkDir, APK_RESOURCES_FILENAMES);
|
||||||
@ -262,24 +249,24 @@ public class ApkBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean buildResourcesFull(File appDir, UsesFramework usesFramework)
|
private boolean buildResourcesFull(File appDir, UsesFramework usesFramework)
|
||||||
throws AndrolibException {
|
throws AndrolibException {
|
||||||
try {
|
try {
|
||||||
if (!new File(appDir, "res").exists()) {
|
if (!new File(appDir, "res").exists()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (! config.forceBuildAll) {
|
if (! mConfig.forceBuildAll) {
|
||||||
LOGGER.info("Checking whether resources has changed...");
|
LOGGER.info("Checking whether resources has changed...");
|
||||||
}
|
}
|
||||||
File apkDir = new File(appDir, APK_DIRNAME);
|
File apkDir = new File(appDir, APK_DIRNAME);
|
||||||
File resourceFile = new File(apkDir.getParent(), "resources.zip");
|
File resourceFile = new File(apkDir.getParent(), "resources.zip");
|
||||||
|
|
||||||
if (config.forceBuildAll || isModified(newFiles(APP_RESOURCES_FILENAMES, appDir),
|
if (mConfig.forceBuildAll || isModified(newFiles(APP_RESOURCES_FILENAMES, appDir),
|
||||||
newFiles(APK_RESOURCES_FILENAMES, apkDir)) || (config.isAapt2() && !isFile(resourceFile))) {
|
newFiles(APK_RESOURCES_FILENAMES, apkDir)) || (mConfig.isAapt2() && !isFile(resourceFile))) {
|
||||||
LOGGER.info("Building resources...");
|
LOGGER.info("Building resources...");
|
||||||
|
|
||||||
if (config.debugMode) {
|
if (mConfig.debugMode) {
|
||||||
if (config.isAapt2()) {
|
if (mConfig.isAapt2()) {
|
||||||
LOGGER.info("Using aapt2 - setting 'debuggable' attribute to 'true' in AndroidManifest.xml");
|
LOGGER.info("Using aapt2 - setting 'debuggable' attribute to 'true' in AndroidManifest.xml");
|
||||||
ResXmlPatcher.setApplicationDebugTagTrue(new File(appDir, "AndroidManifest.xml"));
|
ResXmlPatcher.setApplicationDebugTagTrue(new File(appDir, "AndroidManifest.xml"));
|
||||||
} else {
|
} else {
|
||||||
@ -287,10 +274,10 @@ public class ApkBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.netSecConf) {
|
if (mConfig.netSecConf) {
|
||||||
MetaInfo meta = MetaInfo.readMetaFile(new ExtFile(appDir));
|
ApkInfo meta = ApkInfo.load(new ExtFile(appDir));
|
||||||
if (meta.sdkInfo != null && meta.sdkInfo.get("targetSdkVersion") != null) {
|
if (meta.getSdkInfo() != null && meta.getSdkInfo().get("targetSdkVersion") != null) {
|
||||||
if (Integer.parseInt(meta.sdkInfo.get("targetSdkVersion")) < ResConfigFlags.SDK_NOUGAT) {
|
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!");
|
LOGGER.warning("Target SDK version is lower than 24! Network Security Configuration might be ignored!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -315,7 +302,8 @@ public class ApkBuilder {
|
|||||||
if (!ninePatch.exists()) {
|
if (!ninePatch.exists()) {
|
||||||
ninePatch = null;
|
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"),
|
"AndroidManifest.xml"), new File(appDir, "res"),
|
||||||
ninePatch, null, parseUsesFramework(usesFramework));
|
ninePatch, null, parseUsesFramework(usesFramework));
|
||||||
|
|
||||||
@ -345,7 +333,7 @@ public class ApkBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean buildManifestRaw(ExtFile appDir)
|
private boolean buildManifestRaw(ExtFile appDir)
|
||||||
throws AndrolibException {
|
throws AndrolibException {
|
||||||
try {
|
try {
|
||||||
File apkDir = new File(appDir, APK_DIRNAME);
|
File apkDir = new File(appDir, APK_DIRNAME);
|
||||||
@ -357,19 +345,19 @@ public class ApkBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean buildManifest(ExtFile appDir, UsesFramework usesFramework)
|
private boolean buildManifest(ExtFile appDir, UsesFramework usesFramework)
|
||||||
throws BrutException {
|
throws BrutException {
|
||||||
try {
|
try {
|
||||||
if (!new File(appDir, "AndroidManifest.xml").exists()) {
|
if (!new File(appDir, "AndroidManifest.xml").exists()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (! config.forceBuildAll) {
|
if (! mConfig.forceBuildAll) {
|
||||||
LOGGER.info("Checking whether resources has changed...");
|
LOGGER.info("Checking whether resources has changed...");
|
||||||
}
|
}
|
||||||
|
|
||||||
File apkDir = new File(appDir, APK_DIRNAME);
|
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))) {
|
newFiles(APK_MANIFEST_FILENAMES, apkDir))) {
|
||||||
LOGGER.info("Building AndroidManifest.xml...");
|
LOGGER.info("Building AndroidManifest.xml...");
|
||||||
|
|
||||||
@ -382,7 +370,8 @@ public class ApkBuilder {
|
|||||||
ninePatch = null;
|
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,
|
"AndroidManifest.xml"), null, ninePatch, null,
|
||||||
parseUsesFramework(usesFramework));
|
parseUsesFramework(usesFramework));
|
||||||
|
|
||||||
@ -401,14 +390,14 @@ public class ApkBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void buildLibs(File appDir) throws AndrolibException {
|
private void buildLibs(File appDir) throws AndrolibException {
|
||||||
buildLibrary(appDir, "lib");
|
buildLibrary(appDir, "lib");
|
||||||
buildLibrary(appDir, "libs");
|
buildLibrary(appDir, "libs");
|
||||||
buildLibrary(appDir, "kotlin");
|
buildLibrary(appDir, "kotlin");
|
||||||
buildLibrary(appDir, "META-INF/services");
|
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);
|
File working = new File(appDir, folder);
|
||||||
|
|
||||||
if (! working.exists()) {
|
if (! working.exists()) {
|
||||||
@ -416,7 +405,7 @@ public class ApkBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
File stored = new File(appDir, APK_DIRNAME + "/" + folder);
|
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 + ")");
|
LOGGER.info("Copying libs... (/" + folder + ")");
|
||||||
try {
|
try {
|
||||||
OS.rmdir(stored);
|
OS.rmdir(stored);
|
||||||
@ -427,9 +416,9 @@ public class ApkBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void buildCopyOriginalFiles(File appDir)
|
private void buildCopyOriginalFiles(File appDir)
|
||||||
throws AndrolibException {
|
throws AndrolibException {
|
||||||
if (config.copyOriginalFiles) {
|
if (mConfig.copyOriginalFiles) {
|
||||||
File originalDir = new File(appDir, "original");
|
File originalDir = new File(appDir, "original");
|
||||||
if (originalDir.exists()) {
|
if (originalDir.exists()) {
|
||||||
try {
|
try {
|
||||||
@ -454,7 +443,7 @@ public class ApkBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void buildUnknownFiles(File appDir, File outFile, MetaInfo meta)
|
private void buildUnknownFiles(File appDir, File outFile, ApkInfo meta)
|
||||||
throws AndrolibException {
|
throws AndrolibException {
|
||||||
if (meta.unknownFiles != null) {
|
if (meta.unknownFiles != null) {
|
||||||
LOGGER.info("Copying unknown files/dir...");
|
LOGGER.info("Copying unknown files/dir...");
|
||||||
@ -541,7 +530,7 @@ public class ApkBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void buildApk(File appDir, File outApk) throws AndrolibException {
|
private void buildApk(File appDir, File outApk) throws AndrolibException {
|
||||||
LOGGER.info("Building apk file...");
|
LOGGER.info("Building apk file...");
|
||||||
if (outApk.exists()) {
|
if (outApk.exists()) {
|
||||||
//noinspection ResultOfMethodCallIgnored
|
//noinspection ResultOfMethodCallIgnored
|
||||||
@ -557,7 +546,17 @@ public class ApkBuilder {
|
|||||||
if (!assetDir.exists()) {
|
if (!assetDir.exists()) {
|
||||||
assetDir = null;
|
assetDir = null;
|
||||||
}
|
}
|
||||||
mAndRes.zipPackage(outApk, new File(appDir, APK_DIRNAME), assetDir);
|
zipPackage(outApk, new File(appDir, APK_DIRNAME), assetDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void zipPackage(File apkFile, File rawDir, File assetDir)
|
||||||
|
throws AndrolibException {
|
||||||
|
|
||||||
|
try {
|
||||||
|
ZipUtils.zipFolders(rawDir, apkFile, assetDir, mApkInfo.doNotCompress);
|
||||||
|
} catch (IOException | BrutException ex) {
|
||||||
|
throw new AndrolibException(ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private File[] parseUsesFramework(UsesFramework usesFramework)
|
private File[] parseUsesFramework(UsesFramework usesFramework)
|
||||||
@ -571,7 +570,7 @@ public class ApkBuilder {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Framework framework = new Framework(config);
|
Framework framework = new Framework(mConfig);
|
||||||
String tag = usesFramework.tag;
|
String tag = usesFramework.tag;
|
||||||
File[] files = new File[ids.size()];
|
File[] files = new File[ids.size()];
|
||||||
int i = 0;
|
int i = 0;
|
||||||
@ -606,7 +605,23 @@ public class ApkBuilder {
|
|||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void close() throws IOException {
|
public boolean detectWhetherAppIsFramework(File appDir)
|
||||||
mAndRes.close();
|
throws AndrolibException {
|
||||||
|
File publicXml = new File(appDir, "res/values/public.xml");
|
||||||
|
if (! publicXml.exists()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator<String> 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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,10 +19,9 @@ package brut.androlib;
|
|||||||
import brut.androlib.exceptions.AndrolibException;
|
import brut.androlib.exceptions.AndrolibException;
|
||||||
import brut.androlib.exceptions.InFileNotFoundException;
|
import brut.androlib.exceptions.InFileNotFoundException;
|
||||||
import brut.androlib.exceptions.OutDirExistsException;
|
import brut.androlib.exceptions.OutDirExistsException;
|
||||||
import brut.androlib.meta.MetaInfo;
|
import brut.androlib.apk.ApkInfo;
|
||||||
import brut.androlib.res.AndrolibResources;
|
import brut.androlib.res.ResourcesDecoder;
|
||||||
import brut.androlib.res.data.ResTable;
|
import brut.androlib.res.data.*;
|
||||||
import brut.androlib.res.data.ResUnknownFiles;
|
|
||||||
import brut.androlib.src.SmaliDecoder;
|
import brut.androlib.src.SmaliDecoder;
|
||||||
import brut.directory.Directory;
|
import brut.directory.Directory;
|
||||||
import brut.directory.ExtFile;
|
import brut.directory.ExtFile;
|
||||||
@ -42,20 +41,14 @@ public class ApkDecoder {
|
|||||||
|
|
||||||
private final static Logger LOGGER = Logger.getLogger(ApkDecoder.class.getName());
|
private final static Logger LOGGER = Logger.getLogger(ApkDecoder.class.getName());
|
||||||
|
|
||||||
private final Config config;
|
private final Config mConfig;
|
||||||
private final AndrolibResources mAndRes;
|
|
||||||
private final ExtFile mApkFile;
|
private final ExtFile mApkFile;
|
||||||
private ResTable mResTable;
|
|
||||||
protected final ResUnknownFiles mResUnknownFiles;
|
protected final ResUnknownFiles mResUnknownFiles;
|
||||||
private Collection<String> mUncompressedFiles;
|
|
||||||
private int mMinSdkVersion = 0;
|
private int mMinSdkVersion = 0;
|
||||||
|
|
||||||
|
|
||||||
private final static String SMALI_DIRNAME = "smali";
|
private final static String SMALI_DIRNAME = "smali";
|
||||||
private final static String UNK_DIRNAME = "unknown";
|
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[] {
|
private final static String[] APK_STANDARD_ALL_FILENAMES = new String[] {
|
||||||
"classes.dex", "AndroidManifest.xml", "resources.arsc", "res", "r", "R",
|
"classes.dex", "AndroidManifest.xml", "resources.arsc", "res", "r", "R",
|
||||||
"lib", "libs", "assets", "META-INF", "kotlin" };
|
"lib", "libs", "assets", "META-INF", "kotlin" };
|
||||||
@ -68,8 +61,7 @@ public class ApkDecoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public ApkDecoder(Config config, ExtFile apkFile) {
|
public ApkDecoder(Config config, ExtFile apkFile) {
|
||||||
this.config = config;
|
mConfig = config;
|
||||||
mAndRes = new AndrolibResources(config);
|
|
||||||
mResUnknownFiles = new ResUnknownFiles();
|
mResUnknownFiles = new ResUnknownFiles();
|
||||||
mApkFile = apkFile;
|
mApkFile = apkFile;
|
||||||
}
|
}
|
||||||
@ -84,7 +76,7 @@ public class ApkDecoder {
|
|||||||
|
|
||||||
public void decode(File outDir) throws AndrolibException, IOException, DirectoryException {
|
public void decode(File outDir) throws AndrolibException, IOException, DirectoryException {
|
||||||
try {
|
try {
|
||||||
if (!config.forceDelete && outDir.exists()) {
|
if (!mConfig.forceDelete && outDir.exists()) {
|
||||||
throw new OutDirExistsException();
|
throw new OutDirExistsException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,11 +93,12 @@ public class ApkDecoder {
|
|||||||
|
|
||||||
LOGGER.info("Using Apktool " + ApktoolProperties.getVersion() + " on " + mApkFile.getName());
|
LOGGER.info("Using Apktool " + ApktoolProperties.getVersion() + " on " + mApkFile.getName());
|
||||||
|
|
||||||
decodeManifest(outDir);
|
ResourcesDecoder resourcesDecoder = new ResourcesDecoder(mConfig, mApkFile);
|
||||||
decodeResources(outDir);
|
resourcesDecoder.decodeManifest(outDir);
|
||||||
|
resourcesDecoder.decodeResources(outDir);
|
||||||
|
|
||||||
if (hasSources()) {
|
if (hasSources()) {
|
||||||
switch (config.decodeSources) {
|
switch (mConfig.decodeSources) {
|
||||||
case Config.DECODE_SOURCES_NONE:
|
case Config.DECODE_SOURCES_NONE:
|
||||||
copySourcesRaw(outDir, "classes.dex");
|
copySourcesRaw(outDir, "classes.dex");
|
||||||
break;
|
break;
|
||||||
@ -122,7 +115,7 @@ public class ApkDecoder {
|
|||||||
for (String file : files) {
|
for (String file : files) {
|
||||||
if (file.endsWith(".dex")) {
|
if (file.endsWith(".dex")) {
|
||||||
if (! file.equalsIgnoreCase("classes.dex")) {
|
if (! file.equalsIgnoreCase("classes.dex")) {
|
||||||
switch(config.decodeSources) {
|
switch(mConfig.decodeSources) {
|
||||||
case Config.DECODE_SOURCES_NONE:
|
case Config.DECODE_SOURCES_NONE:
|
||||||
copySourcesRaw(outDir, file);
|
copySourcesRaw(outDir, file);
|
||||||
break;
|
break;
|
||||||
@ -141,13 +134,17 @@ public class ApkDecoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ApkInfo apkInfo = resourcesDecoder.getApkInfo();
|
||||||
|
if (mMinSdkVersion > 0) {
|
||||||
|
apkInfo.setSdkInfo(getMinSdkInfo());
|
||||||
|
}
|
||||||
|
|
||||||
copyRawFiles(outDir);
|
copyRawFiles(outDir);
|
||||||
copyUnknownFiles(outDir);
|
copyUnknownFiles(apkInfo, outDir);
|
||||||
mUncompressedFiles = new ArrayList<>();
|
Collection<String> mUncompressedFiles = new ArrayList<>();
|
||||||
recordUncompressedFiles(mUncompressedFiles);
|
recordUncompressedFiles(apkInfo, resourcesDecoder.getResFileMapping(), mUncompressedFiles);
|
||||||
copyOriginalFiles(outDir);
|
copyOriginalFiles(outDir);
|
||||||
writeMetaFile(outDir);
|
writeApkInfo(apkInfo, outDir);
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
mApkFile.close();
|
mApkFile.close();
|
||||||
@ -155,21 +152,7 @@ public class ApkDecoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResTable getResTable() throws AndrolibException {
|
private boolean hasSources() 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);
|
|
||||||
}
|
|
||||||
return mResTable;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasSources() throws AndrolibException {
|
|
||||||
try {
|
try {
|
||||||
return mApkFile.getDirectory().containsFile("classes.dex");
|
return mApkFile.getDirectory().containsFile("classes.dex");
|
||||||
} catch (DirectoryException ex) {
|
} catch (DirectoryException ex) {
|
||||||
@ -177,7 +160,7 @@ public class ApkDecoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasMultipleSources() throws AndrolibException {
|
private boolean hasMultipleSources() throws AndrolibException {
|
||||||
try {
|
try {
|
||||||
Set<String> files = mApkFile.getDirectory().getFiles(false);
|
Set<String> files = mApkFile.getDirectory().getFiles(false);
|
||||||
for (String file : files) {
|
for (String file : files) {
|
||||||
@ -194,48 +177,9 @@ public class ApkDecoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasManifest() throws AndrolibException {
|
private void writeApkInfo(ApkInfo apkInfo, File outDir) throws AndrolibException {
|
||||||
try {
|
try {
|
||||||
return mApkFile.getDirectory().containsFile("AndroidManifest.xml");
|
apkInfo.save(new File(outDir, "apktool.yml"));
|
||||||
} catch (DirectoryException ex) {
|
|
||||||
throw new AndrolibException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public 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 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
meta.save(new File(outDir, "apktool.yml"));
|
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
throw new AndrolibException(ex);
|
throw new AndrolibException(ex);
|
||||||
}
|
}
|
||||||
@ -271,7 +215,7 @@ public class ApkDecoder {
|
|||||||
smaliDir.mkdirs();
|
smaliDir.mkdirs();
|
||||||
LOGGER.info("Baksmaling " + filename + "...");
|
LOGGER.info("Baksmaling " + filename + "...");
|
||||||
DexFile dexFile = SmaliDecoder.decode(mApkFile, smaliDir, filename,
|
DexFile dexFile = SmaliDecoder.decode(mApkFile, smaliDir, filename,
|
||||||
config.baksmaliDebugMode, config.apiLevel);
|
mConfig.baksmaliDebugMode, mConfig.apiLevel);
|
||||||
int minSdkVersion = dexFile.getOpcodes().api;
|
int minSdkVersion = dexFile.getOpcodes().api;
|
||||||
if (mMinSdkVersion == 0 || mMinSdkVersion > minSdkVersion) {
|
if (mMinSdkVersion == 0 || mMinSdkVersion > minSdkVersion) {
|
||||||
mMinSdkVersion = minSdkVersion;
|
mMinSdkVersion = minSdkVersion;
|
||||||
@ -281,54 +225,13 @@ public class ApkDecoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void decodeManifest(File outDir) throws AndrolibException {
|
|
||||||
if (hasManifest()) {
|
|
||||||
if (config.decodeResources == Config.DECODE_RESOURCES_FULL ||
|
|
||||||
config.forceDecodeManifest == Config.FORCE_DECODE_MANIFEST_FULL) {
|
|
||||||
if (hasResources()) {
|
|
||||||
mAndRes.decodeManifestWithResources(getResTable(), mApkFile, outDir);
|
|
||||||
} else {
|
|
||||||
// if there's no resources.arsc, decode the manifest without looking
|
|
||||||
// up attribute references
|
|
||||||
mAndRes.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 decodeResources(File outDir) throws AndrolibException {
|
|
||||||
if (hasResources()) {
|
|
||||||
switch (config.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:
|
|
||||||
mAndRes.decode(getResTable(), mApkFile, outDir);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void copyRawFiles(File outDir)
|
private void copyRawFiles(File outDir)
|
||||||
throws AndrolibException {
|
throws AndrolibException {
|
||||||
LOGGER.info("Copying assets and libs...");
|
LOGGER.info("Copying assets and libs...");
|
||||||
try {
|
try {
|
||||||
Directory in = mApkFile.getDirectory();
|
Directory in = mApkFile.getDirectory();
|
||||||
|
|
||||||
if (config.decodeAssets == Config.DECODE_ASSETS_FULL) {
|
if (mConfig.decodeAssets == Config.DECODE_ASSETS_FULL) {
|
||||||
if (in.containsDir("assets")) {
|
if (in.containsDir("assets")) {
|
||||||
in.copyToDir(outDir, "assets");
|
in.copyToDir(outDir, "assets");
|
||||||
}
|
}
|
||||||
@ -356,7 +259,7 @@ public class ApkDecoder {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void copyUnknownFiles(File outDir)
|
private void copyUnknownFiles(ApkInfo apkInfo, File outDir)
|
||||||
throws AndrolibException {
|
throws AndrolibException {
|
||||||
LOGGER.info("Copying unknown files...");
|
LOGGER.info("Copying unknown files...");
|
||||||
File unknownOut = new File(outDir, UNK_DIRNAME);
|
File unknownOut = new File(outDir, UNK_DIRNAME);
|
||||||
@ -375,6 +278,8 @@ public class ApkDecoder {
|
|||||||
mResUnknownFiles.addUnknownFileInfo(file, String.valueOf(unk.getCompressionLevel(file)));
|
mResUnknownFiles.addUnknownFileInfo(file, String.valueOf(unk.getCompressionLevel(file)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// update apk info
|
||||||
|
apkInfo.unknownFiles = mResUnknownFiles.getUnknownFiles();
|
||||||
} catch (DirectoryException ex) {
|
} catch (DirectoryException ex) {
|
||||||
throw new AndrolibException(ex);
|
throw new AndrolibException(ex);
|
||||||
}
|
}
|
||||||
@ -413,7 +318,10 @@ public class ApkDecoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void recordUncompressedFiles(Collection<String> uncompressedFilesOrExts) throws AndrolibException {
|
private void recordUncompressedFiles(ApkInfo apkInfo,
|
||||||
|
Map<String, String> resFileMapping,
|
||||||
|
Collection<String> uncompressedFilesOrExts)
|
||||||
|
throws AndrolibException {
|
||||||
try {
|
try {
|
||||||
Directory unk = mApkFile.getDirectory();
|
Directory unk = mApkFile.getDirectory();
|
||||||
Set<String> files = unk.getFiles(true);
|
Set<String> files = unk.getFiles(true);
|
||||||
@ -427,8 +335,8 @@ public class ApkDecoder {
|
|||||||
|
|
||||||
if (extOrFile.isEmpty() || !NO_COMPRESS_PATTERN.matcher(extOrFile).find()) {
|
if (extOrFile.isEmpty() || !NO_COMPRESS_PATTERN.matcher(extOrFile).find()) {
|
||||||
extOrFile = file;
|
extOrFile = file;
|
||||||
if (mAndRes.mResFileMapping.containsKey(extOrFile)) {
|
if (resFileMapping.containsKey(extOrFile)) {
|
||||||
extOrFile = mAndRes.mResFileMapping.get(extOrFile);
|
extOrFile = resFileMapping.get(extOrFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!uncompressedFilesOrExts.contains(extOrFile)) {
|
if (!uncompressedFilesOrExts.contains(extOrFile)) {
|
||||||
@ -436,6 +344,11 @@ public class ApkDecoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// update apk info
|
||||||
|
if (!uncompressedFilesOrExts.isEmpty()) {
|
||||||
|
apkInfo.doNotCompress = uncompressedFilesOrExts;
|
||||||
|
}
|
||||||
|
|
||||||
} catch (DirectoryException ex) {
|
} catch (DirectoryException ex) {
|
||||||
throw new AndrolibException(ex);
|
throw new AndrolibException(ex);
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,6 @@ import brut.androlib.exceptions.AndrolibException;
|
|||||||
import brut.util.OSDetection;
|
import brut.util.OSDetection;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
public class Config {
|
public class Config {
|
||||||
@ -48,12 +47,9 @@ public class Config {
|
|||||||
public boolean verbose = false;
|
public boolean verbose = false;
|
||||||
public boolean copyOriginalFiles = false;
|
public boolean copyOriginalFiles = false;
|
||||||
public boolean updateFiles = false;
|
public boolean updateFiles = false;
|
||||||
public boolean isFramework = false;
|
|
||||||
public boolean resourcesAreCompressed = false;
|
|
||||||
public boolean useAapt2 = false;
|
public boolean useAapt2 = false;
|
||||||
public boolean noCrunch = false;
|
public boolean noCrunch = false;
|
||||||
public int forceApi = 0;
|
public int forceApi = 0;
|
||||||
public Collection<String> doNotCompress;
|
|
||||||
|
|
||||||
// Decode options
|
// Decode options
|
||||||
public short decodeSources = DECODE_SOURCES_SMALI;
|
public short decodeSources = DECODE_SOURCES_SMALI;
|
||||||
|
@ -0,0 +1,177 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 Ryszard Wiśniewski <brut.alll@gmail.com>
|
||||||
|
* Copyright (C) 2010 Connor Tumbleson <connor.tumbleson@gmail.com>
|
||||||
|
*
|
||||||
|
* 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<String, String> mSdkInfo = new LinkedHashMap<>();
|
||||||
|
public PackageInfo packageInfo = new PackageInfo();
|
||||||
|
public VersionInfo versionInfo = new VersionInfo();
|
||||||
|
public boolean resourcesAreCompressed;
|
||||||
|
public boolean sharedLibrary;
|
||||||
|
public boolean sparseResources;
|
||||||
|
public Map<String, String> unknownFiles;
|
||||||
|
public Collection<String> 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<String, String> getSdkInfo() {
|
||||||
|
return mSdkInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSdkInfo(Map<String, String> 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_TIRAMISU;
|
||||||
|
case "UPSIDEDOWNCAKE":
|
||||||
|
case "UPSIDE_DOWN_CAKE":
|
||||||
|
case "VANILLAICECREAM":
|
||||||
|
case "VANILLA_ICE_CREAM":
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package brut.androlib.meta;
|
package brut.androlib.apk;
|
||||||
|
|
||||||
import org.yaml.snakeyaml.constructor.AbstractConstruct;
|
import org.yaml.snakeyaml.constructor.AbstractConstruct;
|
||||||
import org.yaml.snakeyaml.constructor.Constructor;
|
import org.yaml.snakeyaml.constructor.Constructor;
|
||||||
@ -33,7 +33,7 @@ public class ClassSafeConstructor extends Constructor {
|
|||||||
super(new LoaderOptions());
|
super(new LoaderOptions());
|
||||||
this.yamlConstructors.put(Tag.STR, new ConstructStringEx());
|
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(PackageInfo.class);
|
||||||
this.allowableClasses.add(UsesFramework.class);
|
this.allowableClasses.add(UsesFramework.class);
|
||||||
this.allowableClasses.add(VersionInfo.class);
|
this.allowableClasses.add(VersionInfo.class);
|
@ -14,7 +14,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package brut.androlib.meta;
|
package brut.androlib.apk;
|
||||||
|
|
||||||
import org.yaml.snakeyaml.DumperOptions;
|
import org.yaml.snakeyaml.DumperOptions;
|
||||||
import org.yaml.snakeyaml.nodes.Node;
|
import org.yaml.snakeyaml.nodes.Node;
|
@ -14,7 +14,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package brut.androlib.meta;
|
package brut.androlib.apk;
|
||||||
|
|
||||||
public class PackageInfo {
|
public class PackageInfo {
|
||||||
public String forcedPackageId;
|
public String forcedPackageId;
|
@ -14,7 +14,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package brut.androlib.meta;
|
package brut.androlib.apk;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -14,7 +14,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package brut.androlib.meta;
|
package brut.androlib.apk;
|
||||||
|
|
||||||
public class VersionInfo {
|
public class VersionInfo {
|
||||||
public String versionCode;
|
public String versionCode;
|
@ -14,7 +14,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package brut.androlib.meta;
|
package brut.androlib.apk;
|
||||||
|
|
||||||
import org.apache.commons.text.StringEscapeUtils;
|
import org.apache.commons.text.StringEscapeUtils;
|
||||||
import org.apache.commons.text.translate.CharSequenceTranslator;
|
import org.apache.commons.text.translate.CharSequenceTranslator;
|
@ -1,90 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2010 Ryszard Wiśniewski <brut.alll@gmail.com>
|
|
||||||
* Copyright (C) 2010 Connor Tumbleson <connor.tumbleson@gmail.com>
|
|
||||||
*
|
|
||||||
* 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<String, String> sdkInfo;
|
|
||||||
public PackageInfo packageInfo;
|
|
||||||
public VersionInfo versionInfo;
|
|
||||||
public boolean compressionType;
|
|
||||||
public boolean sharedLibrary;
|
|
||||||
public boolean sparseResources;
|
|
||||||
public Map<String, String> unknownFiles;
|
|
||||||
public Collection<String> 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,835 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2010 Ryszard Wiśniewski <brut.alll@gmail.com>
|
|
||||||
* Copyright (C) 2010 Connor Tumbleson <connor.tumbleson@gmail.com>
|
|
||||||
*
|
|
||||||
* 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<String, String> 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<ResFileDecoder, AXmlResourceParser> 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<ResFileDecoder, AXmlResourceParser> 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<ResFileDecoder, AXmlResourceParser> 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<String, String> 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<String> cmd, boolean customAapt)
|
|
||||||
throws AndrolibException {
|
|
||||||
|
|
||||||
List<String> 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<String> 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<String> 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<String> 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<ResFileDecoder, AXmlResourceParser> 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<ResFileDecoder, AXmlResourceParser> 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -20,8 +20,8 @@ import brut.androlib.Config;
|
|||||||
import brut.androlib.exceptions.AndrolibException;
|
import brut.androlib.exceptions.AndrolibException;
|
||||||
import brut.androlib.exceptions.CantFindFrameworkResException;
|
import brut.androlib.exceptions.CantFindFrameworkResException;
|
||||||
import brut.androlib.res.decoder.ARSCDecoder;
|
import brut.androlib.res.decoder.ARSCDecoder;
|
||||||
import brut.androlib.res.decoder.arsc.ARSCData;
|
import brut.androlib.res.data.arsc.ARSCData;
|
||||||
import brut.androlib.res.decoder.arsc.FlagsOffset;
|
import brut.androlib.res.data.arsc.FlagsOffset;
|
||||||
import brut.util.Jar;
|
import brut.util.Jar;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
|
|
||||||
@ -137,7 +137,7 @@ public class Framework {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void publicizeResources(byte[] arsc) throws AndrolibException {
|
private void publicizeResources(byte[] arsc) throws AndrolibException {
|
||||||
publicizeResources(arsc, ARSCDecoder.decode(new ByteArrayInputStream(arsc), true, true).getFlagsOffsets());
|
publicizeResources(arsc, ARSCDecoder.decode(new ByteArrayInputStream(arsc), true, true).getFlagsOffsets());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,359 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 Ryszard Wiśniewski <brut.alll@gmail.com>
|
||||||
|
* Copyright (C) 2010 Connor Tumbleson <connor.tumbleson@gmail.com>
|
||||||
|
*
|
||||||
|
* 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.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;
|
||||||
|
|
||||||
|
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<String, String> mResFileMapping = new HashMap<>();
|
||||||
|
|
||||||
|
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<String, String> 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<ResFileDecoder, AXmlResourceParser> 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<ResFileDecoder, AXmlResourceParser> 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<ResFileDecoder, AXmlResourceParser> 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<ResFileDecoder, AXmlResourceParser> 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<ResFileDecoder, AXmlResourceParser> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -16,23 +16,32 @@
|
|||||||
*/
|
*/
|
||||||
package brut.androlib.res.data;
|
package brut.androlib.res.data;
|
||||||
|
|
||||||
|
import brut.androlib.ApkDecoder;
|
||||||
|
import brut.androlib.Config;
|
||||||
import brut.androlib.exceptions.AndrolibException;
|
import brut.androlib.exceptions.AndrolibException;
|
||||||
import brut.androlib.exceptions.UndefinedResObjectException;
|
import brut.androlib.exceptions.UndefinedResObjectException;
|
||||||
import brut.androlib.meta.MetaInfo;
|
import brut.androlib.apk.ApkInfo;
|
||||||
import brut.androlib.meta.PackageInfo;
|
import brut.androlib.apk.UsesFramework;
|
||||||
import brut.androlib.meta.UsesFramework;
|
import brut.androlib.res.Framework;
|
||||||
import brut.androlib.meta.VersionInfo;
|
|
||||||
import brut.androlib.res.AndrolibResources;
|
|
||||||
import brut.androlib.res.data.value.ResValue;
|
import brut.androlib.res.data.value.ResValue;
|
||||||
|
import brut.androlib.res.decoder.ARSCDecoder;
|
||||||
import brut.androlib.res.xml.ResXmlPatcher;
|
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 com.google.common.base.Strings;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
public class ResTable {
|
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<Integer, ResPackage> mPackagesById = new HashMap<>();
|
private final Map<Integer, ResPackage> mPackagesById = new HashMap<>();
|
||||||
private final Map<String, ResPackage> mPackagesByName = new HashMap<>();
|
private final Map<String, ResPackage> mPackagesByName = new HashMap<>();
|
||||||
private final Set<ResPackage> mMainPackages = new LinkedHashSet<>();
|
private final Set<ResPackage> mMainPackages = new LinkedHashSet<>();
|
||||||
@ -41,19 +50,24 @@ public class ResTable {
|
|||||||
private String mPackageRenamed;
|
private String mPackageRenamed;
|
||||||
private String mPackageOriginal;
|
private String mPackageOriginal;
|
||||||
private int mPackageId;
|
private int mPackageId;
|
||||||
private boolean mAnalysisMode = false;
|
|
||||||
private boolean mSharedLibrary = false;
|
|
||||||
private boolean mSparseResources = false;
|
|
||||||
|
|
||||||
private final Map<String, String> mSdkInfo = new LinkedHashMap<>();
|
private boolean mMainPkgLoaded = false;
|
||||||
private final VersionInfo mVersionInfo = new VersionInfo();
|
|
||||||
|
|
||||||
public ResTable() {
|
public ResTable() {
|
||||||
mAndRes = null;
|
this(Config.getDefaultConfig(), new ApkInfo());
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResTable(AndrolibResources andRes) {
|
public ResTable(Config config, ApkInfo apkInfo) {
|
||||||
mAndRes = andRes;
|
mConfig = config;
|
||||||
|
mApkInfo = apkInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getAnalysisMode() {
|
||||||
|
return mConfig.analysisMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMainPkgLoaded() {
|
||||||
|
return mMainPkgLoaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResResSpec getResSpec(int resID) throws AndrolibException {
|
public ResResSpec getResSpec(int resID) throws AndrolibException {
|
||||||
@ -84,10 +98,86 @@ public class ResTable {
|
|||||||
if (pkg != null) {
|
if (pkg != null) {
|
||||||
return pkg;
|
return pkg;
|
||||||
}
|
}
|
||||||
if (mAndRes != null) {
|
pkg = loadFrameworkPkg(id);
|
||||||
return mAndRes.loadFrameworkPkg(this, 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 {
|
public ResPackage getHighestSpecPackage() throws AndrolibException {
|
||||||
@ -147,10 +237,6 @@ public class ResTable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAnalysisMode(boolean mode) {
|
|
||||||
mAnalysisMode = mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPackageRenamed(String pkg) {
|
public void setPackageRenamed(String pkg) {
|
||||||
mPackageRenamed = pkg;
|
mPackageRenamed = pkg;
|
||||||
}
|
}
|
||||||
@ -164,39 +250,28 @@ public class ResTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setSharedLibrary(boolean flag) {
|
public void setSharedLibrary(boolean flag) {
|
||||||
mSharedLibrary = flag;
|
mApkInfo.sharedLibrary = flag;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSparseResources(boolean flag) {
|
public void setSparseResources(boolean flag) {
|
||||||
mSparseResources = flag;
|
mApkInfo.sparseResources = flag;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clearSdkInfo() {
|
public void clearSdkInfo() {
|
||||||
mSdkInfo.clear();
|
mApkInfo.getSdkInfo().clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addSdkInfo(String key, String value) {
|
public void addSdkInfo(String key, String value) {
|
||||||
mSdkInfo.put(key, value);
|
mApkInfo.getSdkInfo().put(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setVersionName(String versionName) {
|
public void setVersionName(String versionName) {
|
||||||
mVersionInfo.versionName = versionName;
|
mApkInfo.versionInfo.versionName = versionName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setVersionCode(String versionCode) {
|
public void setVersionCode(String versionCode) {
|
||||||
mVersionInfo.versionCode = versionCode;
|
mApkInfo.versionInfo.versionCode = versionCode;
|
||||||
}
|
|
||||||
|
|
||||||
public VersionInfo getVersionInfo() {
|
|
||||||
return mVersionInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, String> getSdkInfo() {
|
|
||||||
return mSdkInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean getAnalysisMode() {
|
|
||||||
return mAnalysisMode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPackageRenamed() {
|
public String getPackageRenamed() {
|
||||||
@ -211,15 +286,11 @@ public class ResTable {
|
|||||||
return mPackageId;
|
return mPackageId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getSharedLibrary() {
|
|
||||||
return mSharedLibrary;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean getSparseResources() {
|
public boolean getSparseResources() {
|
||||||
return mSparseResources;
|
return mApkInfo.sparseResources;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isFrameworkApk() {
|
private boolean isFrameworkApk() {
|
||||||
for (ResPackage pkg : listMainPackages()) {
|
for (ResPackage pkg : listMainPackages()) {
|
||||||
if (pkg.getId() > 0 && pkg.getId() < 64) {
|
if (pkg.getId() > 0 && pkg.getId() < 64) {
|
||||||
return true;
|
return true;
|
||||||
@ -228,19 +299,16 @@ public class ResTable {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initMetaInfo(MetaInfo meta, File outDir) throws AndrolibException {
|
public void initApkInfo(ApkInfo apkInfo, File outDir) throws AndrolibException {
|
||||||
meta.isFrameworkApk = isFrameworkApk();
|
apkInfo.isFrameworkApk = isFrameworkApk();
|
||||||
if (!listFramePackages().isEmpty()) {
|
if (!listFramePackages().isEmpty()) {
|
||||||
meta.usesFramework = getUsesFramework();
|
apkInfo.usesFramework = getUsesFramework();
|
||||||
}
|
}
|
||||||
if (!getSdkInfo().isEmpty()) {
|
if (!mApkInfo.getSdkInfo().isEmpty()) {
|
||||||
initSdkInfo(outDir);
|
updateSdkInfoFromResources(outDir);
|
||||||
meta.sdkInfo = getSdkInfo();
|
|
||||||
}
|
}
|
||||||
meta.packageInfo = getPackageInfo();
|
initPackageInfo();
|
||||||
meta.versionInfo = getVersionInfoWithName(outDir);
|
loadVersionName(outDir);
|
||||||
meta.sharedLibrary = getSharedLibrary();
|
|
||||||
meta.sparseResources = getSparseResources();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private UsesFramework getUsesFramework() {
|
private UsesFramework getUsesFramework() {
|
||||||
@ -259,30 +327,30 @@ public class ResTable {
|
|||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initSdkInfo(File outDir) {
|
private void updateSdkInfoFromResources(File outDir) {
|
||||||
Map<String, String> info = mSdkInfo;
|
|
||||||
String refValue;
|
String refValue;
|
||||||
if (info.get("minSdkVersion") != null) {
|
Map<String, String> sdkInfo = mApkInfo.getSdkInfo();
|
||||||
refValue = ResXmlPatcher.pullValueFromIntegers(outDir, info.get("minSdkVersion"));
|
if (sdkInfo.get("minSdkVersion") != null) {
|
||||||
|
refValue = ResXmlPatcher.pullValueFromIntegers(outDir, sdkInfo.get("minSdkVersion"));
|
||||||
if (refValue != null) {
|
if (refValue != null) {
|
||||||
info.put("minSdkVersion", refValue);
|
sdkInfo.put("minSdkVersion", refValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (info.get("targetSdkVersion") != null) {
|
if (sdkInfo.get("targetSdkVersion") != null) {
|
||||||
refValue = ResXmlPatcher.pullValueFromIntegers(outDir, info.get("targetSdkVersion"));
|
refValue = ResXmlPatcher.pullValueFromIntegers(outDir, sdkInfo.get("targetSdkVersion"));
|
||||||
if (refValue != null) {
|
if (refValue != null) {
|
||||||
info.put("targetSdkVersion", refValue);
|
sdkInfo.put("targetSdkVersion", refValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (info.get("maxSdkVersion") != null) {
|
if (sdkInfo.get("maxSdkVersion") != null) {
|
||||||
refValue = ResXmlPatcher.pullValueFromIntegers(outDir, info.get("maxSdkVersion"));
|
refValue = ResXmlPatcher.pullValueFromIntegers(outDir, sdkInfo.get("maxSdkVersion"));
|
||||||
if (refValue != null) {
|
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 renamed = getPackageRenamed();
|
||||||
String original = getPackageOriginal();
|
String original = getPackageOriginal();
|
||||||
|
|
||||||
@ -292,25 +360,21 @@ public class ResTable {
|
|||||||
} catch (UndefinedResObjectException ignored) {}
|
} catch (UndefinedResObjectException ignored) {}
|
||||||
|
|
||||||
if (Strings.isNullOrEmpty(original)) {
|
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
|
// only put rename-manifest-package into apktool.yml, if the change will be required
|
||||||
if (!renamed.equalsIgnoreCase(original)) {
|
if (!renamed.equalsIgnoreCase(original)) {
|
||||||
info.renameManifestPackage = renamed;
|
mApkInfo.packageInfo.renameManifestPackage = renamed;
|
||||||
}
|
}
|
||||||
info.forcedPackageId = String.valueOf(id);
|
mApkInfo.packageInfo.forcedPackageId = String.valueOf(id);
|
||||||
return info;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private VersionInfo getVersionInfoWithName(File outDir) {
|
private void loadVersionName(File outDir) {
|
||||||
VersionInfo info = getVersionInfo();
|
String versionName = mApkInfo.versionInfo.versionName;
|
||||||
String refValue = ResXmlPatcher.pullValueFromStrings(outDir, info.versionName);
|
String refValue = ResXmlPatcher.pullValueFromStrings(outDir, versionName);
|
||||||
if (refValue != null) {
|
if (refValue != null) {
|
||||||
info.versionName = refValue;
|
mApkInfo.versionInfo.versionName = refValue;
|
||||||
}
|
}
|
||||||
return info;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package brut.androlib.res.decoder.arsc;
|
package brut.androlib.res.data.arsc;
|
||||||
|
|
||||||
import brut.androlib.exceptions.AndrolibException;
|
import brut.androlib.exceptions.AndrolibException;
|
||||||
import brut.androlib.res.data.ResPackage;
|
import brut.androlib.res.data.ResPackage;
|
@ -14,7 +14,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package brut.androlib.res.decoder.arsc;
|
package brut.androlib.res.data.arsc;
|
||||||
|
|
||||||
import brut.util.ExtDataInput;
|
import brut.util.ExtDataInput;
|
||||||
import org.apache.commons.io.input.CountingInputStream;
|
import org.apache.commons.io.input.CountingInputStream;
|
@ -14,7 +14,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package brut.androlib.res.decoder.arsc;
|
package brut.androlib.res.data.arsc;
|
||||||
|
|
||||||
import brut.androlib.res.data.value.ResValue;
|
import brut.androlib.res.data.value.ResValue;
|
||||||
|
|
@ -14,7 +14,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package brut.androlib.res.decoder.arsc;
|
package brut.androlib.res.data.arsc;
|
||||||
|
|
||||||
public class FlagsOffset {
|
public class FlagsOffset {
|
||||||
public final int offset;
|
public final int offset;
|
@ -14,7 +14,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* 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.
|
* Namespace stack, holds prefix+uri pairs, as well as depth information.
|
@ -14,7 +14,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package brut.androlib.res.decoder.ninepatch;
|
package brut.androlib.res.data.ninepatch;
|
||||||
|
|
||||||
import brut.util.ExtDataInput;
|
import brut.util.ExtDataInput;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
@ -14,7 +14,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package brut.androlib.res.decoder.ninepatch;
|
package brut.androlib.res.data.ninepatch;
|
||||||
|
|
||||||
import brut.util.ExtDataInput;
|
import brut.util.ExtDataInput;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
@ -20,10 +20,10 @@ import android.util.TypedValue;
|
|||||||
import brut.androlib.exceptions.AndrolibException;
|
import brut.androlib.exceptions.AndrolibException;
|
||||||
import brut.androlib.res.data.*;
|
import brut.androlib.res.data.*;
|
||||||
import brut.androlib.res.data.value.*;
|
import brut.androlib.res.data.value.*;
|
||||||
import brut.androlib.res.decoder.arsc.ARSCData;
|
import brut.androlib.res.data.arsc.ARSCData;
|
||||||
import brut.androlib.res.decoder.arsc.ARSCHeader;
|
import brut.androlib.res.data.arsc.ARSCHeader;
|
||||||
import brut.androlib.res.decoder.arsc.EntryData;
|
import brut.androlib.res.data.arsc.EntryData;
|
||||||
import brut.androlib.res.decoder.arsc.FlagsOffset;
|
import brut.androlib.res.data.arsc.FlagsOffset;
|
||||||
import brut.util.Duo;
|
import brut.util.Duo;
|
||||||
import brut.util.ExtDataInput;
|
import brut.util.ExtDataInput;
|
||||||
import com.google.common.io.LittleEndianDataInputStream;
|
import com.google.common.io.LittleEndianDataInputStream;
|
||||||
|
@ -20,7 +20,7 @@ import android.content.res.XmlResourceParser;
|
|||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import brut.androlib.exceptions.AndrolibException;
|
import brut.androlib.exceptions.AndrolibException;
|
||||||
import brut.androlib.res.data.ResID;
|
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.androlib.res.xml.ResXmlEncoders;
|
||||||
import brut.util.ExtDataInput;
|
import brut.util.ExtDataInput;
|
||||||
import com.google.common.io.LittleEndianDataInputStream;
|
import com.google.common.io.LittleEndianDataInputStream;
|
||||||
|
@ -18,8 +18,8 @@ package brut.androlib.res.decoder;
|
|||||||
|
|
||||||
import brut.androlib.exceptions.AndrolibException;
|
import brut.androlib.exceptions.AndrolibException;
|
||||||
import brut.androlib.exceptions.CantFind9PatchChunkException;
|
import brut.androlib.exceptions.CantFind9PatchChunkException;
|
||||||
import brut.androlib.res.decoder.ninepatch.NinePatchData;
|
import brut.androlib.res.data.ninepatch.NinePatchData;
|
||||||
import brut.androlib.res.decoder.ninepatch.OpticalInset;
|
import brut.androlib.res.data.ninepatch.OpticalInset;
|
||||||
import brut.util.ExtDataInput;
|
import brut.util.ExtDataInput;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
*/
|
*/
|
||||||
package brut.androlib;
|
package brut.androlib;
|
||||||
|
|
||||||
import brut.androlib.meta.MetaInfo;
|
import brut.androlib.apk.ApkInfo;
|
||||||
import brut.common.BrutException;
|
import brut.common.BrutException;
|
||||||
import brut.directory.ExtFile;
|
import brut.directory.ExtFile;
|
||||||
import brut.directory.FileDirectory;
|
import brut.directory.FileDirectory;
|
||||||
@ -37,8 +37,8 @@ import static org.junit.Assert.*;
|
|||||||
public class BaseTest {
|
public class BaseTest {
|
||||||
|
|
||||||
protected void compareUnknownFiles() throws BrutException {
|
protected void compareUnknownFiles() throws BrutException {
|
||||||
MetaInfo control = MetaInfo.readMetaFile(sTestOrigDir);
|
ApkInfo control = ApkInfo.load(sTestOrigDir);
|
||||||
MetaInfo test = MetaInfo.readMetaFile(sTestNewDir);
|
ApkInfo test = ApkInfo.load(sTestNewDir);
|
||||||
assertNotNull(control.unknownFiles);
|
assertNotNull(control.unknownFiles);
|
||||||
assertNotNull(test.unknownFiles);
|
assertNotNull(test.unknownFiles);
|
||||||
|
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
package brut.androlib;
|
package brut.androlib;
|
||||||
|
|
||||||
import brut.androlib.exceptions.AndrolibException;
|
import brut.androlib.exceptions.AndrolibException;
|
||||||
import brut.androlib.res.AndrolibResources;
|
|
||||||
import brut.androlib.res.Framework;
|
import brut.androlib.res.Framework;
|
||||||
import brut.common.BrutException;
|
import brut.common.BrutException;
|
||||||
import brut.directory.DirUtil;
|
import brut.directory.DirUtil;
|
||||||
|
@ -20,7 +20,7 @@ import brut.androlib.ApkBuilder;
|
|||||||
import brut.androlib.ApkDecoder;
|
import brut.androlib.ApkDecoder;
|
||||||
import brut.androlib.BaseTest;
|
import brut.androlib.BaseTest;
|
||||||
import brut.androlib.TestUtils;
|
import brut.androlib.TestUtils;
|
||||||
import brut.androlib.meta.MetaInfo;
|
import brut.androlib.apk.ApkInfo;
|
||||||
import brut.common.BrutException;
|
import brut.common.BrutException;
|
||||||
import brut.directory.ExtFile;
|
import brut.directory.ExtFile;
|
||||||
import brut.util.OS;
|
import brut.util.OS;
|
||||||
@ -491,17 +491,17 @@ public class BuildAndDecodeTest extends BaseTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void confirmZeroByteFileExtensionIsNotStored() throws BrutException {
|
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);
|
assertNotEquals("jpg", item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void confirmZeroByteFileIsStored() throws BrutException {
|
public void confirmZeroByteFileIsStored() throws BrutException {
|
||||||
MetaInfo metaInfo = MetaInfo.readMetaFile(sTestNewDir);
|
ApkInfo apkInfo = ApkInfo.load(sTestNewDir);
|
||||||
assertTrue(metaInfo.doNotCompress.contains("assets/0byte_file.jpg"));
|
assertTrue(apkInfo.doNotCompress.contains("assets/0byte_file.jpg"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -16,11 +16,10 @@
|
|||||||
*/
|
*/
|
||||||
package brut.androlib.aapt1;
|
package brut.androlib.aapt1;
|
||||||
|
|
||||||
import brut.androlib.ApkBuilder;
|
|
||||||
import brut.androlib.ApkDecoder;
|
import brut.androlib.ApkDecoder;
|
||||||
import brut.androlib.BaseTest;
|
import brut.androlib.BaseTest;
|
||||||
import brut.androlib.TestUtils;
|
import brut.androlib.TestUtils;
|
||||||
import brut.androlib.meta.MetaInfo;
|
import brut.androlib.apk.ApkInfo;
|
||||||
import brut.directory.ExtFile;
|
import brut.directory.ExtFile;
|
||||||
import brut.common.BrutException;
|
import brut.common.BrutException;
|
||||||
import brut.util.OS;
|
import brut.util.OS;
|
||||||
@ -57,7 +56,7 @@ public class ReferenceVersionCodeTest extends BaseTest {
|
|||||||
File outDir = new File(sTmpDir + File.separator + apk + ".out");
|
File outDir = new File(sTmpDir + File.separator + apk + ".out");
|
||||||
apkDecoder.decode(outDir);
|
apkDecoder.decode(outDir);
|
||||||
|
|
||||||
MetaInfo metaInfo = MetaInfo.readMetaFile(decodedApk);
|
ApkInfo apkInfo = ApkInfo.load(decodedApk);
|
||||||
assertEquals("v1.0.0", metaInfo.versionInfo.versionName);
|
assertEquals("v1.0.0", apkInfo.versionInfo.versionName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@ package brut.androlib.aapt1;
|
|||||||
|
|
||||||
import brut.androlib.*;
|
import brut.androlib.*;
|
||||||
import brut.androlib.exceptions.AndrolibException;
|
import brut.androlib.exceptions.AndrolibException;
|
||||||
import brut.androlib.res.AndrolibResources;
|
|
||||||
import brut.androlib.res.Framework;
|
import brut.androlib.res.Framework;
|
||||||
import brut.directory.ExtFile;
|
import brut.directory.ExtFile;
|
||||||
import brut.common.BrutException;
|
import brut.common.BrutException;
|
||||||
|
@ -17,9 +17,8 @@
|
|||||||
package brut.androlib.aapt2;
|
package brut.androlib.aapt2;
|
||||||
|
|
||||||
import brut.androlib.*;
|
import brut.androlib.*;
|
||||||
import brut.androlib.meta.MetaInfo;
|
import brut.androlib.apk.ApkInfo;
|
||||||
import brut.androlib.Config;
|
import brut.androlib.Config;
|
||||||
import brut.androlib.res.AndrolibResources;
|
|
||||||
import brut.common.BrutException;
|
import brut.common.BrutException;
|
||||||
import brut.directory.ExtFile;
|
import brut.directory.ExtFile;
|
||||||
import brut.util.OS;
|
import brut.util.OS;
|
||||||
@ -79,14 +78,14 @@ public class BuildAndDecodeTest extends BaseTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void confirmZeroByteFileExtensionIsNotStored() throws BrutException {
|
public void confirmZeroByteFileExtensionIsNotStored() throws BrutException {
|
||||||
MetaInfo metaInfo = MetaInfo.readMetaFile(sTestNewDir);
|
ApkInfo apkInfo = ApkInfo.load(sTestNewDir);
|
||||||
assertFalse(metaInfo.doNotCompress.contains("jpg"));
|
assertFalse(apkInfo.doNotCompress.contains("jpg"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void confirmZeroByteFileIsStored() throws BrutException {
|
public void confirmZeroByteFileIsStored() throws BrutException {
|
||||||
MetaInfo metaInfo = MetaInfo.readMetaFile(sTestNewDir);
|
ApkInfo apkInfo = ApkInfo.load(sTestNewDir);
|
||||||
assertTrue(metaInfo.doNotCompress.contains("assets/0byte_file.jpg"));
|
assertTrue(apkInfo.doNotCompress.contains("assets/0byte_file.jpg"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -18,6 +18,7 @@ package brut.androlib.aapt2;
|
|||||||
|
|
||||||
import brut.androlib.*;
|
import brut.androlib.*;
|
||||||
import brut.androlib.exceptions.AndrolibException;
|
import brut.androlib.exceptions.AndrolibException;
|
||||||
|
import brut.androlib.res.ResourcesDecoder;
|
||||||
import brut.androlib.res.data.ResTable;
|
import brut.androlib.res.data.ResTable;
|
||||||
import brut.common.BrutException;
|
import brut.common.BrutException;
|
||||||
import brut.directory.ExtFile;
|
import brut.directory.ExtFile;
|
||||||
@ -53,9 +54,12 @@ public class NonStandardPkgIdTest extends BaseTest {
|
|||||||
new ApkBuilder(config, sTestOrigDir).build(testApk);
|
new ApkBuilder(config, sTestOrigDir).build(testApk);
|
||||||
|
|
||||||
LOGGER.info("Decoding pkgid8.apk...");
|
LOGGER.info("Decoding pkgid8.apk...");
|
||||||
ApkDecoder apkDecoder = new ApkDecoder(testApk);
|
ResourcesDecoder resourcesDecoder = new ResourcesDecoder(
|
||||||
apkDecoder.decode(sTestNewDir);
|
Config.getDefaultConfig(), new ExtFile(testApk));
|
||||||
mResTable = apkDecoder.getResTable();
|
|
||||||
|
sTestNewDir.mkdirs();
|
||||||
|
resourcesDecoder.decodeManifest(sTestNewDir);
|
||||||
|
mResTable = resourcesDecoder.decodeResources(sTestNewDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterClass
|
@AfterClass
|
||||||
|
@ -17,9 +17,9 @@
|
|||||||
package brut.androlib.androlib;
|
package brut.androlib.androlib;
|
||||||
|
|
||||||
import brut.androlib.BaseTest;
|
import brut.androlib.BaseTest;
|
||||||
import brut.androlib.Config;
|
import brut.androlib.apk.ApkInfo;
|
||||||
import brut.androlib.res.AndrolibResources;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
@ -28,82 +28,82 @@ public class InvalidSdkBoundingTest extends BaseTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void checkIfInvalidValuesPass() {
|
public void checkIfInvalidValuesPass() {
|
||||||
AndrolibResources androlibResources = new AndrolibResources();
|
ApkInfo apkInfo = new ApkInfo();
|
||||||
|
|
||||||
Map<String, String> sdkInfo = new LinkedHashMap<>();
|
Map<String, String> sdkInfo = new LinkedHashMap<>();
|
||||||
sdkInfo.put("minSdkVersion", "15");
|
sdkInfo.put("minSdkVersion", "15");
|
||||||
sdkInfo.put("targetSdkVersion", "25");
|
sdkInfo.put("targetSdkVersion", "25");
|
||||||
sdkInfo.put("maxSdkVersion", "19");
|
sdkInfo.put("maxSdkVersion", "19");
|
||||||
|
|
||||||
androlibResources.setSdkInfo(sdkInfo);
|
apkInfo.setSdkInfo(sdkInfo);
|
||||||
assertEquals("19", androlibResources.checkTargetSdkVersionBounds());
|
assertEquals("19", apkInfo.checkTargetSdkVersionBounds());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void checkIfMissingMinPasses() {
|
public void checkIfMissingMinPasses() {
|
||||||
AndrolibResources androlibResources = new AndrolibResources();
|
ApkInfo apkInfo = new ApkInfo();
|
||||||
|
|
||||||
Map<String, String> sdkInfo = new LinkedHashMap<>();
|
Map<String, String> sdkInfo = new LinkedHashMap<>();
|
||||||
sdkInfo.put("targetSdkVersion", "25");
|
sdkInfo.put("targetSdkVersion", "25");
|
||||||
sdkInfo.put("maxSdkVersion", "19");
|
sdkInfo.put("maxSdkVersion", "19");
|
||||||
|
|
||||||
androlibResources.setSdkInfo(sdkInfo);
|
apkInfo.setSdkInfo(sdkInfo);
|
||||||
assertEquals("19", androlibResources.checkTargetSdkVersionBounds());
|
assertEquals("19", apkInfo.checkTargetSdkVersionBounds());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void checkIfMissingMaxPasses() {
|
public void checkIfMissingMaxPasses() {
|
||||||
AndrolibResources androlibResources = new AndrolibResources();
|
ApkInfo apkInfo = new ApkInfo();
|
||||||
|
|
||||||
Map<String, String> sdkInfo = new LinkedHashMap<>();
|
Map<String, String> sdkInfo = new LinkedHashMap<>();
|
||||||
sdkInfo.put("minSdkVersion", "15");
|
sdkInfo.put("minSdkVersion", "15");
|
||||||
sdkInfo.put("targetSdkVersion", "25");
|
sdkInfo.put("targetSdkVersion", "25");
|
||||||
|
|
||||||
androlibResources.setSdkInfo(sdkInfo);
|
apkInfo.setSdkInfo(sdkInfo);
|
||||||
assertEquals("25", androlibResources.checkTargetSdkVersionBounds());
|
assertEquals("25", apkInfo.checkTargetSdkVersionBounds());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void checkIfMissingBothPasses() {
|
public void checkIfMissingBothPasses() {
|
||||||
AndrolibResources androlibResources = new AndrolibResources();
|
ApkInfo apkInfo = new ApkInfo();
|
||||||
|
|
||||||
Map<String, String> sdkInfo = new LinkedHashMap<>();
|
Map<String, String> sdkInfo = new LinkedHashMap<>();
|
||||||
sdkInfo.put("targetSdkVersion", "25");
|
sdkInfo.put("targetSdkVersion", "25");
|
||||||
|
|
||||||
androlibResources.setSdkInfo(sdkInfo);
|
apkInfo.setSdkInfo(sdkInfo);
|
||||||
assertEquals("25", androlibResources.checkTargetSdkVersionBounds());
|
assertEquals("25", apkInfo.checkTargetSdkVersionBounds());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void checkForShortHandSTag() {
|
public void checkForShortHandSTag() {
|
||||||
AndrolibResources androlibResources = new AndrolibResources();
|
ApkInfo apkInfo = new ApkInfo();
|
||||||
|
|
||||||
Map<String, String> sdkInfo = new LinkedHashMap<>();
|
Map<String, String> sdkInfo = new LinkedHashMap<>();
|
||||||
sdkInfo.put("targetSdkVersion", "S");
|
sdkInfo.put("targetSdkVersion", "S");
|
||||||
|
|
||||||
androlibResources.setSdkInfo(sdkInfo);
|
apkInfo.setSdkInfo(sdkInfo);
|
||||||
assertEquals("31", androlibResources.checkTargetSdkVersionBounds());
|
assertEquals("31", apkInfo.checkTargetSdkVersionBounds());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void checkForShortHandSdkTag() {
|
public void checkForShortHandSdkTag() {
|
||||||
AndrolibResources androlibResources = new AndrolibResources();
|
ApkInfo apkInfo = new ApkInfo();
|
||||||
|
|
||||||
Map<String, String> sdkInfo = new LinkedHashMap<>();
|
Map<String, String> sdkInfo = new LinkedHashMap<>();
|
||||||
sdkInfo.put("targetSdkVersion", "O");
|
sdkInfo.put("targetSdkVersion", "O");
|
||||||
|
|
||||||
androlibResources.setSdkInfo(sdkInfo);
|
apkInfo.setSdkInfo(sdkInfo);
|
||||||
assertEquals("26", androlibResources.checkTargetSdkVersionBounds());
|
assertEquals("26", apkInfo.checkTargetSdkVersionBounds());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void checkForSdkDevelopmentInsaneTestValue() {
|
public void checkForSdkDevelopmentInsaneTestValue() {
|
||||||
AndrolibResources androlibResources = new AndrolibResources();
|
ApkInfo apkInfo = new ApkInfo();
|
||||||
|
|
||||||
Map<String, String> sdkInfo = new LinkedHashMap<>();
|
Map<String, String> sdkInfo = new LinkedHashMap<>();
|
||||||
sdkInfo.put("targetSdkVersion", "VANILLAICECREAM");
|
sdkInfo.put("targetSdkVersion", "VANILLAICECREAM");
|
||||||
|
|
||||||
androlibResources.setSdkInfo(sdkInfo);
|
apkInfo.setSdkInfo(sdkInfo);
|
||||||
assertEquals("10000", androlibResources.checkTargetSdkVersionBounds());
|
assertEquals("10000", apkInfo.checkTargetSdkVersionBounds());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,9 @@ package brut.androlib.decode;
|
|||||||
|
|
||||||
import brut.androlib.ApkDecoder;
|
import brut.androlib.ApkDecoder;
|
||||||
import brut.androlib.BaseTest;
|
import brut.androlib.BaseTest;
|
||||||
|
import brut.androlib.Config;
|
||||||
import brut.androlib.TestUtils;
|
import brut.androlib.TestUtils;
|
||||||
|
import brut.androlib.res.ResourcesDecoder;
|
||||||
import brut.androlib.res.data.ResTable;
|
import brut.androlib.res.data.ResTable;
|
||||||
import brut.androlib.res.data.value.ResArrayValue;
|
import brut.androlib.res.data.value.ResArrayValue;
|
||||||
import brut.androlib.res.data.value.ResValue;
|
import brut.androlib.res.data.value.ResValue;
|
||||||
@ -50,9 +52,12 @@ public class DecodeArrayTest extends BaseTest {
|
|||||||
@Test
|
@Test
|
||||||
public void decodeStringArray() throws BrutException {
|
public void decodeStringArray() throws BrutException {
|
||||||
String apk = "issue1994.apk";
|
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();
|
ResValue value = resTable.getResSpec(0x7f020001).getDefaultResource().getValue();
|
||||||
|
|
||||||
assertTrue("Not a ResArrayValue. Found: " + value.getClass(), value instanceof ResArrayValue);
|
assertTrue("Not a ResArrayValue. Found: " + value.getClass(), value instanceof ResArrayValue);
|
||||||
@ -61,9 +66,11 @@ public class DecodeArrayTest extends BaseTest {
|
|||||||
@Test
|
@Test
|
||||||
public void decodeArray() throws BrutException {
|
public void decodeArray() throws BrutException {
|
||||||
String apk = "issue1994.apk";
|
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();
|
ResValue value = resTable.getResSpec(0x7f020000).getDefaultResource().getValue();
|
||||||
|
|
||||||
assertTrue("Not a ResArrayValue. Found: " + value.getClass(), value instanceof ResArrayValue);
|
assertTrue("Not a ResArrayValue. Found: " + value.getClass(), value instanceof ResArrayValue);
|
||||||
|
@ -16,11 +16,10 @@
|
|||||||
*/
|
*/
|
||||||
package brut.androlib.decode;
|
package brut.androlib.decode;
|
||||||
|
|
||||||
import brut.androlib.ApkBuilder;
|
|
||||||
import brut.androlib.ApkDecoder;
|
import brut.androlib.ApkDecoder;
|
||||||
import brut.androlib.BaseTest;
|
import brut.androlib.BaseTest;
|
||||||
import brut.androlib.TestUtils;
|
import brut.androlib.TestUtils;
|
||||||
import brut.androlib.meta.MetaInfo;
|
import brut.androlib.apk.ApkInfo;
|
||||||
import brut.directory.ExtFile;
|
import brut.directory.ExtFile;
|
||||||
import brut.common.BrutException;
|
import brut.common.BrutException;
|
||||||
import brut.util.OS;
|
import brut.util.OS;
|
||||||
@ -58,8 +57,8 @@ public class DoubleExtensionUnknownFileTest extends BaseTest {
|
|||||||
File outDir = new File(sTmpDir + File.separator + apk + ".out");
|
File outDir = new File(sTmpDir + File.separator + apk + ".out");
|
||||||
apkDecoder.decode(outDir);
|
apkDecoder.decode(outDir);
|
||||||
|
|
||||||
MetaInfo metaInfo = MetaInfo.readMetaFile(decodedApk);
|
ApkInfo apkInfo = ApkInfo.load(decodedApk);
|
||||||
for (String string : metaInfo.doNotCompress) {
|
for (String string : apkInfo.doNotCompress) {
|
||||||
if (StringUtils.countMatches(string, ".") > 1) {
|
if (StringUtils.countMatches(string, ".") > 1) {
|
||||||
assertTrue(string.equalsIgnoreCase("assets/bin/Data/sharedassets1.assets.split0"));
|
assertTrue(string.equalsIgnoreCase("assets/bin/Data/sharedassets1.assets.split0"));
|
||||||
}
|
}
|
||||||
|
@ -16,11 +16,10 @@
|
|||||||
*/
|
*/
|
||||||
package brut.androlib.decode;
|
package brut.androlib.decode;
|
||||||
|
|
||||||
import brut.androlib.ApkBuilder;
|
|
||||||
import brut.androlib.ApkDecoder;
|
import brut.androlib.ApkDecoder;
|
||||||
import brut.androlib.BaseTest;
|
import brut.androlib.BaseTest;
|
||||||
import brut.androlib.TestUtils;
|
import brut.androlib.TestUtils;
|
||||||
import brut.androlib.meta.MetaInfo;
|
import brut.androlib.apk.ApkInfo;
|
||||||
import brut.common.BrutException;
|
import brut.common.BrutException;
|
||||||
import brut.directory.ExtFile;
|
import brut.directory.ExtFile;
|
||||||
import brut.util.OS;
|
import brut.util.OS;
|
||||||
@ -57,7 +56,7 @@ public class MissingVersionManifestTest extends BaseTest {
|
|||||||
File outDir = new File(sTmpDir + File.separator + apk + ".out");
|
File outDir = new File(sTmpDir + File.separator + apk + ".out");
|
||||||
apkDecoder.decode(outDir);
|
apkDecoder.decode(outDir);
|
||||||
|
|
||||||
MetaInfo metaInfo = MetaInfo.readMetaFile(decodedApk);
|
ApkInfo apkInfo = ApkInfo.load(decodedApk);
|
||||||
assertNull(metaInfo.versionInfo.versionName);
|
assertNull(apkInfo.versionInfo.versionName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user