Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: ApkDecoder & ApkBuilder overhaul #3699

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
131 changes: 77 additions & 54 deletions brut.apktool/apktool-cli/src/main/java/brut/apktool/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -88,24 +88,34 @@ public static void main(String[] args) throws BrutException {

boolean cmdFound = false;
for (String opt : commandLine.getArgs()) {
if (opt.equalsIgnoreCase("d") || opt.equalsIgnoreCase("decode")) {
cmdDecode(commandLine, config);
cmdFound = true;
} else if (opt.equalsIgnoreCase("b") || opt.equalsIgnoreCase("build")) {
cmdBuild(commandLine, config);
cmdFound = true;
} else if (opt.equalsIgnoreCase("if") || opt.equalsIgnoreCase("install-framework")) {
cmdInstallFramework(commandLine, config);
cmdFound = true;
} else if (opt.equalsIgnoreCase("empty-framework-dir")) {
cmdEmptyFrameworkDirectory(commandLine, config);
cmdFound = true;
} else if (opt.equalsIgnoreCase("list-frameworks")) {
cmdListFrameworks(commandLine, config);
cmdFound = true;
} else if (opt.equalsIgnoreCase("publicize-resources")) {
cmdPublicizeResources(commandLine, config);
cmdFound = true;
switch (opt) {
case "d":
case "decode":
cmdDecode(commandLine, config);
cmdFound = true;
break;
case "b":
case "build":
cmdBuild(commandLine, config);
cmdFound = true;
break;
case "if":
case "install-framework":
cmdInstallFramework(commandLine, config);
cmdFound = true;
break;
case "empty-framework-dir":
cmdEmptyFrameworkDirectory(commandLine, config);
cmdFound = true;
break;
case "list-frameworks":
cmdListFrameworks(commandLine, config);
cmdFound = true;
break;
case "publicize-resources":
cmdPublicizeResources(commandLine, config);
cmdFound = true;
break;
}
}

Expand Down Expand Up @@ -214,8 +224,7 @@ private static void cmdDecode(CommandLine cli, Config config) throws AndrolibExc
}

ExtFile apkFile = new ExtFile(apkName);
ApkDecoder decoder = new ApkDecoder(config, apkFile);

ApkDecoder decoder = new ApkDecoder(apkFile, config);
try {
decoder.decode(outDir);
} catch (OutDirExistsException ex) {
Expand All @@ -235,18 +244,12 @@ private static void cmdDecode(CommandLine cli, Config config) throws AndrolibExc
+ ". You must install proper "
+ "framework files, see project website for more info.");
System.exit(1);
} catch (IOException ex) {
System.err.println("Could not modify file. Please ensure you have permission.");
System.exit(1);
} catch (DirectoryException ex) {
System.err.println("Could not modify internal dex files. Please ensure you have permission.");
System.exit(1);
}
}

private static void cmdBuild(CommandLine cli, Config config) {
private static void cmdBuild(CommandLine cli, Config config) throws AndrolibException {
String[] args = cli.getArgs();
String appDirName = args.length < 2 ? "." : args[1];
String apkDirName = args.length < 2 ? "." : args[1];

// check for build options
if (cli.hasOption("f") || cli.hasOption("force-all")) {
Expand All @@ -262,21 +265,41 @@ private static void cmdBuild(CommandLine cli, Config config) {
config.verbose = true;
}
if (cli.hasOption("a") || cli.hasOption("aapt")) {
config.aaptPath = cli.getOptionValue("a");
if (cli.hasOption("use-aapt1") || cli.hasOption("use-aapt2")) {
System.err.println("You can only use one of -a/--aapt or --use-aapt1 or --use-aapt2.");
System.exit(1);
}

try {
String aaptPath = cli.getOptionValue("a");
int aaptVersion = AaptManager.getAaptVersion(aaptPath);
if (aaptVersion < AaptManager.AAPT_VERSION_MIN && aaptVersion > AaptManager.AAPT_VERSION_MAX) {
System.err.println("AAPT version " + aaptVersion + " is not supported");
System.exit(1);
}

config.aaptPath = aaptPath;
config.aaptVersion = aaptVersion;
} catch (BrutException ex) {
System.err.println(ex.getMessage());
System.exit(1);
}
} else if (cli.hasOption("use-aapt1")) {
if (cli.hasOption("use-aapt2")) {
System.err.println("You can only use one of --use-aapt1 or --use-aapt2.");
System.exit(1);
}

config.aaptVersion = 1;
}
if (cli.hasOption("c") || cli.hasOption("copy-original")) {
config.copyOriginalFiles = true;
}
if (cli.hasOption("nc") || cli.hasOption("no-crunch")) {
config.noCrunch = true;
}
if (cli.hasOption("use-aapt1")) {
config.useAapt2 = false;
}

if (cli.hasOption("use-aapt1") && cli.hasOption("use-aapt2")) {
System.err.println("You can only use one of --use-aapt1 or --use-aapt2.");
System.exit(1);
if (cli.hasOption("na") || cli.hasOption("no-apk")) {
config.noApk = true;
}

File outFile;
Expand All @@ -286,21 +309,14 @@ private static void cmdBuild(CommandLine cli, Config config) {
outFile = null;
}

if (config.netSecConf && !config.useAapt2) {
System.err.println("-n / --net-sec-conf is only supported with --use-aapt2.");
if (config.netSecConf && config.aaptVersion == 1) {
System.err.println("-n / --net-sec-conf is not supported with legacy AAPT.");
System.exit(1);
}

// try and build apk
try {
if (cli.hasOption("a") || cli.hasOption("aapt")) {
config.aaptVersion = AaptManager.getAaptVersion(cli.getOptionValue("a"));
}
new ApkBuilder(config, new ExtFile(appDirName)).build(outFile);
} catch (BrutException ex) {
System.err.println(ex.getMessage());
System.exit(1);
}
ExtFile apkDir = new ExtFile(apkDirName);
ApkBuilder builder = new ApkBuilder(apkDir, config);
builder.build(outFile);
}

private static void cmdInstallFramework(CommandLine cli, Config config) throws AndrolibException {
Expand Down Expand Up @@ -462,13 +478,13 @@ private static void _options() {
.build();

Option aapt1Option = Option.builder()
.longOpt("use-aapt1")
.desc("Use aapt binary instead of aapt2 during the build step.")
.build();
.longOpt("use-aapt1")
.desc("Use aapt binary instead of aapt2 during the build step.")
.build();

Option aapt2Option = Option.builder()
.longOpt("use-aapt2")
.desc("Use aapt2 binary instead of aapt during the build step.")
.desc("Use aapt2 binary instead of aapt during the build step. (default)")
.build();

Option originalOption = Option.builder("c")
Expand All @@ -481,6 +497,11 @@ private static void _options() {
.desc("Disable crunching of resource files during the build step.")
.build();

Option noApkOption = Option.builder("na")
.longOpt("no-apk")
.desc("Disable repacking of the built files into a new apk.")
.build();

Option tagOption = Option.builder("t")
.longOpt("tag")
.desc("Tag frameworks using <tag>.")
Expand Down Expand Up @@ -530,6 +551,7 @@ private static void _options() {
buildOptions.addOption(originalOption);
buildOptions.addOption(aapt1Option);
buildOptions.addOption(noCrunchOption);
buildOptions.addOption(noApkOption);
}

// add global options
Expand Down Expand Up @@ -591,6 +613,7 @@ private static void _options() {
allOptions.addOption(aapt1Option);
allOptions.addOption(aapt2Option);
allOptions.addOption(noCrunchOption);
allOptions.addOption(noApkOption);
allOptions.addOption(onlyMainClassesOption);
}

Expand Down Expand Up @@ -675,8 +698,8 @@ public String format(LogRecord record) {
}
}
}
} catch (Exception exception) {
reportError(null, exception, ErrorManager.FORMAT_FAILURE);
} catch (Exception ex) {
reportError(null, ex, ErrorManager.FORMAT_FAILURE);
}
}

Expand Down
114 changes: 27 additions & 87 deletions brut.apktool/apktool-lib/src/main/java/brut/androlib/AaptInvoker.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,39 +39,39 @@ public AaptInvoker(Config config, ApkInfo apkInfo) {

private File getAaptBinaryFile() throws AndrolibException {
try {
if (getAaptVersion() == 2) {
return AaptManager.getAapt2();
switch (mConfig.aaptVersion) {
case 2:
return AaptManager.getAapt2();
default:
return AaptManager.getAapt1();
}
return AaptManager.getAapt1();
} catch (BrutException ex) {
throw new AndrolibException(ex);
}
}

private int getAaptVersion() {
return mConfig.isAapt2() ? 2 : 1;
}
public void invokeAapt(File apkFile, File manifest, File resDir, File rawDir, File assetDir, File[] include)
throws AndrolibException {

private File createDoNotCompressExtensionsFile(ApkInfo apkInfo) throws AndrolibException {
if (apkInfo.doNotCompress == null || apkInfo.doNotCompress.isEmpty()) {
return null;
}
String aaptPath = mConfig.aaptPath;
boolean customAapt = !aaptPath.isEmpty();
List<String> cmd = new ArrayList<>();

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();
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(mConfig.aaptVersion));
}

return doNotCompressFile;
} catch (IOException ex) {
throw new AndrolibException(ex);
switch (mConfig.aaptVersion) {
case 2:
invokeAapt2(apkFile, manifest, resDir, rawDir, assetDir, include, cmd, customAapt);
break;
default:
invokeAapt1(apkFile, manifest, resDir, rawDir, assetDir, include, cmd, customAapt);
break;
}
}

Expand Down Expand Up @@ -131,7 +131,9 @@ private void invokeAapt2(File apkFile, File manifest, File resDir, File rawDir,
cmd.add("-o");
cmd.add(apkFile.getAbsolutePath());

if (mApkInfo.packageInfo.forcedPackageId != null && ! mApkInfo.sharedLibrary) {
if (mApkInfo.packageInfo.forcedPackageId != null && !mApkInfo.packageInfo.forcedPackageId.equals("1")
&& !mApkInfo.sharedLibrary) {
cmd.add("--allow-reserved-package-id");
cmd.add("--package-id");
cmd.add(mApkInfo.packageInfo.forcedPackageId);
}
Expand Down Expand Up @@ -174,8 +176,6 @@ private void invokeAapt2(File apkFile, File manifest, File resDir, File rawDir,
cmd.add("--no-version-transitions");
cmd.add("--no-resource-deduping");

cmd.add("--allow-reserved-package-id");

cmd.add("--no-compile-sdk-metadata");

// #3427 - Ignore stricter parsing during aapt2
Expand All @@ -189,25 +189,6 @@ private void invokeAapt2(File apkFile, File manifest, File resDir, File rawDir,
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");
Expand Down Expand Up @@ -264,7 +245,7 @@ private void invokeAapt1(File apkFile, File manifest, File resDir, File rawDir,
}
// 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) {
if (mApkInfo.packageInfo.forcedPackageId != null && !mApkInfo.sharedLibrary && !customAapt) {
cmd.add("--forced-package-id");
cmd.add(mApkInfo.packageInfo.forcedPackageId);
}
Expand Down Expand Up @@ -311,25 +292,6 @@ private void invokeAapt1(File apkFile, File manifest, File resDir, File rawDir,
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");
Expand Down Expand Up @@ -359,26 +321,4 @@ private void invokeAapt1(File apkFile, File manifest, File resDir, File rawDir,
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);
}
}
Loading