mirror of
https://github.com/revanced/Apktool.git
synced 2025-04-29 21:54:25 +02:00

* refactor: ExtDataInput rework, source layout and formatting Refactor ExtDataInput classes: ExtDataInput is now the extended interface, ExtDataInputStream is an easy-to-use FilterInputStream implementing ExtDataInput with static creator methods for big-endian and little-endian wrappers. Refactor AaptManager class: unify aapt-related verifications to one class. Replace Apache Commons' deprecated CountingInputStream with Google Guava's equivalent with the same name. Apache's BoundedInputStream is an overkill for our use case and its constructors are deprecated as well. Normalize source layout to have a common and somewhat more standard order: Static fields first, instance fields after, methods last. Fix some formatting, like empty spaces or extra spaces and exception messages. Renamed ResXmlPatcher to ResXmlUtils, as it has more purposes than just patching. Renamed DirUtil to DirUtils, to match other utility classes naming convention. Moved "properties/apktool.properties" to jar's root, to match smali/baksmali. Moved Android Framework to "prebuilt", as it is just a prebuilt, looks out of place among .class files. @SuppressWarnings removed from Duo as there are quite a few unsafe assignments of raw Duo[] instances to parameterized Duo<> variables in the project, this is just Java being the primitive boilerplate it is, no point in fighting it. No end-user changes. Tested against a full ROM decompile/recompile, no issues found. * small tweak * last refinement * missed a stream
331 lines
11 KiB
Java
331 lines
11 KiB
Java
/*
|
|
* 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.apk.ApkInfo;
|
|
import brut.androlib.exceptions.AndrolibException;
|
|
import brut.common.BrutException;
|
|
import brut.util.AaptManager;
|
|
import brut.util.OS;
|
|
|
|
import java.io.*;
|
|
import java.util.*;
|
|
import java.util.logging.Logger;
|
|
|
|
public class AaptInvoker {
|
|
private static final Logger LOGGER = Logger.getLogger(AaptInvoker.class.getName());
|
|
|
|
private final Config mConfig;
|
|
private final ApkInfo mApkInfo;
|
|
|
|
public AaptInvoker(Config config, ApkInfo apkInfo) {
|
|
mConfig = config;
|
|
mApkInfo = apkInfo;
|
|
}
|
|
|
|
public void invoke(File apkFile, File manifest, File resDir, File rawDir, File assetDir, File[] include)
|
|
throws AndrolibException {
|
|
File aaptBinary = mConfig.aaptBinary;
|
|
|
|
List<String> cmd = new ArrayList<>();
|
|
String aaptPath;
|
|
boolean customAapt;
|
|
|
|
if (mConfig.aaptBinary != null) {
|
|
aaptPath = mConfig.aaptBinary.getPath();
|
|
customAapt = true;
|
|
} else {
|
|
try {
|
|
aaptPath = AaptManager.getAaptBinary(mConfig.aaptVersion).getPath();
|
|
customAapt = false;
|
|
} catch (BrutException ex) {
|
|
aaptPath = AaptManager.getAaptName(mConfig.aaptVersion);
|
|
customAapt = true;
|
|
LOGGER.warning(aaptPath + ": " + ex.getMessage() + " (defaulting to $PATH binary)");
|
|
}
|
|
}
|
|
|
|
cmd.add(aaptPath);
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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.packageInfo.forcedPackageId.equals("1")
|
|
&& !mApkInfo.sharedLibrary) {
|
|
cmd.add("--allow-reserved-package-id");
|
|
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("--no-compile-sdk-metadata");
|
|
|
|
// #3427 - Ignore stricter parsing during aapt2
|
|
cmd.add("--warn-manifest-validation");
|
|
|
|
if (mApkInfo.sparseResources) {
|
|
cmd.add("--enable-sparse-encoding");
|
|
}
|
|
|
|
if (mApkInfo.compactEntries) {
|
|
cmd.add("--enable-compact-entries");
|
|
}
|
|
|
|
if (mApkInfo.isFrameworkApk) {
|
|
cmd.add("-x");
|
|
}
|
|
|
|
if (!mApkInfo.featureFlags.isEmpty()) {
|
|
List<String> featureFlags = new ArrayList<>();
|
|
for (Map.Entry<String, Boolean> entry : mApkInfo.featureFlags.entrySet()) {
|
|
featureFlags.add(entry.getKey() + "=" + entry.getValue());
|
|
}
|
|
cmd.add("--feature-flags");
|
|
cmd.add(String.join(",", featureFlags));
|
|
}
|
|
|
|
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 && !mApkInfo.sharedLibrary && !customAapt) {
|
|
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 (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);
|
|
}
|
|
}
|
|
}
|