refactor: convert meta classes to pojo, rework ARSC & rework cli parsing (#3868)
* refactor: convert meta classes to pojo Wanted to make that change back when I converted Config class to POJO, finishing the job now. This will guarantee safe access to all meta classes (moved from brut.androlib.apk to brut.androlib.meta to make more sense). All non-primitive fields are guaranteed to never be null. Outputs clean apktool.yml without useless fields that don't affect the rebuild procedure. Still backwards compatible, just cleaner output, especially for JARs. Cleaned some logic in a few classes, especially related to updateApkInfo and loading of libraries/frameworks. It was a mess that was difficult to make sense of. Test issue1235 removed, it's a aapt1 duplicate of aapt2's issue2328/debuggable-false. Misc style tweaks for consistency. Tested on a ROM decompile/recompile, no regression detected. * fix accessors * fix flaw in net sec conf * refactor ResConfigFlags and isInvalid logic * git windows case issue (1) * git windows case issue (2) * style * checker, wake up * reorganize some classes and minimize reading signed data from arsc Move: * brut.util.AaptManager -> brut.androlib.AaptManager * brut.androlib.BackgroundWorker -> brut.util.BackgroundWorker * brut.androlib.meta.Yaml* -> brut.yaml Removed useless "throws" on signatures of Yaml functions, none of the Yaml functions throw any managed exceptions, it's a perfectly generic set of classes that deserves a dedicated module. Most of data in arsc is encoded as unsigned integers, so normally we'd use readUnsignedByte/Short where possible (both return int) to ensure the values are never negative. Java doesn't have any valid unsigned counterpart for readInt, so we'll have to live with it, it works for now. Java was never good for dealing with raw binary data. * remove useless semicolon * cases ordered by value * comment correction * unneeded import * use readBytes for clarity * config options larping as flags * config options larping as flags (2) * refactor: command line parsing and Config Make command line commands and options great again. Each command has its own valid options, verified properly like any good CLI application. Instead of checking hasOption(shortName/longName), we do hasOption(Option) on the option object, which maintains sanity and very easy to manage. "allOptions" collection is no longer needed.
@@ -28,6 +28,7 @@ dependencies {
|
||||
api(project(":brut.j.util"))
|
||||
api(project(":brut.j.dir"))
|
||||
api(project(":brut.j.xml"))
|
||||
api(project(":brut.j.yaml"))
|
||||
|
||||
implementation(libs.baksmali)
|
||||
implementation(libs.smali)
|
||||
|
||||
@@ -87,37 +87,37 @@ public class TypedValue {
|
||||
* Identifies the start of integer values that were specified as color
|
||||
* constants (starting with '#').
|
||||
*/
|
||||
public static final int TYPE_FIRST_COLOR_INT = 0x1c;
|
||||
public static final int TYPE_FIRST_COLOR_INT = 0x1C;
|
||||
|
||||
/**
|
||||
* The <var>data</var> field holds a color that was originally specified as
|
||||
* #aarrggbb.
|
||||
*/
|
||||
public static final int TYPE_INT_COLOR_ARGB8 = 0x1c;
|
||||
public static final int TYPE_INT_COLOR_ARGB8 = 0x1C;
|
||||
/**
|
||||
* The <var>data</var> field holds a color that was originally specified as
|
||||
* #rrggbb.
|
||||
*/
|
||||
public static final int TYPE_INT_COLOR_RGB8 = 0x1d;
|
||||
public static final int TYPE_INT_COLOR_RGB8 = 0x1D;
|
||||
/**
|
||||
* The <var>data</var> field holds a color that was originally specified as
|
||||
* #argb.
|
||||
*/
|
||||
public static final int TYPE_INT_COLOR_ARGB4 = 0x1e;
|
||||
public static final int TYPE_INT_COLOR_ARGB4 = 0x1E;
|
||||
/**
|
||||
* The <var>data</var> field holds a color that was originally specified as
|
||||
* #rgb.
|
||||
*/
|
||||
public static final int TYPE_INT_COLOR_RGB4 = 0x1f;
|
||||
public static final int TYPE_INT_COLOR_RGB4 = 0x1F;
|
||||
|
||||
/**
|
||||
* Identifies the end of integer values that were specified as color
|
||||
* constants.
|
||||
*/
|
||||
public static final int TYPE_LAST_COLOR_INT = 0x1f;
|
||||
public static final int TYPE_LAST_COLOR_INT = 0x1F;
|
||||
|
||||
/** Identifies the end of plain integer values. */
|
||||
public static final int TYPE_LAST_INT = 0x1f;
|
||||
public static final int TYPE_LAST_INT = 0x1F;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
@@ -128,7 +128,7 @@ public class TypedValue {
|
||||
* {@link #COMPLEX_UNIT_SHIFT}). This gives us 16 possible types, as defined
|
||||
* below.
|
||||
*/
|
||||
public static final int COMPLEX_UNIT_MASK = 0xf;
|
||||
public static final int COMPLEX_UNIT_MASK = 0xF;
|
||||
|
||||
/** {@link #TYPE_DIMENSION} complex unit: Value is raw pixels. */
|
||||
public static final int COMPLEX_UNIT_PX = 0;
|
||||
@@ -180,7 +180,7 @@ public class TypedValue {
|
||||
* {@link #COMPLEX_MANTISSA_SHIFT}). This gives us 23 bits of precision; the
|
||||
* top bit is the sign.
|
||||
*/
|
||||
public static final int COMPLEX_MANTISSA_MASK = 0xffffff;
|
||||
public static final int COMPLEX_MANTISSA_MASK = 0xFFFFFF;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
@@ -205,7 +205,7 @@ public class TypedValue {
|
||||
* If density is equal to this value, then there is no density
|
||||
* associated with the resource and it should not be scaled.
|
||||
*/
|
||||
public static final int DENSITY_NONE = 0xffff;
|
||||
public static final int DENSITY_NONE = 0xFFFF;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
||||
@@ -288,12 +288,10 @@ public class TypedValue {
|
||||
res = res.substring(2);
|
||||
break;
|
||||
case TYPE_INT_COLOR_ARGB4:// #AARRGGBB->#ARGB
|
||||
res = String.valueOf(vals[0]) + vals[2] +
|
||||
vals[4] + vals[6];
|
||||
res = new String(new char[] { vals[0], vals[2], vals[4], vals[6] });
|
||||
break;
|
||||
case TYPE_INT_COLOR_RGB4:// #FFRRGGBB->#RGB
|
||||
res = String.valueOf(vals[2]) + vals[4] +
|
||||
vals[6];
|
||||
res = new String(new char[] { vals[2], vals[4], vals[6] });
|
||||
break;
|
||||
}
|
||||
return "#" + res;
|
||||
|
||||
@@ -16,10 +16,9 @@
|
||||
*/
|
||||
package brut.androlib;
|
||||
|
||||
import brut.androlib.apk.ApkInfo;
|
||||
import brut.androlib.exceptions.AndrolibException;
|
||||
import brut.androlib.meta.ApkInfo;
|
||||
import brut.common.BrutException;
|
||||
import brut.util.AaptManager;
|
||||
import brut.util.OS;
|
||||
|
||||
import java.io.*;
|
||||
@@ -50,7 +49,7 @@ public class AaptInvoker {
|
||||
try {
|
||||
aaptPath = AaptManager.getAaptBinary(mConfig.getAaptVersion()).getPath();
|
||||
customAapt = false;
|
||||
} catch (BrutException ex) {
|
||||
} catch (AndrolibException ex) {
|
||||
aaptPath = AaptManager.getAaptName(mConfig.getAaptVersion());
|
||||
customAapt = true;
|
||||
LOGGER.warning(aaptPath + ": " + ex.getMessage() + " (defaulting to $PATH binary)");
|
||||
@@ -120,40 +119,40 @@ public class AaptInvoker {
|
||||
cmd.add("-o");
|
||||
cmd.add(apkFile.getAbsolutePath());
|
||||
|
||||
if (mApkInfo.packageInfo.forcedPackageId != null) {
|
||||
int pkgId = Integer.parseInt(mApkInfo.packageInfo.forcedPackageId);
|
||||
if (mApkInfo.getPackageInfo().getForcedPackageId() != null) {
|
||||
int pkgId = Integer.parseInt(mApkInfo.getPackageInfo().getForcedPackageId());
|
||||
if (pkgId == 0) {
|
||||
cmd.add("--shared-lib");
|
||||
} else if (pkgId > 1) {
|
||||
cmd.add("--package-id");
|
||||
cmd.add(mApkInfo.packageInfo.forcedPackageId);
|
||||
if (pkgId < 0x7f) {
|
||||
cmd.add(Integer.toString(pkgId));
|
||||
if (pkgId < 0x7F) {
|
||||
cmd.add("--allow-reserved-package-id");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mApkInfo.getMinSdkVersion() != null) {
|
||||
if (mApkInfo.getSdkInfo().getMinSdkVersion() != null) {
|
||||
cmd.add("--min-sdk-version");
|
||||
cmd.add(mApkInfo.getMinSdkVersion());
|
||||
cmd.add(mApkInfo.getSdkInfo().getMinSdkVersion());
|
||||
}
|
||||
if (mApkInfo.getTargetSdkVersion() != null) {
|
||||
if (mApkInfo.getSdkInfo().getTargetSdkVersion() != null) {
|
||||
cmd.add("--target-sdk-version");
|
||||
cmd.add(mApkInfo.checkTargetSdkVersionBounds());
|
||||
cmd.add(mApkInfo.getSdkInfo().getTargetSdkVersion());
|
||||
}
|
||||
if (mApkInfo.packageInfo.renameManifestPackage != null) {
|
||||
if (mApkInfo.getPackageInfo().getRenameManifestPackage() != null) {
|
||||
cmd.add("--rename-manifest-package");
|
||||
cmd.add(mApkInfo.packageInfo.renameManifestPackage);
|
||||
cmd.add(mApkInfo.getPackageInfo().getRenameManifestPackage());
|
||||
|
||||
cmd.add("--rename-instrumentation-target-package");
|
||||
cmd.add(mApkInfo.packageInfo.renameManifestPackage);
|
||||
cmd.add(mApkInfo.getPackageInfo().getRenameManifestPackage());
|
||||
}
|
||||
if (mApkInfo.versionInfo.versionCode != null) {
|
||||
if (mApkInfo.getVersionInfo().getVersionCode() != null) {
|
||||
cmd.add("--version-code");
|
||||
cmd.add(mApkInfo.versionInfo.versionCode);
|
||||
cmd.add(mApkInfo.getVersionInfo().getVersionCode());
|
||||
}
|
||||
if (mApkInfo.versionInfo.versionName != null) {
|
||||
if (mApkInfo.getVersionInfo().getVersionName() != null) {
|
||||
cmd.add("--version-name");
|
||||
cmd.add(mApkInfo.versionInfo.versionName);
|
||||
cmd.add(mApkInfo.getVersionInfo().getVersionName());
|
||||
}
|
||||
|
||||
// Disable automatic changes
|
||||
@@ -166,15 +165,15 @@ public class AaptInvoker {
|
||||
// #3427 - Ignore stricter parsing during aapt2
|
||||
cmd.add("--warn-manifest-validation");
|
||||
|
||||
if (mApkInfo.sparseResources) {
|
||||
if (mApkInfo.isSparseResources()) {
|
||||
cmd.add("--enable-sparse-encoding");
|
||||
}
|
||||
if (mApkInfo.compactEntries) {
|
||||
if (mApkInfo.isCompactEntries()) {
|
||||
cmd.add("--enable-compact-entries");
|
||||
}
|
||||
if (!mApkInfo.featureFlags.isEmpty()) {
|
||||
if (!mApkInfo.getFeatureFlags().isEmpty()) {
|
||||
List<String> featureFlags = new ArrayList<>();
|
||||
for (Map.Entry<String, Boolean> entry : mApkInfo.featureFlags.entrySet()) {
|
||||
for (Map.Entry<String, Boolean> entry : mApkInfo.getFeatureFlags().entrySet()) {
|
||||
featureFlags.add(entry.getKey() + "=" + entry.getValue());
|
||||
}
|
||||
cmd.add("--feature-flags");
|
||||
@@ -223,60 +222,54 @@ public class AaptInvoker {
|
||||
if (mConfig.isVerbose()) { // output aapt verbose
|
||||
cmd.add("-v");
|
||||
}
|
||||
if (mConfig.isUpdateFiles()) {
|
||||
cmd.add("-u");
|
||||
}
|
||||
if (mConfig.isDebugMode()) { // inject debuggable="true" into manifest
|
||||
cmd.add("--debug-mode");
|
||||
}
|
||||
if (mConfig.isNoCrunch()) {
|
||||
cmd.add("--no-crunch");
|
||||
}
|
||||
if (mApkInfo.packageInfo.forcedPackageId != null) {
|
||||
int pkgId = Integer.parseInt(mApkInfo.packageInfo.forcedPackageId);
|
||||
if (mApkInfo.getPackageInfo().getForcedPackageId() != null) {
|
||||
int pkgId = Integer.parseInt(mApkInfo.getPackageInfo().getForcedPackageId());
|
||||
if (pkgId == 0) {
|
||||
cmd.add("--shared-lib");
|
||||
} else if (pkgId > 0) {
|
||||
if (!customAapt) { // custom aapt might not have this option
|
||||
cmd.add("--forced-package-id");
|
||||
cmd.add(mApkInfo.packageInfo.forcedPackageId);
|
||||
cmd.add(Integer.toString(pkgId));
|
||||
}
|
||||
if (pkgId < 0x7f) {
|
||||
if (pkgId < 0x7F) {
|
||||
cmd.add("-x");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mApkInfo.getMinSdkVersion() != null) {
|
||||
if (mApkInfo.getSdkInfo().getMinSdkVersion() != null) {
|
||||
cmd.add("--min-sdk-version");
|
||||
cmd.add(mApkInfo.getMinSdkVersion());
|
||||
cmd.add(mApkInfo.getSdkInfo().getMinSdkVersion());
|
||||
}
|
||||
if (mApkInfo.getTargetSdkVersion() != null) {
|
||||
if (mApkInfo.getSdkInfo().getTargetSdkVersion() != null) {
|
||||
cmd.add("--target-sdk-version");
|
||||
|
||||
// Ensure that targetSdkVersion is between minSdkVersion/maxSdkVersion if
|
||||
// they are specified.
|
||||
cmd.add(mApkInfo.checkTargetSdkVersionBounds());
|
||||
cmd.add(mApkInfo.getSdkInfo().getTargetSdkVersionBounded());
|
||||
}
|
||||
if (mApkInfo.getMaxSdkVersion() != null) {
|
||||
if (mApkInfo.getSdkInfo().getMaxSdkVersion() != null) {
|
||||
cmd.add("--max-sdk-version");
|
||||
cmd.add(mApkInfo.getMaxSdkVersion());
|
||||
cmd.add(mApkInfo.getSdkInfo().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());
|
||||
cmd.add(mApkInfo.getSdkInfo().getMaxSdkVersion());
|
||||
}
|
||||
if (mApkInfo.packageInfo.renameManifestPackage != null) {
|
||||
if (mApkInfo.getPackageInfo().getRenameManifestPackage() != null) {
|
||||
cmd.add("--rename-manifest-package");
|
||||
cmd.add(mApkInfo.packageInfo.renameManifestPackage);
|
||||
cmd.add(mApkInfo.getPackageInfo().getRenameManifestPackage());
|
||||
}
|
||||
if (mApkInfo.versionInfo.versionCode != null) {
|
||||
if (mApkInfo.getVersionInfo().getVersionCode() != null) {
|
||||
cmd.add("--version-code");
|
||||
cmd.add(mApkInfo.versionInfo.versionCode);
|
||||
cmd.add(mApkInfo.getVersionInfo().getVersionCode());
|
||||
}
|
||||
if (mApkInfo.versionInfo.versionName != null) {
|
||||
if (mApkInfo.getVersionInfo().getVersionName() != null) {
|
||||
cmd.add("--version-name");
|
||||
cmd.add(mApkInfo.versionInfo.versionName);
|
||||
cmd.add(mApkInfo.getVersionInfo().getVersionName());
|
||||
}
|
||||
cmd.add("--no-version-vectors");
|
||||
cmd.add("-F");
|
||||
|
||||
@@ -14,9 +14,13 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package brut.util;
|
||||
package brut.androlib;
|
||||
|
||||
import brut.androlib.exceptions.AndrolibException;
|
||||
import brut.common.BrutException;
|
||||
import brut.util.Jar;
|
||||
import brut.util.OS;
|
||||
import brut.util.OSDetection;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
@@ -37,11 +41,12 @@ public final class AaptManager {
|
||||
}
|
||||
}
|
||||
|
||||
public static File getAaptBinary(int version) throws BrutException {
|
||||
public static File getAaptBinary(int version) throws AndrolibException {
|
||||
String aaptName = getAaptName(version);
|
||||
|
||||
if (!OSDetection.is64Bit() && OSDetection.isMacOSX()) {
|
||||
throw new BrutException(aaptName + " binary is not available for 32-bit platform: " + OSDetection.returnOS());
|
||||
throw new AndrolibException(
|
||||
aaptName + " binary is not available for 32-bit platform: " + OSDetection.returnOS());
|
||||
}
|
||||
|
||||
StringBuilder aaptPath = new StringBuilder("/prebuilt/");
|
||||
@@ -52,7 +57,7 @@ public final class AaptManager {
|
||||
} else if (OSDetection.isWindows()) {
|
||||
aaptPath.append("windows");
|
||||
} else {
|
||||
throw new BrutException("Could not identify platform: " + OSDetection.returnOS());
|
||||
throw new AndrolibException("Could not identify platform: " + OSDetection.returnOS());
|
||||
}
|
||||
aaptPath.append("/");
|
||||
aaptPath.append(aaptName);
|
||||
@@ -63,12 +68,17 @@ public final class AaptManager {
|
||||
aaptPath.append(".exe");
|
||||
}
|
||||
|
||||
File aaptBinary = Jar.getResourceAsFile(AaptManager.class, aaptPath.toString());
|
||||
File aaptBinary;
|
||||
try {
|
||||
aaptBinary = Jar.getResourceAsFile(AaptManager.class, aaptPath.toString());
|
||||
} catch (BrutException ex) {
|
||||
throw new AndrolibException(ex);
|
||||
}
|
||||
setAaptBinaryExecutable(aaptBinary);
|
||||
return aaptBinary;
|
||||
}
|
||||
|
||||
public static int getAaptVersion(File aaptBinary) throws BrutException {
|
||||
public static int getAaptVersion(File aaptBinary) throws AndrolibException {
|
||||
setAaptBinaryExecutable(aaptBinary);
|
||||
|
||||
List<String> cmd = new ArrayList<>();
|
||||
@@ -77,13 +87,13 @@ public final class AaptManager {
|
||||
|
||||
String versionStr = OS.execAndReturn(cmd.toArray(new String[0]));
|
||||
if (versionStr == null) {
|
||||
throw new BrutException("Could not execute aapt binary at location: " + aaptBinary.getPath());
|
||||
throw new AndrolibException("Could not execute aapt binary at location: " + aaptBinary.getPath());
|
||||
}
|
||||
|
||||
return getAaptVersionFromString(versionStr);
|
||||
}
|
||||
|
||||
public static int getAaptVersionFromString(String versionStr) throws BrutException {
|
||||
public static int getAaptVersionFromString(String versionStr) throws AndrolibException {
|
||||
if (versionStr.startsWith("Android Asset Packaging Tool (aapt) 2:")) {
|
||||
return 2;
|
||||
} else if (versionStr.startsWith("Android Asset Packaging Tool (aapt) 2.")) {
|
||||
@@ -92,15 +102,15 @@ public final class AaptManager {
|
||||
return 1;
|
||||
}
|
||||
|
||||
throw new BrutException("aapt version could not be identified: " + versionStr);
|
||||
throw new AndrolibException("aapt version could not be identified: " + versionStr);
|
||||
}
|
||||
|
||||
private static void setAaptBinaryExecutable(File aaptBinary) throws BrutException {
|
||||
private static void setAaptBinaryExecutable(File aaptBinary) throws AndrolibException {
|
||||
if (!aaptBinary.isFile() || !aaptBinary.canRead()) {
|
||||
throw new BrutException("Could not read aapt binary: " + aaptBinary.getPath());
|
||||
throw new AndrolibException("Could not read aapt binary: " + aaptBinary.getPath());
|
||||
}
|
||||
if (!aaptBinary.setExecutable(true)) {
|
||||
throw new BrutException("Could not set aapt binary as executable: " + aaptBinary.getPath());
|
||||
throw new AndrolibException("Could not set aapt binary as executable: " + aaptBinary.getPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,9 @@
|
||||
package brut.androlib;
|
||||
|
||||
import brut.androlib.exceptions.AndrolibException;
|
||||
import brut.androlib.apk.ApkInfo;
|
||||
import brut.androlib.apk.UsesFramework;
|
||||
import brut.androlib.meta.ApkInfo;
|
||||
import brut.androlib.meta.SdkInfo;
|
||||
import brut.androlib.meta.UsesFramework;
|
||||
import brut.androlib.res.Framework;
|
||||
import brut.androlib.res.data.ResConfigFlags;
|
||||
import brut.androlib.res.xml.ResXmlUtils;
|
||||
@@ -31,7 +32,7 @@ import brut.directory.Directory;
|
||||
import brut.directory.DirectoryException;
|
||||
import brut.directory.ExtFile;
|
||||
import brut.directory.ZipUtils;
|
||||
import brut.util.AaptManager;
|
||||
import brut.util.BackgroundWorker;
|
||||
import brut.util.BrutIO;
|
||||
import brut.util.OS;
|
||||
|
||||
@@ -66,17 +67,19 @@ public class ApkBuilder {
|
||||
try {
|
||||
mApkInfo = ApkInfo.load(mApkDir);
|
||||
|
||||
String minSdkVersion = mApkInfo.getMinSdkVersion();
|
||||
String minSdkVersion = mApkInfo.getSdkInfo().getMinSdkVersion();
|
||||
if (minSdkVersion != null) {
|
||||
mMinSdkVersion = mApkInfo.getMinSdkVersionFromAndroidCodename(minSdkVersion);
|
||||
mMinSdkVersion = SdkInfo.parseSdkInt(minSdkVersion);
|
||||
}
|
||||
|
||||
if (outApk == null) {
|
||||
String outFileName = mApkInfo.apkFileName;
|
||||
if (outFileName == null) {
|
||||
outFileName = "out.apk";
|
||||
}
|
||||
outApk = new File(mApkDir, "dist/" + outFileName);
|
||||
String apkName = mApkInfo.getApkFileName();
|
||||
if (apkName == null) {
|
||||
apkName = "out.apk";
|
||||
}
|
||||
if (mConfig.isNoApk()) {
|
||||
outApk = null;
|
||||
} else if (outApk == null) {
|
||||
outApk = new File(mApkDir, "dist/" + apkName);
|
||||
}
|
||||
|
||||
File outDir = new File(mApkDir, "build/apk");
|
||||
@@ -85,7 +88,7 @@ public class ApkBuilder {
|
||||
File manifest = new File(mApkDir, "AndroidManifest.xml");
|
||||
File manifestOrig = new File(mApkDir, "AndroidManifest.xml.orig");
|
||||
|
||||
LOGGER.info("Using Apktool " + ApktoolProperties.getVersion() + " on " + outApk.getName()
|
||||
LOGGER.info("Using Apktool " + ApktoolProperties.getVersion() + " on " + apkName
|
||||
+ (mWorker != null ? " with " + mConfig.getJobs() + " threads" : ""));
|
||||
|
||||
buildSources(outDir);
|
||||
@@ -99,7 +102,7 @@ public class ApkBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
if (!mConfig.isNoApk()) {
|
||||
if (outApk != null) {
|
||||
if (outApk.exists()) {
|
||||
OS.rmfile(outApk);
|
||||
} else {
|
||||
@@ -116,7 +119,7 @@ public class ApkBuilder {
|
||||
try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(outApk.toPath()))) {
|
||||
// zip aapt output files
|
||||
try {
|
||||
ZipUtils.zipDir(outDir, out, mApkInfo.doNotCompress);
|
||||
ZipUtils.zipDir(outDir, out, mApkInfo.getDoNotCompress());
|
||||
} catch (IOException ex) {
|
||||
throw new AndrolibException(ex);
|
||||
}
|
||||
@@ -186,7 +189,7 @@ public class ApkBuilder {
|
||||
}
|
||||
|
||||
File stored = new File(outDir, fileName);
|
||||
if (!mConfig.isForceBuildAll() && !isModified(working, stored)) {
|
||||
if (!mConfig.isForced() && !isModified(working, stored)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -222,7 +225,7 @@ public class ApkBuilder {
|
||||
}
|
||||
|
||||
File dex = new File(outDir, fileName);
|
||||
if (!mConfig.isForceBuildAll()) {
|
||||
if (!mConfig.isForced()) {
|
||||
LOGGER.info("Checking whether sources have changed...");
|
||||
if (!isModified(smaliDir, dex)) {
|
||||
return;
|
||||
@@ -231,8 +234,7 @@ public class ApkBuilder {
|
||||
OS.rmfile(dex);
|
||||
|
||||
LOGGER.info("Smaling " + dirName + " folder into " + fileName + "...");
|
||||
int apiLevel = mConfig.getApiLevel() > 0 ? mConfig.getApiLevel() : mMinSdkVersion;
|
||||
SmaliBuilder builder = new SmaliBuilder(smaliDir, apiLevel);
|
||||
SmaliBuilder builder = new SmaliBuilder(smaliDir, mMinSdkVersion);
|
||||
builder.build(dex);
|
||||
}
|
||||
|
||||
@@ -273,7 +275,7 @@ public class ApkBuilder {
|
||||
}
|
||||
|
||||
private void copyResourcesRaw(File outDir, File manifest) throws AndrolibException {
|
||||
if (!mConfig.isForceBuildAll()) {
|
||||
if (!mConfig.isForced()) {
|
||||
LOGGER.info("Checking whether resources have changed...");
|
||||
if (!isModified(manifest, new File(outDir, "AndroidManifest.xml"))
|
||||
&& !isModified(new File(mApkDir, "resources.arsc"), new File(outDir, "resources.arsc"))
|
||||
@@ -297,7 +299,7 @@ public class ApkBuilder {
|
||||
|
||||
private void buildResourcesFull(File outDir, File manifest) throws AndrolibException {
|
||||
File resourcesFile = new File(outDir.getParentFile(), "resources.zip");
|
||||
if (!mConfig.isForceBuildAll()) {
|
||||
if (!mConfig.isForced()) {
|
||||
LOGGER.info("Checking whether resources have changed...");
|
||||
if (!isModified(manifest, new File(outDir, "AndroidManifest.xml"))
|
||||
&& !isModified(newFiles(mApkDir, ApkInfo.RESOURCES_DIRNAMES),
|
||||
@@ -318,14 +320,15 @@ public class ApkBuilder {
|
||||
}
|
||||
|
||||
if (mConfig.isNetSecConf()) {
|
||||
String targetSdkVersion = mApkInfo.getTargetSdkVersion();
|
||||
String targetSdkVersion = mApkInfo.getSdkInfo().getTargetSdkVersion();
|
||||
if (targetSdkVersion != null) {
|
||||
if (Integer.parseInt(targetSdkVersion) < ResConfigFlags.SDK_NOUGAT) {
|
||||
if (SdkInfo.parseSdkInt(targetSdkVersion) < ResConfigFlags.SDK_NOUGAT) {
|
||||
LOGGER.warning("Target SDK version is lower than 24! Network Security Configuration might be ignored!");
|
||||
}
|
||||
}
|
||||
|
||||
File netSecConfOrig = new File(mApkDir, "res/xml/network_security_config.xml");
|
||||
OS.mkdir(netSecConfOrig.getParentFile());
|
||||
ResXmlUtils.modNetworkSecurityConfig(netSecConfOrig);
|
||||
ResXmlUtils.setNetworkSecurityConfig(manifest);
|
||||
LOGGER.info("Added permissive network security config in manifest");
|
||||
@@ -363,7 +366,7 @@ public class ApkBuilder {
|
||||
}
|
||||
|
||||
private void buildManifest(File outDir, File manifest) throws AndrolibException {
|
||||
if (!mConfig.isForceBuildAll()) {
|
||||
if (!mConfig.isForced()) {
|
||||
LOGGER.info("Checking whether AndroidManifest.xml has changed...");
|
||||
if (!isModified(manifest, new File(outDir, "AndroidManifest.xml"))) {
|
||||
return;
|
||||
@@ -445,7 +448,7 @@ public class ApkBuilder {
|
||||
|
||||
LOGGER.info("Importing " + dirName + "...");
|
||||
try {
|
||||
ZipUtils.zipDir(mApkDir, dirName, out, mApkInfo.doNotCompress);
|
||||
ZipUtils.zipDir(mApkDir, dirName, out, mApkInfo.getDoNotCompress());
|
||||
} catch (IOException ex) {
|
||||
throw new AndrolibException(ex);
|
||||
}
|
||||
@@ -460,7 +463,7 @@ public class ApkBuilder {
|
||||
|
||||
LOGGER.info("Importing unknown files...");
|
||||
try {
|
||||
ZipUtils.zipDir(unknownDir, out, mApkInfo.doNotCompress);
|
||||
ZipUtils.zipDir(unknownDir, out, mApkInfo.getDoNotCompress());
|
||||
} catch (IOException ex) {
|
||||
throw new AndrolibException(ex);
|
||||
}
|
||||
@@ -469,26 +472,24 @@ public class ApkBuilder {
|
||||
private File[] getIncludeFiles() throws AndrolibException {
|
||||
List<File> files = new ArrayList<>();
|
||||
|
||||
UsesFramework usesFramework = mApkInfo.usesFramework;
|
||||
if (usesFramework != null) {
|
||||
List<Integer> ids = usesFramework.ids;
|
||||
if (ids != null) {
|
||||
Framework framework = new Framework(mConfig);
|
||||
String tag = usesFramework.tag;
|
||||
for (Integer id : ids) {
|
||||
files.add(framework.getApkFile(id, tag));
|
||||
}
|
||||
UsesFramework usesFramework = mApkInfo.getUsesFramework();
|
||||
List<Integer> frameworkIds = usesFramework.getIds();
|
||||
if (!frameworkIds.isEmpty()) {
|
||||
Framework framework = new Framework(mConfig);
|
||||
String tag = usesFramework.getTag();
|
||||
for (Integer id : frameworkIds) {
|
||||
files.add(framework.getApkFile(id, tag));
|
||||
}
|
||||
}
|
||||
|
||||
List<String> usesLibrary = mApkInfo.usesLibrary;
|
||||
if (usesLibrary != null) {
|
||||
List<String> usesLibrary = mApkInfo.getUsesLibrary();
|
||||
if (!usesLibrary.isEmpty()) {
|
||||
String[] libFiles = mConfig.getLibraryFiles();
|
||||
for (String name : usesLibrary) {
|
||||
File apkFile = null;
|
||||
if (libFiles != null) {
|
||||
for (String libName : libFiles) {
|
||||
String[] parts = libName.split(":", 2);
|
||||
for (String libEntry : libFiles) {
|
||||
String[] parts = libEntry.split(":", 2);
|
||||
if (parts.length == 2 && name.equals(parts[0])) {
|
||||
apkFile = new File(parts[1]);
|
||||
break;
|
||||
|
||||
@@ -19,13 +19,14 @@ package brut.androlib;
|
||||
import brut.androlib.exceptions.AndrolibException;
|
||||
import brut.androlib.exceptions.InFileNotFoundException;
|
||||
import brut.androlib.exceptions.OutDirExistsException;
|
||||
import brut.androlib.apk.ApkInfo;
|
||||
import brut.androlib.meta.ApkInfo;
|
||||
import brut.androlib.res.ResourcesDecoder;
|
||||
import brut.androlib.src.SmaliDecoder;
|
||||
import brut.directory.Directory;
|
||||
import brut.directory.ExtFile;
|
||||
import brut.common.BrutException;
|
||||
import brut.directory.Directory;
|
||||
import brut.directory.DirectoryException;
|
||||
import brut.directory.ExtFile;
|
||||
import brut.util.BackgroundWorker;
|
||||
import brut.util.OS;
|
||||
import com.android.tools.smali.dexlib2.iface.DexFile;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
@@ -60,7 +61,7 @@ public class ApkDecoder {
|
||||
}
|
||||
|
||||
public ApkInfo decode(File outDir) throws AndrolibException {
|
||||
if (!mConfig.isForceDelete() && outDir.exists()) {
|
||||
if (!mConfig.isForced() && outDir.exists()) {
|
||||
throw new OutDirExistsException();
|
||||
}
|
||||
if (!mApkFile.isFile() || !mApkFile.canRead()) {
|
||||
@@ -112,11 +113,11 @@ public class ApkDecoder {
|
||||
}
|
||||
|
||||
switch (mConfig.getDecodeSources()) {
|
||||
case Config.DECODE_SOURCES_NONE:
|
||||
case NONE:
|
||||
copySourcesRaw(outDir, "classes.dex");
|
||||
break;
|
||||
case Config.DECODE_SOURCES_SMALI:
|
||||
case Config.DECODE_SOURCES_SMALI_ONLY_MAIN_CLASSES:
|
||||
case FULL:
|
||||
case ONLY_MAIN_CLASSES:
|
||||
decodeSourcesSmali(outDir, "classes.dex");
|
||||
break;
|
||||
}
|
||||
@@ -128,13 +129,13 @@ public class ApkDecoder {
|
||||
for (String fileName : in.getFiles(true)) {
|
||||
if (fileName.endsWith(".dex") && !fileName.equals("classes.dex")) {
|
||||
switch (mConfig.getDecodeSources()) {
|
||||
case Config.DECODE_SOURCES_NONE:
|
||||
case NONE:
|
||||
copySourcesRaw(outDir, fileName);
|
||||
break;
|
||||
case Config.DECODE_SOURCES_SMALI:
|
||||
case FULL:
|
||||
decodeSourcesSmali(outDir, fileName);
|
||||
break;
|
||||
case Config.DECODE_SOURCES_SMALI_ONLY_MAIN_CLASSES:
|
||||
case ONLY_MAIN_CLASSES:
|
||||
if (fileName.startsWith("classes")) {
|
||||
decodeSourcesSmali(outDir, fileName);
|
||||
} else {
|
||||
@@ -189,7 +190,7 @@ public class ApkDecoder {
|
||||
|
||||
LOGGER.info("Baksmaling " + fileName + "...");
|
||||
SmaliDecoder decoder = new SmaliDecoder(mApkFile, fileName,
|
||||
mConfig.isBaksmaliDebugMode(), mConfig.getApiLevel());
|
||||
mConfig.isBaksmaliDebugMode(), mConfig.getBaksmaliApiLevel());
|
||||
DexFile dexFile = decoder.decode(smaliDir);
|
||||
|
||||
// record minSdkVersion for jars
|
||||
@@ -205,10 +206,11 @@ public class ApkDecoder {
|
||||
}
|
||||
|
||||
switch (mConfig.getDecodeResources()) {
|
||||
case Config.DECODE_RESOURCES_NONE:
|
||||
case NONE:
|
||||
case ONLY_MANIFEST:
|
||||
copyResourcesRaw(outDir);
|
||||
break;
|
||||
case Config.DECODE_RESOURCES_FULL:
|
||||
case FULL:
|
||||
mResDecoder.decodeResources(outDir);
|
||||
break;
|
||||
}
|
||||
@@ -231,11 +233,14 @@ public class ApkDecoder {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mConfig.getDecodeResources() == Config.DECODE_RESOURCES_FULL
|
||||
|| mConfig.getForceDecodeManifest() == Config.FORCE_DECODE_MANIFEST_FULL) {
|
||||
mResDecoder.decodeManifest(outDir);
|
||||
} else {
|
||||
copyManifestRaw(outDir);
|
||||
switch (mConfig.getDecodeResources()) {
|
||||
case NONE:
|
||||
copyManifestRaw(outDir);
|
||||
break;
|
||||
case FULL:
|
||||
case ONLY_MANIFEST:
|
||||
mResDecoder.decodeManifest(outDir);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,7 +260,7 @@ public class ApkDecoder {
|
||||
Directory in = mApkFile.getDirectory();
|
||||
|
||||
for (String dirName : ApkInfo.RAW_DIRNAMES) {
|
||||
if ((mConfig.getDecodeAssets() == Config.DECODE_ASSETS_FULL || !dirName.equals("assets"))
|
||||
if ((mConfig.getDecodeAssets() == Config.DecodeAssets.FULL || !dirName.equals("assets"))
|
||||
&& in.containsDir(dirName)) {
|
||||
LOGGER.info("Copying " + dirName + "...");
|
||||
for (String fileName : in.getDir(dirName).getFiles(true)) {
|
||||
@@ -304,11 +309,9 @@ public class ApkDecoder {
|
||||
}
|
||||
|
||||
private void writeApkInfo(File outDir) throws AndrolibException {
|
||||
mResDecoder.updateApkInfo(outDir);
|
||||
|
||||
// in case we have no resources, we should store the minSdk we pulled from the source opcode api level
|
||||
if (!mApkInfo.hasResources() && mMinSdkVersion > 0) {
|
||||
mApkInfo.setMinSdkVersion(Integer.toString(mMinSdkVersion));
|
||||
mApkInfo.getSdkInfo().setMinSdkVersion(Integer.toString(mMinSdkVersion));
|
||||
}
|
||||
|
||||
// record uncompressed files
|
||||
@@ -344,22 +347,16 @@ public class ApkDecoder {
|
||||
}
|
||||
|
||||
// update apk info
|
||||
int doNotCompressSize = uncompressedExts.size() + uncompressedFiles.size();
|
||||
if (doNotCompressSize > 0) {
|
||||
List<String> doNotCompress = new ArrayList<>(doNotCompressSize);
|
||||
if (!uncompressedExts.isEmpty()) {
|
||||
List<String> uncompressedExtsList = new ArrayList<>(uncompressedExts);
|
||||
uncompressedExtsList.sort(null);
|
||||
doNotCompress.addAll(uncompressedExtsList);
|
||||
}
|
||||
if (!uncompressedFiles.isEmpty()) {
|
||||
List<String> uncompressedFilesList = new ArrayList<>(uncompressedFiles);
|
||||
uncompressedFilesList.sort(null);
|
||||
doNotCompress.addAll(uncompressedFilesList);
|
||||
}
|
||||
if (!doNotCompress.isEmpty()) {
|
||||
mApkInfo.doNotCompress = doNotCompress;
|
||||
}
|
||||
List<String> doNotCompress = mApkInfo.getDoNotCompress();
|
||||
if (!uncompressedExts.isEmpty()) {
|
||||
List<String> uncompressedExtsList = new ArrayList<>(uncompressedExts);
|
||||
uncompressedExtsList.sort(null);
|
||||
doNotCompress.addAll(uncompressedExtsList);
|
||||
}
|
||||
if (!uncompressedFiles.isEmpty()) {
|
||||
List<String> uncompressedFilesList = new ArrayList<>(uncompressedFiles);
|
||||
uncompressedFilesList.sort(null);
|
||||
doNotCompress.addAll(uncompressedFilesList);
|
||||
}
|
||||
} catch (DirectoryException ex) {
|
||||
throw new AndrolibException(ex);
|
||||
|
||||
@@ -19,8 +19,8 @@ package brut.androlib;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
|
||||
@@ -17,34 +17,17 @@
|
||||
package brut.androlib;
|
||||
|
||||
import brut.androlib.exceptions.AndrolibException;
|
||||
import brut.common.BrutException;
|
||||
import brut.util.AaptManager;
|
||||
import brut.util.OSDetection;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public final class Config {
|
||||
private static final Logger LOGGER = Logger.getLogger(Config.class.getName());
|
||||
|
||||
public static final short DECODE_SOURCES_NONE = 0x0000;
|
||||
public static final short DECODE_SOURCES_SMALI = 0x0001;
|
||||
public static final short DECODE_SOURCES_SMALI_ONLY_MAIN_CLASSES = 0x0010;
|
||||
|
||||
public static final short DECODE_RESOURCES_NONE = 0x0100;
|
||||
public static final short DECODE_RESOURCES_FULL = 0x0101;
|
||||
|
||||
public static final short FORCE_DECODE_MANIFEST_NONE = 0x0000;
|
||||
public static final short FORCE_DECODE_MANIFEST_FULL = 0x0001;
|
||||
|
||||
public static final short DECODE_ASSETS_NONE = 0x0000;
|
||||
public static final short DECODE_ASSETS_FULL = 0x0001;
|
||||
|
||||
public static final short DECODE_RES_RESOLVE_REMOVE = 0x0000;
|
||||
public static final short DECODE_RES_RESOLVE_DUMMY = 0x0001;
|
||||
public static final short DECODE_RES_RESOLVE_RETAIN = 0x0002;
|
||||
public enum DecodeSources { NONE, FULL, ONLY_MAIN_CLASSES }
|
||||
public enum DecodeResources { NONE, FULL, ONLY_MANIFEST }
|
||||
public enum DecodeAssets { NONE, FULL }
|
||||
public enum DecodeResolve { REMOVE, DUMMY, KEEP }
|
||||
|
||||
private static final String DEFAULT_FRAMEWORK_DIRECTORY;
|
||||
|
||||
@@ -66,85 +49,190 @@ public final class Config {
|
||||
DEFAULT_FRAMEWORK_DIRECTORY = path.toString();
|
||||
}
|
||||
|
||||
// Build options
|
||||
private boolean mForceBuildAll;
|
||||
private boolean mForceDeleteFramework;
|
||||
private boolean mDebugMode;
|
||||
private boolean mNetSecConf;
|
||||
private boolean mVerbose;
|
||||
private boolean mCopyOriginalFiles;
|
||||
private boolean mUpdateFiles;
|
||||
private boolean mNoCrunch;
|
||||
private boolean mNoApk;
|
||||
|
||||
// Decode options
|
||||
private short mDecodeSources;
|
||||
private short mDecodeResources;
|
||||
private short mForceDecodeManifest;
|
||||
private short mDecodeAssets;
|
||||
private short mDecodeResolveMode;
|
||||
private int mApiLevel;
|
||||
private boolean mAnalysisMode;
|
||||
private boolean mForceDelete;
|
||||
private boolean mKeepBrokenResources;
|
||||
private boolean mBaksmaliDebugMode;
|
||||
|
||||
// Common options
|
||||
private int mJobs;
|
||||
private String mFrameworkDirectory;
|
||||
private String mFrameworkTag;
|
||||
private String[] mLibraryFiles;
|
||||
private boolean mForced;
|
||||
private boolean mVerbose;
|
||||
|
||||
// Decode options
|
||||
private DecodeSources mDecodeSources;
|
||||
private boolean mBaksmaliDebugMode;
|
||||
private int mBaksmaliApiLevel;
|
||||
private DecodeResources mDecodeResources;
|
||||
private DecodeResolve mDecodeResolve;
|
||||
private boolean mAnalysisMode;
|
||||
private boolean mKeepBrokenResources;
|
||||
private DecodeAssets mDecodeAssets;
|
||||
|
||||
// Build options
|
||||
private boolean mDebugMode;
|
||||
private boolean mNetSecConf;
|
||||
private boolean mCopyOriginalFiles;
|
||||
private boolean mNoCrunch;
|
||||
private boolean mNoApk;
|
||||
private File mAaptBinary;
|
||||
private int mAaptVersion;
|
||||
|
||||
public Config() {
|
||||
mForceBuildAll = false;
|
||||
mForceDeleteFramework = false;
|
||||
mDebugMode = false;
|
||||
mNetSecConf = false;
|
||||
mVerbose = false;
|
||||
mCopyOriginalFiles = false;
|
||||
mUpdateFiles = false;
|
||||
mNoCrunch = false;
|
||||
mNoApk = false;
|
||||
|
||||
mDecodeSources = DECODE_SOURCES_SMALI;
|
||||
mDecodeResources = DECODE_RESOURCES_FULL;
|
||||
mForceDecodeManifest = FORCE_DECODE_MANIFEST_NONE;
|
||||
mDecodeAssets = DECODE_ASSETS_FULL;
|
||||
mDecodeResolveMode = DECODE_RES_RESOLVE_REMOVE;
|
||||
mApiLevel = 0;
|
||||
mAnalysisMode = false;
|
||||
mForceDelete = false;
|
||||
mKeepBrokenResources = false;
|
||||
mBaksmaliDebugMode = true;
|
||||
|
||||
// Common options
|
||||
mJobs = Math.min(Runtime.getRuntime().availableProcessors(), 8);
|
||||
mFrameworkDirectory = DEFAULT_FRAMEWORK_DIRECTORY;
|
||||
mFrameworkTag = null;
|
||||
mLibraryFiles = null;
|
||||
mForced = false;
|
||||
mVerbose = false;
|
||||
|
||||
// Decode options
|
||||
mDecodeSources = DecodeSources.FULL;
|
||||
mBaksmaliDebugMode = true;
|
||||
mBaksmaliApiLevel = 0;
|
||||
mDecodeResources = DecodeResources.FULL;
|
||||
mDecodeResolve = DecodeResolve.REMOVE;
|
||||
mAnalysisMode = false;
|
||||
mKeepBrokenResources = false;
|
||||
mDecodeAssets = DecodeAssets.FULL;
|
||||
|
||||
// Build options
|
||||
mDebugMode = false;
|
||||
mNetSecConf = false;
|
||||
mCopyOriginalFiles = false;
|
||||
mNoCrunch = false;
|
||||
mNoApk = false;
|
||||
mAaptBinary = null;
|
||||
mAaptVersion = 2;
|
||||
}
|
||||
|
||||
// Common options
|
||||
|
||||
public int getJobs() {
|
||||
return mJobs;
|
||||
}
|
||||
|
||||
public void setJobs(int jobs) {
|
||||
mJobs = jobs;
|
||||
}
|
||||
|
||||
public String getFrameworkDirectory() {
|
||||
return mFrameworkDirectory;
|
||||
}
|
||||
|
||||
public void setFrameworkDirectory(String frameworkDirectory) {
|
||||
if (frameworkDirectory == null || frameworkDirectory.isEmpty()) {
|
||||
frameworkDirectory = DEFAULT_FRAMEWORK_DIRECTORY;
|
||||
}
|
||||
mFrameworkDirectory = frameworkDirectory;
|
||||
}
|
||||
|
||||
public String getFrameworkTag() {
|
||||
return mFrameworkTag;
|
||||
}
|
||||
|
||||
public void setFrameworkTag(String frameworkTag) {
|
||||
if (frameworkTag == null || frameworkTag.isEmpty()) {
|
||||
frameworkTag = null;
|
||||
}
|
||||
mFrameworkTag = frameworkTag;
|
||||
}
|
||||
|
||||
public String[] getLibraryFiles() {
|
||||
return mLibraryFiles;
|
||||
}
|
||||
|
||||
public void setLibraryFiles(String[] libraryFiles) {
|
||||
mLibraryFiles = libraryFiles;
|
||||
}
|
||||
|
||||
public boolean isForced() {
|
||||
return mForced;
|
||||
}
|
||||
|
||||
public void setForced(boolean forced) {
|
||||
mForced = forced;
|
||||
}
|
||||
|
||||
public boolean isVerbose() {
|
||||
return mVerbose;
|
||||
}
|
||||
|
||||
public void setVerbose(boolean verbose) {
|
||||
mVerbose = verbose;
|
||||
}
|
||||
|
||||
// Decode options
|
||||
|
||||
public DecodeSources getDecodeSources() {
|
||||
return mDecodeSources;
|
||||
}
|
||||
|
||||
public void setDecodeSources(DecodeSources decodeSources) {
|
||||
assert decodeSources != null;
|
||||
mDecodeSources = decodeSources;
|
||||
}
|
||||
|
||||
public boolean isBaksmaliDebugMode() {
|
||||
return mBaksmaliDebugMode;
|
||||
}
|
||||
|
||||
public void setBaksmaliDebugMode(boolean baksmaliDebugMode) {
|
||||
mBaksmaliDebugMode = baksmaliDebugMode;
|
||||
}
|
||||
|
||||
public int getBaksmaliApiLevel() {
|
||||
return mBaksmaliApiLevel;
|
||||
}
|
||||
|
||||
public void setBaksmaliApiLevel(int baksmaliApiLevel) {
|
||||
mBaksmaliApiLevel = baksmaliApiLevel;
|
||||
}
|
||||
|
||||
public DecodeResources getDecodeResources() {
|
||||
return mDecodeResources;
|
||||
}
|
||||
|
||||
public void setDecodeResources(DecodeResources decodeResources) {
|
||||
assert decodeResources != null;
|
||||
mDecodeResources = decodeResources;
|
||||
}
|
||||
|
||||
public DecodeAssets getDecodeAssets() {
|
||||
return mDecodeAssets;
|
||||
}
|
||||
|
||||
public void setDecodeAssets(DecodeAssets decodeAssets) {
|
||||
assert decodeAssets != null;
|
||||
mDecodeAssets = decodeAssets;
|
||||
}
|
||||
|
||||
public DecodeResolve getDecodeResolve() {
|
||||
return mDecodeResolve;
|
||||
}
|
||||
|
||||
public void setDecodeResolve(DecodeResolve decodeResolve) {
|
||||
assert decodeResolve != null;
|
||||
mDecodeResolve = decodeResolve;
|
||||
}
|
||||
|
||||
public boolean isKeepBrokenResources() {
|
||||
return mKeepBrokenResources;
|
||||
}
|
||||
|
||||
public void setKeepBrokenResources(boolean keepBrokenResources) {
|
||||
mKeepBrokenResources = keepBrokenResources;
|
||||
}
|
||||
|
||||
public boolean isAnalysisMode() {
|
||||
return mAnalysisMode;
|
||||
}
|
||||
|
||||
public void setAnalysisMode(boolean analysisMode) {
|
||||
mAnalysisMode = analysisMode;
|
||||
}
|
||||
|
||||
// Build options
|
||||
|
||||
public boolean isForceBuildAll() {
|
||||
return mForceBuildAll;
|
||||
}
|
||||
|
||||
public void setForceBuildAll(boolean forceBuildAll) {
|
||||
mForceBuildAll = forceBuildAll;
|
||||
}
|
||||
|
||||
public boolean isForceDeleteFramework() {
|
||||
return mForceDeleteFramework;
|
||||
}
|
||||
|
||||
public void setForceDeleteFramework(boolean forceDeleteFramework) {
|
||||
mForceDeleteFramework = forceDeleteFramework;
|
||||
}
|
||||
|
||||
public boolean isDebugMode() {
|
||||
return mDebugMode;
|
||||
}
|
||||
@@ -161,14 +249,6 @@ public final class Config {
|
||||
mNetSecConf = netSecConf;
|
||||
}
|
||||
|
||||
public boolean isVerbose() {
|
||||
return mVerbose;
|
||||
}
|
||||
|
||||
public void setVerbose(boolean verbose) {
|
||||
mVerbose = verbose;
|
||||
}
|
||||
|
||||
public boolean isCopyOriginalFiles() {
|
||||
return mCopyOriginalFiles;
|
||||
}
|
||||
@@ -177,14 +257,6 @@ public final class Config {
|
||||
mCopyOriginalFiles = copyOriginalFiles;
|
||||
}
|
||||
|
||||
public boolean isUpdateFiles() {
|
||||
return mUpdateFiles;
|
||||
}
|
||||
|
||||
public void setUpdateFiles(boolean updateFiles) {
|
||||
mUpdateFiles = updateFiles;
|
||||
}
|
||||
|
||||
public boolean isNoCrunch() {
|
||||
return mNoCrunch;
|
||||
}
|
||||
@@ -201,171 +273,13 @@ public final class Config {
|
||||
mNoApk = noApk;
|
||||
}
|
||||
|
||||
// Decode options
|
||||
|
||||
public short getDecodeSources() {
|
||||
return mDecodeSources;
|
||||
}
|
||||
|
||||
public void setDecodeSources(short decodeSources) throws AndrolibException {
|
||||
switch (decodeSources) {
|
||||
case DECODE_SOURCES_NONE:
|
||||
case DECODE_SOURCES_SMALI:
|
||||
case DECODE_SOURCES_SMALI_ONLY_MAIN_CLASSES:
|
||||
mDecodeSources = decodeSources;
|
||||
break;
|
||||
default:
|
||||
throw new AndrolibException("Invalid decode sources mode: " + decodeSources);
|
||||
}
|
||||
}
|
||||
|
||||
public short getDecodeResources() {
|
||||
return mDecodeResources;
|
||||
}
|
||||
|
||||
public void setDecodeResources(short decodeResources) throws AndrolibException {
|
||||
switch (decodeResources) {
|
||||
case DECODE_RESOURCES_NONE:
|
||||
case DECODE_RESOURCES_FULL:
|
||||
mDecodeResources = decodeResources;
|
||||
break;
|
||||
default:
|
||||
throw new AndrolibException("Invalid decode resources mode: " + decodeResources);
|
||||
}
|
||||
}
|
||||
|
||||
public short getForceDecodeManifest() {
|
||||
return mForceDecodeManifest;
|
||||
}
|
||||
|
||||
public void setForceDecodeManifest(short forceDecodeManifest) throws AndrolibException {
|
||||
switch (forceDecodeManifest) {
|
||||
case FORCE_DECODE_MANIFEST_NONE:
|
||||
case FORCE_DECODE_MANIFEST_FULL:
|
||||
mForceDecodeManifest = forceDecodeManifest;
|
||||
break;
|
||||
default:
|
||||
throw new AndrolibException("Invalid force decode manifest mode: " + forceDecodeManifest);
|
||||
}
|
||||
}
|
||||
|
||||
public short getDecodeAssets() {
|
||||
return mDecodeAssets;
|
||||
}
|
||||
|
||||
public void setDecodeAssets(short decodeAssets) throws AndrolibException {
|
||||
switch (decodeAssets) {
|
||||
case DECODE_ASSETS_NONE:
|
||||
case DECODE_ASSETS_FULL:
|
||||
mDecodeAssets = decodeAssets;
|
||||
break;
|
||||
default:
|
||||
throw new AndrolibException("Invalid decode asset mode: " + decodeAssets);
|
||||
}
|
||||
}
|
||||
|
||||
public short getDecodeResolveMode() {
|
||||
return mDecodeResolveMode;
|
||||
}
|
||||
|
||||
public void setDecodeResolveMode(short decodeResolveMode) throws AndrolibException {
|
||||
switch (decodeResolveMode) {
|
||||
case DECODE_RES_RESOLVE_REMOVE:
|
||||
case DECODE_RES_RESOLVE_DUMMY:
|
||||
case DECODE_RES_RESOLVE_RETAIN:
|
||||
mDecodeResolveMode = decodeResolveMode;
|
||||
break;
|
||||
default:
|
||||
throw new AndrolibException("Invalid decode resolve mode: " + decodeResolveMode);
|
||||
}
|
||||
}
|
||||
|
||||
public int getApiLevel() {
|
||||
return mApiLevel;
|
||||
}
|
||||
|
||||
public void setApiLevel(int apiLevel) {
|
||||
mApiLevel = apiLevel;
|
||||
}
|
||||
|
||||
public boolean isAnalysisMode() {
|
||||
return mAnalysisMode;
|
||||
}
|
||||
|
||||
public void setAnalysisMode(boolean analysisMode) {
|
||||
mAnalysisMode = analysisMode;
|
||||
}
|
||||
|
||||
public boolean isForceDelete() {
|
||||
return mForceDelete;
|
||||
}
|
||||
|
||||
public void setForceDelete(boolean forceDelete) {
|
||||
mForceDelete = forceDelete;
|
||||
}
|
||||
|
||||
public boolean isKeepBrokenResources() {
|
||||
return mKeepBrokenResources;
|
||||
}
|
||||
|
||||
public void setKeepBrokenResources(boolean keepBrokenResources) {
|
||||
mKeepBrokenResources = keepBrokenResources;
|
||||
}
|
||||
|
||||
public boolean isBaksmaliDebugMode() {
|
||||
return mBaksmaliDebugMode;
|
||||
}
|
||||
|
||||
public void setBaksmaliDebugMode(boolean baksmaliDebugMode) {
|
||||
mBaksmaliDebugMode = baksmaliDebugMode;
|
||||
}
|
||||
|
||||
// Common options
|
||||
|
||||
public int getJobs() {
|
||||
return mJobs;
|
||||
}
|
||||
|
||||
public void setJobs(int jobs) {
|
||||
mJobs = jobs;
|
||||
}
|
||||
|
||||
public String getFrameworkDirectory() {
|
||||
return mFrameworkDirectory;
|
||||
}
|
||||
|
||||
public void setFrameworkDirectory(String frameworkDirectory) {
|
||||
mFrameworkDirectory = frameworkDirectory != null
|
||||
? frameworkDirectory : DEFAULT_FRAMEWORK_DIRECTORY;
|
||||
}
|
||||
|
||||
public String getFrameworkTag() {
|
||||
return mFrameworkTag;
|
||||
}
|
||||
|
||||
public void setFrameworkTag(String frameworkTag) {
|
||||
mFrameworkTag = frameworkTag;
|
||||
}
|
||||
|
||||
public String[] getLibraryFiles() {
|
||||
return mLibraryFiles;
|
||||
}
|
||||
|
||||
public void setLibraryFiles(String[] libraryFiles) {
|
||||
mLibraryFiles = libraryFiles;
|
||||
}
|
||||
|
||||
public File getAaptBinary() {
|
||||
return mAaptBinary;
|
||||
}
|
||||
|
||||
public void setAaptBinary(File aaptBinary) throws AndrolibException {
|
||||
try {
|
||||
mAaptBinary = aaptBinary;
|
||||
mAaptVersion = AaptManager.getAaptVersion(aaptBinary);
|
||||
} catch (BrutException ex) {
|
||||
throw new AndrolibException(ex);
|
||||
}
|
||||
mAaptBinary = aaptBinary;
|
||||
mAaptVersion = AaptManager.getAaptVersion(aaptBinary);
|
||||
}
|
||||
|
||||
public int getAaptVersion() {
|
||||
|
||||
@@ -1,295 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Ryszard Wiśniewski <[email protected]>
|
||||
* Copyright (C) 2010 Connor Tumbleson <[email protected]>
|
||||
*
|
||||
* 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.ExtFile;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class ApkInfo implements YamlSerializable {
|
||||
public static final String[] RESOURCES_DIRNAMES = { "res", "r", "R" };
|
||||
public static final String[] RAW_DIRNAMES = { "assets", "lib" };
|
||||
|
||||
public static final Pattern ORIGINAL_FILENAMES_PATTERN = Pattern.compile(
|
||||
"AndroidManifest\\.xml|META-INF/[^/]+\\.(RSA|SF|MF)|stamp-cert-sha256");
|
||||
|
||||
public static final Pattern STANDARD_FILENAMES_PATTERN = Pattern.compile(
|
||||
"[^/]+\\.dex|resources\\.arsc|(" + String.join("|", RESOURCES_DIRNAMES) + "|"
|
||||
+ String.join("|", RAW_DIRNAMES) + ")/.*|" + ORIGINAL_FILENAMES_PATTERN.pattern());
|
||||
|
||||
// only set when loaded from a file (not a stream)
|
||||
private transient ExtFile mApkFile;
|
||||
|
||||
public String version;
|
||||
public String apkFileName;
|
||||
public UsesFramework usesFramework;
|
||||
public List<String> usesLibrary = new ArrayList<>();
|
||||
public Map<String, String> sdkInfo = new LinkedHashMap<>();
|
||||
public PackageInfo packageInfo = new PackageInfo();
|
||||
public VersionInfo versionInfo = new VersionInfo();
|
||||
public Map<String, Boolean> featureFlags = new LinkedHashMap<>();
|
||||
public boolean sparseResources;
|
||||
public boolean compactEntries;
|
||||
public List<String> doNotCompress = new ArrayList<>();
|
||||
|
||||
public ApkInfo() {
|
||||
version = ApktoolProperties.getVersion();
|
||||
}
|
||||
|
||||
public ApkInfo(ExtFile apkFile) {
|
||||
this();
|
||||
setApkFile(apkFile);
|
||||
}
|
||||
|
||||
public ExtFile getApkFile() {
|
||||
return mApkFile;
|
||||
}
|
||||
|
||||
public void setApkFile(ExtFile apkFile) {
|
||||
mApkFile = apkFile;
|
||||
if (apkFileName == null) {
|
||||
apkFileName = apkFile.getName();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasSources() throws AndrolibException {
|
||||
if (mApkFile == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return mApkFile.getDirectory().containsFile("classes.dex");
|
||||
} catch (DirectoryException ex) {
|
||||
throw new AndrolibException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasManifest() throws AndrolibException {
|
||||
if (mApkFile == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return mApkFile.getDirectory().containsFile("AndroidManifest.xml");
|
||||
} catch (DirectoryException ex) {
|
||||
throw new AndrolibException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasResources() throws AndrolibException {
|
||||
if (mApkFile == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return mApkFile.getDirectory().containsFile("resources.arsc");
|
||||
} catch (DirectoryException ex) {
|
||||
throw new AndrolibException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public String checkTargetSdkVersionBounds() {
|
||||
int target = mapSdkShorthandToVersion(getTargetSdkVersion());
|
||||
int min = getMinSdkVersion() != null ? mapSdkShorthandToVersion(getMinSdkVersion()) : 0;
|
||||
int max = getMaxSdkVersion() != null ? mapSdkShorthandToVersion(getMaxSdkVersion()) : target;
|
||||
|
||||
return Integer.toString(Math.max(min, Math.min(max, target)));
|
||||
}
|
||||
|
||||
public String getMinSdkVersion() {
|
||||
return sdkInfo.get("minSdkVersion");
|
||||
}
|
||||
|
||||
public void setMinSdkVersion(String minSdkVersion) {
|
||||
sdkInfo.put("minSdkVersion", minSdkVersion);
|
||||
}
|
||||
|
||||
public String getMaxSdkVersion() {
|
||||
return sdkInfo.get("maxSdkVersion");
|
||||
}
|
||||
|
||||
public void setMaxSdkVersion(String maxSdkVersion) {
|
||||
sdkInfo.put("maxSdkVersion", maxSdkVersion);
|
||||
}
|
||||
|
||||
public String getTargetSdkVersion() {
|
||||
return sdkInfo.get("targetSdkVersion");
|
||||
}
|
||||
|
||||
public void setTargetSdkVersion(String targetSdkVersion) {
|
||||
sdkInfo.put("targetSdkVersion", targetSdkVersion);
|
||||
}
|
||||
|
||||
public int getMinSdkVersionFromAndroidCodename(String sdkVersion) {
|
||||
int sdkNumber = mapSdkShorthandToVersion(sdkVersion);
|
||||
|
||||
if (sdkNumber == ResConfigFlags.SDK_BASE) {
|
||||
return Integer.parseInt(getMinSdkVersion());
|
||||
}
|
||||
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":
|
||||
return ResConfigFlags.SDK_UPSIDEDOWN_CAKE;
|
||||
case "VANILLAICECREAM":
|
||||
case "VANILLA_ICE_CREAM":
|
||||
return ResConfigFlags.SDK_VANILLA_ICE_CREAM;
|
||||
case "BAKLAVA":
|
||||
return ResConfigFlags.SDK_BAKLAVA;
|
||||
case "SDK_CUR_DEVELOPMENT":
|
||||
return ResConfigFlags.SDK_DEVELOPMENT;
|
||||
default:
|
||||
return Integer.parseInt(sdkVersion);
|
||||
}
|
||||
}
|
||||
|
||||
public void addFeatureFlag(String flag, boolean value) {
|
||||
featureFlags.put(flag, value);
|
||||
}
|
||||
|
||||
public void save(File file) throws AndrolibException {
|
||||
try (YamlWriter writer = new YamlWriter(Files.newOutputStream(file.toPath()))) {
|
||||
write(writer);
|
||||
} catch (FileNotFoundException ex) {
|
||||
throw new AndrolibException("File not found");
|
||||
} catch (Exception ex) {
|
||||
throw new AndrolibException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static ApkInfo load(InputStream in) throws AndrolibException {
|
||||
YamlReader reader = new YamlReader(in);
|
||||
ApkInfo apkInfo = new ApkInfo();
|
||||
reader.readRoot(apkInfo);
|
||||
return apkInfo;
|
||||
}
|
||||
|
||||
public static ApkInfo load(ExtFile apkDir) throws AndrolibException {
|
||||
try (InputStream in = apkDir.getDirectory().getFileInput("apktool.yml")) {
|
||||
ApkInfo apkInfo = ApkInfo.load(in);
|
||||
apkInfo.setApkFile(apkDir);
|
||||
return apkInfo;
|
||||
} catch (DirectoryException | IOException ex) {
|
||||
throw new AndrolibException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readItem(YamlReader reader) throws AndrolibException {
|
||||
YamlLine line = reader.getLine();
|
||||
switch (line.getKey()) {
|
||||
case "version": {
|
||||
version = line.getValue();
|
||||
break;
|
||||
}
|
||||
case "apkFileName": {
|
||||
apkFileName = line.getValue();
|
||||
break;
|
||||
}
|
||||
case "usesFramework": {
|
||||
usesFramework = new UsesFramework();
|
||||
reader.readObject(usesFramework);
|
||||
break;
|
||||
}
|
||||
case "usesLibrary": {
|
||||
usesLibrary.clear();
|
||||
reader.readStringList(usesLibrary);
|
||||
break;
|
||||
}
|
||||
case "sdkInfo": {
|
||||
sdkInfo.clear();
|
||||
reader.readStringMap(sdkInfo);
|
||||
break;
|
||||
}
|
||||
case "packageInfo": {
|
||||
packageInfo = new PackageInfo();
|
||||
reader.readObject(packageInfo);
|
||||
break;
|
||||
}
|
||||
case "versionInfo": {
|
||||
versionInfo = new VersionInfo();
|
||||
reader.readObject(versionInfo);
|
||||
break;
|
||||
}
|
||||
case "featureFlags": {
|
||||
featureFlags.clear();
|
||||
reader.readBoolMap(featureFlags);
|
||||
break;
|
||||
}
|
||||
case "sparseResources": {
|
||||
sparseResources = line.getValueBool();
|
||||
break;
|
||||
}
|
||||
case "compactEntries": {
|
||||
compactEntries = line.getValueBool();
|
||||
break;
|
||||
}
|
||||
case "doNotCompress": {
|
||||
doNotCompress.clear();
|
||||
reader.readStringList(doNotCompress);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(YamlWriter writer) {
|
||||
writer.writeString("version", version);
|
||||
writer.writeString("apkFileName", apkFileName);
|
||||
writer.writeObject("usesFramework", usesFramework);
|
||||
if (!usesLibrary.isEmpty()) {
|
||||
writer.writeList("usesLibrary", usesLibrary);
|
||||
}
|
||||
writer.writeMap("sdkInfo", sdkInfo);
|
||||
writer.writeObject("packageInfo", packageInfo);
|
||||
writer.writeObject("versionInfo", versionInfo);
|
||||
if (!featureFlags.isEmpty()) {
|
||||
writer.writeMap("featureFlags", featureFlags);
|
||||
}
|
||||
writer.writeBool("sparseResources", sparseResources);
|
||||
writer.writeBool("compactEntries", compactEntries);
|
||||
if (!doNotCompress.isEmpty()) {
|
||||
writer.writeList("doNotCompress", doNotCompress);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Ryszard Wiśniewski <[email protected]>
|
||||
* Copyright (C) 2010 Connor Tumbleson <[email protected]>
|
||||
*
|
||||
* 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;
|
||||
|
||||
public class PackageInfo implements YamlSerializable {
|
||||
public String forcedPackageId;
|
||||
public String renameManifestPackage;
|
||||
|
||||
@Override
|
||||
public void readItem(YamlReader reader) {
|
||||
YamlLine line = reader.getLine();
|
||||
switch (line.getKey()) {
|
||||
case "forcedPackageId": {
|
||||
forcedPackageId = line.getValue();
|
||||
break;
|
||||
}
|
||||
case "renameManifestPackage": {
|
||||
renameManifestPackage = line.getValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(YamlWriter writer) {
|
||||
writer.writeString("forcedPackageId", forcedPackageId);
|
||||
writer.writeString("renameManifestPackage", renameManifestPackage);
|
||||
}
|
||||
}
|
||||
@@ -17,18 +17,18 @@
|
||||
package brut.androlib.exceptions;
|
||||
|
||||
public class CantFindFrameworkResException extends AndrolibException {
|
||||
private final int mPkgId;
|
||||
private final int mPackageId;
|
||||
|
||||
public CantFindFrameworkResException(int pkgId) {
|
||||
mPkgId = pkgId;
|
||||
mPackageId = pkgId;
|
||||
}
|
||||
|
||||
public int getPkgId() {
|
||||
return mPkgId;
|
||||
public int getPackageId() {
|
||||
return mPackageId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return String.format("Could not find framework resources for package of id: %d", mPkgId);
|
||||
return String.format("Could not find framework resources for package of id: %d", mPackageId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Ryszard Wiśniewski <[email protected]>
|
||||
* Copyright (C) 2010 Connor Tumbleson <[email protected]>
|
||||
*
|
||||
* 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.ApktoolProperties;
|
||||
import brut.androlib.exceptions.AndrolibException;
|
||||
import brut.directory.DirectoryException;
|
||||
import brut.directory.ExtFile;
|
||||
import brut.yaml.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class ApkInfo implements YamlSerializable {
|
||||
public static final String[] RESOURCES_DIRNAMES = { "res", "r", "R" };
|
||||
public static final String[] RAW_DIRNAMES = { "assets", "lib" };
|
||||
|
||||
public static final Pattern ORIGINAL_FILENAMES_PATTERN = Pattern.compile(
|
||||
"AndroidManifest\\.xml|META-INF/[^/]+\\.(RSA|SF|MF)|stamp-cert-sha256");
|
||||
|
||||
public static final Pattern STANDARD_FILENAMES_PATTERN = Pattern.compile(
|
||||
"[^/]+\\.dex|resources\\.arsc|(" + String.join("|", RESOURCES_DIRNAMES) + "|"
|
||||
+ String.join("|", RAW_DIRNAMES) + ")/.*|" + ORIGINAL_FILENAMES_PATTERN.pattern());
|
||||
|
||||
private String mVersion;
|
||||
private String mApkFileName;
|
||||
private final UsesFramework mUsesFramework;
|
||||
private final List<String> mUsesLibrary;
|
||||
private final SdkInfo mSdkInfo;
|
||||
private final PackageInfo mPackageInfo;
|
||||
private final VersionInfo mVersionInfo;
|
||||
private final Map<String, Boolean> mFeatureFlags;
|
||||
private Boolean mSparseResources;
|
||||
private Boolean mCompactEntries;
|
||||
private final List<String> mDoNotCompress;
|
||||
|
||||
// only set when loaded from a file (not a stream)
|
||||
private ExtFile mApkFile;
|
||||
|
||||
public ApkInfo() {
|
||||
mVersion = ApktoolProperties.getVersion();
|
||||
mApkFileName = null;
|
||||
mUsesFramework = new UsesFramework();
|
||||
mUsesLibrary = new ArrayList<>();
|
||||
mSdkInfo = new SdkInfo();
|
||||
mPackageInfo = new PackageInfo();
|
||||
mVersionInfo = new VersionInfo();
|
||||
mFeatureFlags = new LinkedHashMap<>();
|
||||
mSparseResources = null;
|
||||
mCompactEntries = null;
|
||||
mDoNotCompress = new ArrayList<>();
|
||||
}
|
||||
|
||||
public ApkInfo(ExtFile apkFile) {
|
||||
this();
|
||||
setApkFile(apkFile);
|
||||
}
|
||||
|
||||
public static ApkInfo load(InputStream in) {
|
||||
YamlReader reader = new YamlReader(in);
|
||||
ApkInfo apkInfo = new ApkInfo();
|
||||
reader.readRoot(apkInfo);
|
||||
return apkInfo;
|
||||
}
|
||||
|
||||
public static ApkInfo load(ExtFile apkDir) throws AndrolibException {
|
||||
try (InputStream in = apkDir.getDirectory().getFileInput("apktool.yml")) {
|
||||
ApkInfo apkInfo = ApkInfo.load(in);
|
||||
apkInfo.setApkFile(apkDir);
|
||||
return apkInfo;
|
||||
} catch (DirectoryException | IOException ex) {
|
||||
throw new AndrolibException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void save(File file) throws AndrolibException {
|
||||
try (YamlWriter writer = new YamlWriter(Files.newOutputStream(file.toPath()))) {
|
||||
write(writer);
|
||||
} catch (FileNotFoundException ex) {
|
||||
throw new AndrolibException("File not found");
|
||||
} catch (Exception ex) {
|
||||
throw new AndrolibException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readItem(YamlReader reader) {
|
||||
YamlLine line = reader.getLine();
|
||||
switch (line.getKey()) {
|
||||
case "version": {
|
||||
mVersion = line.getValue();
|
||||
break;
|
||||
}
|
||||
case "apkFileName": {
|
||||
mApkFileName = line.getValue();
|
||||
break;
|
||||
}
|
||||
case "usesFramework": {
|
||||
mUsesFramework.clear();
|
||||
reader.readObject(mUsesFramework);
|
||||
break;
|
||||
}
|
||||
case "usesLibrary": {
|
||||
mUsesLibrary.clear();
|
||||
reader.readStringList(mUsesLibrary);
|
||||
break;
|
||||
}
|
||||
case "sdkInfo": {
|
||||
mSdkInfo.clear();
|
||||
reader.readObject(mSdkInfo);
|
||||
break;
|
||||
}
|
||||
case "packageInfo": {
|
||||
mPackageInfo.clear();
|
||||
reader.readObject(mPackageInfo);
|
||||
break;
|
||||
}
|
||||
case "versionInfo": {
|
||||
mVersionInfo.clear();
|
||||
reader.readObject(mVersionInfo);
|
||||
break;
|
||||
}
|
||||
case "featureFlags": {
|
||||
mFeatureFlags.clear();
|
||||
reader.readBoolMap(mFeatureFlags);
|
||||
break;
|
||||
}
|
||||
case "sparseResources": {
|
||||
mSparseResources = line.getValueBool();
|
||||
break;
|
||||
}
|
||||
case "compactEntries": {
|
||||
mCompactEntries = line.getValueBool();
|
||||
break;
|
||||
}
|
||||
case "doNotCompress": {
|
||||
mDoNotCompress.clear();
|
||||
reader.readStringList(mDoNotCompress);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(YamlWriter writer) {
|
||||
writer.writeString("version", mVersion);
|
||||
writer.writeString("apkFileName", mApkFileName);
|
||||
if (!mUsesFramework.isEmpty()) {
|
||||
writer.writeObject("usesFramework", mUsesFramework);
|
||||
}
|
||||
if (!mUsesLibrary.isEmpty()) {
|
||||
writer.writeList("usesLibrary", mUsesLibrary);
|
||||
}
|
||||
if (!mSdkInfo.isEmpty()) {
|
||||
writer.writeObject("sdkInfo", mSdkInfo);
|
||||
}
|
||||
if (!mPackageInfo.isEmpty()) {
|
||||
writer.writeObject("packageInfo", mPackageInfo);
|
||||
}
|
||||
if (!mVersionInfo.isEmpty()) {
|
||||
writer.writeObject("versionInfo", mVersionInfo);
|
||||
}
|
||||
if (!mFeatureFlags.isEmpty()) {
|
||||
writer.writeMap("featureFlags", mFeatureFlags);
|
||||
}
|
||||
if (mSparseResources != null) {
|
||||
writer.writeBool("sparseResources", mSparseResources);
|
||||
}
|
||||
if (mCompactEntries != null) {
|
||||
writer.writeBool("compactEntries", mCompactEntries);
|
||||
}
|
||||
if (!mDoNotCompress.isEmpty()) {
|
||||
writer.writeList("doNotCompress", mDoNotCompress);
|
||||
}
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return mVersion;
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
mVersion = version;
|
||||
}
|
||||
|
||||
public String getApkFileName() {
|
||||
return mApkFileName;
|
||||
}
|
||||
|
||||
public void setApkFileName(String apkFileName) {
|
||||
mApkFileName = apkFileName;
|
||||
}
|
||||
|
||||
public UsesFramework getUsesFramework() {
|
||||
return mUsesFramework;
|
||||
}
|
||||
|
||||
public List<String> getUsesLibrary() {
|
||||
return mUsesLibrary;
|
||||
}
|
||||
|
||||
public SdkInfo getSdkInfo() {
|
||||
return mSdkInfo;
|
||||
}
|
||||
|
||||
public PackageInfo getPackageInfo() {
|
||||
return mPackageInfo;
|
||||
}
|
||||
|
||||
public VersionInfo getVersionInfo() {
|
||||
return mVersionInfo;
|
||||
}
|
||||
|
||||
public Map<String, Boolean> getFeatureFlags() {
|
||||
return mFeatureFlags;
|
||||
}
|
||||
|
||||
public boolean isSparseResources() {
|
||||
return mSparseResources != null ? mSparseResources : false;
|
||||
}
|
||||
|
||||
public void setSparseResources(boolean sparseResources) {
|
||||
mSparseResources = sparseResources;
|
||||
}
|
||||
|
||||
public boolean isCompactEntries() {
|
||||
return mCompactEntries != null ? mCompactEntries : false;
|
||||
}
|
||||
|
||||
public void setCompactEntries(boolean compactEntries) {
|
||||
mCompactEntries = compactEntries;
|
||||
}
|
||||
|
||||
public List<String> getDoNotCompress() {
|
||||
return mDoNotCompress;
|
||||
}
|
||||
|
||||
public ExtFile getApkFile() {
|
||||
return mApkFile;
|
||||
}
|
||||
|
||||
public void setApkFile(ExtFile apkFile) {
|
||||
mApkFile = apkFile;
|
||||
if (mApkFileName == null) {
|
||||
mApkFileName = apkFile.getName();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasSources() throws AndrolibException {
|
||||
if (mApkFile == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return mApkFile.getDirectory().containsFile("classes.dex");
|
||||
} catch (DirectoryException ex) {
|
||||
throw new AndrolibException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasManifest() throws AndrolibException {
|
||||
if (mApkFile == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return mApkFile.getDirectory().containsFile("AndroidManifest.xml");
|
||||
} catch (DirectoryException ex) {
|
||||
throw new AndrolibException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasResources() throws AndrolibException {
|
||||
if (mApkFile == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return mApkFile.getDirectory().containsFile("resources.arsc");
|
||||
} catch (DirectoryException ex) {
|
||||
throw new AndrolibException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Ryszard Wiśniewski <[email protected]>
|
||||
* Copyright (C) 2010 Connor Tumbleson <[email protected]>
|
||||
*
|
||||
* 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.yaml.*;
|
||||
|
||||
public class PackageInfo implements YamlSerializable {
|
||||
private String mForcedPackageId;
|
||||
private String mRenameManifestPackage;
|
||||
|
||||
public PackageInfo() {
|
||||
clear();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
mForcedPackageId = null;
|
||||
mRenameManifestPackage = null;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return mForcedPackageId == null && mRenameManifestPackage == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readItem(YamlReader reader) {
|
||||
YamlLine line = reader.getLine();
|
||||
switch (line.getKey()) {
|
||||
case "forcedPackageId": {
|
||||
mForcedPackageId = line.getValue();
|
||||
break;
|
||||
}
|
||||
case "renameManifestPackage": {
|
||||
mRenameManifestPackage = line.getValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(YamlWriter writer) {
|
||||
if (mForcedPackageId != null) {
|
||||
writer.writeString("forcedPackageId", mForcedPackageId);
|
||||
}
|
||||
if (mRenameManifestPackage != null) {
|
||||
writer.writeString("renameManifestPackage", mRenameManifestPackage);
|
||||
}
|
||||
}
|
||||
|
||||
public String getForcedPackageId() {
|
||||
return mForcedPackageId;
|
||||
}
|
||||
|
||||
public void setForcedPackageId(String forcedPackageId) {
|
||||
mForcedPackageId = forcedPackageId;
|
||||
}
|
||||
|
||||
public String getRenameManifestPackage() {
|
||||
return mRenameManifestPackage;
|
||||
}
|
||||
|
||||
public void setRenameManifestPackage(String renameManifestPackage) {
|
||||
mRenameManifestPackage = renameManifestPackage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Ryszard Wiśniewski <[email protected]>
|
||||
* Copyright (C) 2010 Connor Tumbleson <[email protected]>
|
||||
*
|
||||
* 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.res.data.ResConfigFlags;
|
||||
import brut.yaml.*;
|
||||
|
||||
public class SdkInfo implements YamlSerializable {
|
||||
private String mMinSdkVersion;
|
||||
private String mTargetSdkVersion;
|
||||
private String mMaxSdkVersion;
|
||||
|
||||
public SdkInfo() {
|
||||
clear();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
mMinSdkVersion = null;
|
||||
mTargetSdkVersion = null;
|
||||
mMaxSdkVersion = null;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return mMinSdkVersion == null && mTargetSdkVersion == null && mMaxSdkVersion == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readItem(YamlReader reader) {
|
||||
YamlLine line = reader.getLine();
|
||||
switch (line.getKey()) {
|
||||
case "minSdkVersion": {
|
||||
mMinSdkVersion = line.getValue();
|
||||
break;
|
||||
}
|
||||
case "targetSdkVersion": {
|
||||
mTargetSdkVersion = line.getValue();
|
||||
break;
|
||||
}
|
||||
case "maxSdkVersion": {
|
||||
mMaxSdkVersion = line.getValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(YamlWriter writer) {
|
||||
if (mMinSdkVersion != null) {
|
||||
writer.writeString("minSdkVersion", mMinSdkVersion);
|
||||
}
|
||||
if (mTargetSdkVersion != null) {
|
||||
writer.writeString("targetSdkVersion", mTargetSdkVersion);
|
||||
}
|
||||
if (mMaxSdkVersion != null) {
|
||||
writer.writeString("maxSdkVersion", mMaxSdkVersion);
|
||||
}
|
||||
}
|
||||
|
||||
public String getMinSdkVersion() {
|
||||
return mMinSdkVersion;
|
||||
}
|
||||
|
||||
public void setMinSdkVersion(String minSdkVersion) {
|
||||
mMinSdkVersion = minSdkVersion;
|
||||
}
|
||||
|
||||
public String getTargetSdkVersion() {
|
||||
return mTargetSdkVersion;
|
||||
}
|
||||
|
||||
public String getTargetSdkVersionBounded() {
|
||||
int target = parseSdkInt(mTargetSdkVersion);
|
||||
int min = mMinSdkVersion != null ? parseSdkInt(mMinSdkVersion) : 0;
|
||||
int max = mMaxSdkVersion != null ? parseSdkInt(mMaxSdkVersion) : target;
|
||||
return Integer.toString(Math.max(min, Math.min(max, target)));
|
||||
}
|
||||
|
||||
public void setTargetSdkVersion(String targetSdkVersion) {
|
||||
mTargetSdkVersion = targetSdkVersion;
|
||||
}
|
||||
|
||||
public String getMaxSdkVersion() {
|
||||
return mMaxSdkVersion;
|
||||
}
|
||||
|
||||
public void setMaxSdkVersion(String maxSdkVersion) {
|
||||
mMaxSdkVersion = maxSdkVersion;
|
||||
}
|
||||
|
||||
public static int parseSdkInt(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":
|
||||
return ResConfigFlags.SDK_UPSIDEDOWN_CAKE;
|
||||
case "VANILLAICECREAM":
|
||||
case "VANILLA_ICE_CREAM":
|
||||
return ResConfigFlags.SDK_VANILLA_ICE_CREAM;
|
||||
case "BAKLAVA":
|
||||
return ResConfigFlags.SDK_BAKLAVA;
|
||||
case "SDK_CUR_DEVELOPMENT":
|
||||
return ResConfigFlags.SDK_DEVELOPMENT;
|
||||
default:
|
||||
return Integer.parseInt(sdkVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,28 +14,42 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package brut.androlib.apk;
|
||||
package brut.androlib.meta;
|
||||
|
||||
import brut.androlib.exceptions.AndrolibException;
|
||||
import brut.yaml.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class UsesFramework implements YamlSerializable {
|
||||
public List<Integer> ids;
|
||||
public String tag;
|
||||
private final List<Integer> mIds;
|
||||
private String mTag;
|
||||
|
||||
public UsesFramework() {
|
||||
mIds = new ArrayList<>();
|
||||
clear();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
mIds.clear();
|
||||
mTag = null;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return mIds.isEmpty() && mTag == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readItem(YamlReader reader) throws AndrolibException {
|
||||
public void readItem(YamlReader reader) {
|
||||
YamlLine line = reader.getLine();
|
||||
switch (line.getKey()) {
|
||||
case "ids": {
|
||||
ids = new ArrayList<>();
|
||||
reader.readIntList(ids);
|
||||
mIds.clear();
|
||||
reader.readIntList(mIds);
|
||||
break;
|
||||
}
|
||||
case "tag": {
|
||||
tag = line.getValue();
|
||||
mTag = line.getValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -43,7 +57,23 @@ public class UsesFramework implements YamlSerializable {
|
||||
|
||||
@Override
|
||||
public void write(YamlWriter writer) {
|
||||
writer.writeList("ids", ids);
|
||||
writer.writeString("tag", tag);
|
||||
if (!mIds.isEmpty()) {
|
||||
writer.writeList("ids", mIds);
|
||||
}
|
||||
if (mTag != null) {
|
||||
writer.writeString("tag", mTag);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Integer> getIds() {
|
||||
return mIds;
|
||||
}
|
||||
|
||||
public String getTag() {
|
||||
return mTag;
|
||||
}
|
||||
|
||||
public void setTag(String tag) {
|
||||
mTag = tag;
|
||||
}
|
||||
}
|
||||
@@ -14,22 +14,37 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package brut.androlib.apk;
|
||||
package brut.androlib.meta;
|
||||
|
||||
import brut.yaml.*;
|
||||
|
||||
public class VersionInfo implements YamlSerializable {
|
||||
public String versionCode;
|
||||
public String versionName;
|
||||
private String mVersionCode;
|
||||
private String mVersionName;
|
||||
|
||||
public VersionInfo() {
|
||||
clear();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
mVersionCode = null;
|
||||
mVersionName = null;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return mVersionCode == null && mVersionName == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readItem(YamlReader reader) {
|
||||
YamlLine line = reader.getLine();
|
||||
switch (line.getKey()) {
|
||||
case "versionCode": {
|
||||
versionCode = line.getValue();
|
||||
mVersionCode = line.getValue();
|
||||
break;
|
||||
}
|
||||
case "versionName": {
|
||||
versionName = line.getValue();
|
||||
mVersionName = line.getValue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -37,7 +52,27 @@ public class VersionInfo implements YamlSerializable {
|
||||
|
||||
@Override
|
||||
public void write(YamlWriter writer) {
|
||||
writer.writeString("versionCode", versionCode);
|
||||
writer.writeString("versionName", versionName);
|
||||
if (mVersionCode != null) {
|
||||
writer.writeString("versionCode", mVersionCode);
|
||||
}
|
||||
if (mVersionName != null) {
|
||||
writer.writeString("versionName", mVersionName);
|
||||
}
|
||||
}
|
||||
|
||||
public String getVersionCode() {
|
||||
return mVersionCode;
|
||||
}
|
||||
|
||||
public void setVersionCode(String versionCode) {
|
||||
mVersionCode = versionCode;
|
||||
}
|
||||
|
||||
public String getVersionName() {
|
||||
return mVersionName;
|
||||
}
|
||||
|
||||
public void setVersionName(String versionName) {
|
||||
mVersionName = versionName;
|
||||
}
|
||||
}
|
||||
@@ -17,9 +17,9 @@
|
||||
package brut.androlib.res;
|
||||
|
||||
import brut.androlib.Config;
|
||||
import brut.androlib.apk.ApkInfo;
|
||||
import brut.androlib.exceptions.AndrolibException;
|
||||
import brut.androlib.exceptions.CantFindFrameworkResException;
|
||||
import brut.androlib.meta.ApkInfo;
|
||||
import brut.androlib.res.decoder.ARSCDecoder;
|
||||
import brut.androlib.res.data.ResTable;
|
||||
import brut.androlib.res.data.arsc.ARSCData;
|
||||
@@ -29,6 +29,7 @@ import brut.util.OS;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.util.*;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.zip.CRC32;
|
||||
import java.util.zip.ZipEntry;
|
||||
@@ -98,30 +99,19 @@ public class Framework {
|
||||
}
|
||||
}
|
||||
|
||||
public void listDirectory() throws AndrolibException {
|
||||
File dir = getDirectory();
|
||||
if (dir == null) {
|
||||
LOGGER.severe("No framework directory found. Nothing to list.");
|
||||
return;
|
||||
}
|
||||
|
||||
for (File file : dir.listFiles()) {
|
||||
if (file.isFile() && file.getName().endsWith(".apk")) {
|
||||
LOGGER.info(file.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void publicizeResources(File arscFile) throws AndrolibException {
|
||||
byte[] data = new byte[(int) arscFile.length()];
|
||||
|
||||
try (
|
||||
InputStream in = Files.newInputStream(arscFile.toPath());
|
||||
OutputStream out = Files.newOutputStream(arscFile.toPath())
|
||||
) {
|
||||
try (InputStream in = Files.newInputStream(arscFile.toPath())) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
in.read(data);
|
||||
publicizeResources(data);
|
||||
} catch (IOException ex){
|
||||
throw new AndrolibException(ex);
|
||||
}
|
||||
|
||||
publicizeResources(data);
|
||||
|
||||
try (OutputStream out = Files.newOutputStream(arscFile.toPath())) {
|
||||
out.write(data);
|
||||
} catch (IOException ex){
|
||||
throw new AndrolibException(ex);
|
||||
@@ -169,12 +159,12 @@ public class Framework {
|
||||
return mDirectory;
|
||||
}
|
||||
|
||||
public File getApkFile(int id, String frameTag) throws AndrolibException {
|
||||
public File getApkFile(int id, String tag) throws AndrolibException {
|
||||
File dir = getDirectory();
|
||||
File apk;
|
||||
|
||||
if (frameTag != null) {
|
||||
apk = new File(dir, id + "-" + frameTag + ".apk");
|
||||
if (tag != null) {
|
||||
apk = new File(dir, id + "-" + tag + ".apk");
|
||||
if (apk.exists()) {
|
||||
return apk;
|
||||
}
|
||||
@@ -201,6 +191,24 @@ public class Framework {
|
||||
return getClass().getResourceAsStream("/prebuilt/android-framework.jar");
|
||||
}
|
||||
|
||||
public List<File> listDirectory() throws AndrolibException {
|
||||
List<File> files = new ArrayList<>();
|
||||
|
||||
File dir = getDirectory();
|
||||
if (dir == null) {
|
||||
LOGGER.severe("No framework directory found. Nothing to list.");
|
||||
return files;
|
||||
}
|
||||
|
||||
for (File file : dir.listFiles()) {
|
||||
if (file.isFile() && file.getName().endsWith(".apk")) {
|
||||
files.add(file);
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
public void emptyDirectory() throws AndrolibException {
|
||||
File dir = getDirectory();
|
||||
File apk = new File(dir, "1.apk");
|
||||
@@ -212,7 +220,7 @@ public class Framework {
|
||||
|
||||
File[] files = dir.listFiles();
|
||||
|
||||
if (apk.exists() && files.length > 1 && !mConfig.isForceDeleteFramework()) {
|
||||
if (files.length > 1 && !mConfig.isForced()) {
|
||||
LOGGER.warning("More than default framework detected. Please run command with `--force` parameter to wipe framework directory.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -17,8 +17,11 @@
|
||||
package brut.androlib.res;
|
||||
|
||||
import brut.androlib.Config;
|
||||
import brut.androlib.apk.ApkInfo;
|
||||
import brut.androlib.exceptions.AndrolibException;
|
||||
import brut.androlib.meta.ApkInfo;
|
||||
import brut.androlib.meta.PackageInfo;
|
||||
import brut.androlib.meta.SdkInfo;
|
||||
import brut.androlib.meta.VersionInfo;
|
||||
import brut.androlib.res.data.*;
|
||||
import brut.androlib.res.decoder.*;
|
||||
import brut.androlib.res.xml.ResValuesXmlSerializable;
|
||||
@@ -66,98 +69,12 @@ public class ResourcesDecoder {
|
||||
return mResFileMapping;
|
||||
}
|
||||
|
||||
public void loadMainPkg() throws AndrolibException {
|
||||
mResTable.loadMainPkg(mApkInfo.getApkFile());
|
||||
}
|
||||
|
||||
public void decodeManifest(File apkDir) throws AndrolibException {
|
||||
if (!mApkInfo.hasManifest()) {
|
||||
return;
|
||||
}
|
||||
|
||||
AXmlResourceParser axmlParser = new AndroidManifestResourceParser(mResTable);
|
||||
XmlSerializer xmlSerializer = newXmlSerializer();
|
||||
ResStreamDecoder fileDecoder = new AndroidManifestPullStreamDecoder(axmlParser, xmlSerializer);
|
||||
|
||||
Directory inDir, outDir;
|
||||
try {
|
||||
inDir = mApkInfo.getApkFile().getDirectory();
|
||||
outDir = new ExtFile(apkDir).getDirectory();
|
||||
|
||||
if (mApkInfo.hasResources()) {
|
||||
LOGGER.info("Decoding AndroidManifest.xml with resources...");
|
||||
} else {
|
||||
LOGGER.info("Decoding AndroidManifest.xml with only framework resources...");
|
||||
}
|
||||
|
||||
try (
|
||||
InputStream in = inDir.getFileInput("AndroidManifest.xml");
|
||||
OutputStream out = outDir.getFileOutput("AndroidManifest.xml")
|
||||
) {
|
||||
fileDecoder.decode(in, out);
|
||||
}
|
||||
} catch (DirectoryException | IOException ex) {
|
||||
throw new AndrolibException(ex);
|
||||
}
|
||||
|
||||
File manifest = new File(apkDir, "AndroidManifest.xml");
|
||||
|
||||
if (mApkInfo.hasResources() && !mConfig.isAnalysisMode()) {
|
||||
// Remove versionName / versionCode (aapt API 16)
|
||||
//
|
||||
// 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(manifest);
|
||||
|
||||
ResXmlUtils.removeManifestVersions(manifest);
|
||||
|
||||
// update apk info
|
||||
mApkInfo.packageInfo.forcedPackageId = String.valueOf(mResTable.getPackageId());
|
||||
}
|
||||
|
||||
// record feature flags
|
||||
String[] featureFlags = ResXmlUtils.pullManifestFeatureFlags(manifest);
|
||||
if (featureFlags != null) {
|
||||
for (String flag : featureFlags) {
|
||||
mApkInfo.addFeatureFlag(flag, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void updateApkInfo(File apkDir) throws AndrolibException {
|
||||
mResTable.initApkInfo(mApkInfo, apkDir);
|
||||
}
|
||||
|
||||
private void adjustPackageManifest(File manifest) throws AndrolibException {
|
||||
// compare resources.arsc package name to the one present in AndroidManifest
|
||||
ResPackage resPackage = mResTable.getCurrentResPackage();
|
||||
String pkgOriginal = resPackage.getName();
|
||||
String pkgRenamed = mResTable.getPackageRenamed();
|
||||
|
||||
mResTable.setPackageId(resPackage.getId());
|
||||
mResTable.setPackageOriginal(pkgOriginal);
|
||||
|
||||
// 1) Check if pkgOriginal is null (empty resources.arsc)
|
||||
// 2) Check if pkgRenamed is null
|
||||
// 3) Check if pkgOriginal === mPackageRenamed
|
||||
// 4) Check if pkgOriginal is ignored via IGNORED_PACKAGES
|
||||
if (pkgOriginal == null || pkgRenamed == null || pkgOriginal.equals(pkgRenamed)
|
||||
|| IGNORED_PACKAGES.contains(pkgOriginal)) {
|
||||
LOGGER.info("Regular manifest package...");
|
||||
} else {
|
||||
LOGGER.info("Renamed manifest package found! Replacing " + pkgRenamed + " with " + pkgOriginal);
|
||||
ResXmlUtils.renameManifestPackage(manifest, pkgOriginal);
|
||||
}
|
||||
}
|
||||
|
||||
public void decodeResources(File apkDir) throws AndrolibException {
|
||||
if (!mApkInfo.hasResources()) {
|
||||
return;
|
||||
}
|
||||
|
||||
loadMainPkg();
|
||||
mResTable.loadMainPackage();
|
||||
|
||||
ResStreamDecoderContainer decoders = new ResStreamDecoderContainer();
|
||||
decoders.setDecoder("raw", new ResRawStreamDecoder());
|
||||
@@ -177,20 +94,20 @@ public class ResourcesDecoder {
|
||||
throw new AndrolibException(ex);
|
||||
}
|
||||
|
||||
for (ResPackage pkg : mResTable.listMainPackages()) {
|
||||
LOGGER.info("Decoding file-resources...");
|
||||
for (ResResource res : pkg.listFiles()) {
|
||||
fileDecoder.decode(res, inDir, outDir, mResFileMapping);
|
||||
}
|
||||
ResPackage pkg = mResTable.getMainPackage();
|
||||
|
||||
LOGGER.info("Decoding values */* XMLs...");
|
||||
for (ResValuesFile valuesFile : pkg.listValuesFiles()) {
|
||||
generateValuesFile(valuesFile, outDir, xmlSerializer);
|
||||
}
|
||||
|
||||
generatePublicXml(pkg, outDir, xmlSerializer);
|
||||
LOGGER.info("Decoding file-resources...");
|
||||
for (ResResource res : pkg.listFiles()) {
|
||||
fileDecoder.decode(res, inDir, outDir, mResFileMapping);
|
||||
}
|
||||
|
||||
LOGGER.info("Decoding values */* XMLs...");
|
||||
for (ResValuesFile valuesFile : pkg.listValuesFiles()) {
|
||||
generateValuesFile(valuesFile, outDir, xmlSerializer);
|
||||
}
|
||||
|
||||
generatePublicXml(pkg, outDir, xmlSerializer);
|
||||
|
||||
AndrolibException decodeError = axmlParser.getFirstError();
|
||||
if (decodeError != null) {
|
||||
throw decodeError;
|
||||
@@ -257,4 +174,113 @@ public class ResourcesDecoder {
|
||||
throw new AndrolibException("Could not generate public.xml file", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void decodeManifest(File apkDir) throws AndrolibException {
|
||||
if (!mApkInfo.hasManifest()) {
|
||||
return;
|
||||
}
|
||||
|
||||
AXmlResourceParser axmlParser = new AndroidManifestResourceParser(mResTable);
|
||||
XmlSerializer xmlSerializer = newXmlSerializer();
|
||||
ResStreamDecoder fileDecoder = new AndroidManifestPullStreamDecoder(axmlParser, xmlSerializer);
|
||||
|
||||
Directory inDir, outDir;
|
||||
try {
|
||||
inDir = mApkInfo.getApkFile().getDirectory();
|
||||
outDir = new ExtFile(apkDir).getDirectory();
|
||||
|
||||
if (mResTable.isMainPackageLoaded()) {
|
||||
LOGGER.info("Decoding AndroidManifest.xml with resources...");
|
||||
} else {
|
||||
LOGGER.info("Decoding AndroidManifest.xml with only framework resources...");
|
||||
}
|
||||
|
||||
try (
|
||||
InputStream in = inDir.getFileInput("AndroidManifest.xml");
|
||||
OutputStream out = outDir.getFileOutput("AndroidManifest.xml")
|
||||
) {
|
||||
fileDecoder.decode(in, out);
|
||||
}
|
||||
} catch (DirectoryException | IOException ex) {
|
||||
throw new AndrolibException(ex);
|
||||
}
|
||||
|
||||
File manifest = new File(apkDir, "AndroidManifest.xml");
|
||||
|
||||
if (mResTable.isMainPackageLoaded()) {
|
||||
mResTable.updateApkInfo();
|
||||
|
||||
// resolve sdkInfo from resources
|
||||
SdkInfo sdkInfo = mApkInfo.getSdkInfo();
|
||||
if (!sdkInfo.isEmpty()) {
|
||||
String minSdkVersion = sdkInfo.getMinSdkVersion();
|
||||
if (minSdkVersion != null) {
|
||||
String refValue = ResXmlUtils.pullValueFromIntegers(apkDir, minSdkVersion);
|
||||
if (refValue != null) {
|
||||
sdkInfo.setMinSdkVersion(refValue);
|
||||
}
|
||||
}
|
||||
String targetSdkVersion = sdkInfo.getTargetSdkVersion();
|
||||
if (targetSdkVersion != null) {
|
||||
String refValue = ResXmlUtils.pullValueFromIntegers(apkDir, targetSdkVersion);
|
||||
if (refValue != null) {
|
||||
sdkInfo.setTargetSdkVersion(refValue);
|
||||
}
|
||||
}
|
||||
String maxSdkVersion = sdkInfo.getMaxSdkVersion();
|
||||
if (maxSdkVersion != null) {
|
||||
String refValue = ResXmlUtils.pullValueFromIntegers(apkDir, maxSdkVersion);
|
||||
if (refValue != null) {
|
||||
sdkInfo.setMaxSdkVersion(refValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// resolve versionInfo from resources
|
||||
VersionInfo versionInfo = mApkInfo.getVersionInfo();
|
||||
if (!versionInfo.isEmpty()) {
|
||||
String versionName = versionInfo.getVersionName();
|
||||
if (versionName != null) {
|
||||
String refValue = ResXmlUtils.pullValueFromStrings(apkDir, versionName);
|
||||
if (refValue != null) {
|
||||
versionInfo.setVersionName(refValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!mConfig.isAnalysisMode()) {
|
||||
// check if manifest package has to be renamed to main resource package
|
||||
PackageInfo packageInfo = mApkInfo.getPackageInfo();
|
||||
String manifestPackage = packageInfo.getRenameManifestPackage();
|
||||
String mainResPackage = mResTable.getMainPackage().getName();
|
||||
|
||||
if (mainResPackage != null && !mainResPackage.equals(manifestPackage)
|
||||
&& !IGNORED_PACKAGES.contains(mainResPackage)) {
|
||||
LOGGER.info("Renaming manifest package from " + manifestPackage + " to " + mainResPackage + "...");
|
||||
ResXmlUtils.renameManifestPackage(manifest, mainResPackage);
|
||||
} else {
|
||||
// renaming not needed: main resource package is null/identical/ignored
|
||||
packageInfo.setRenameManifestPackage(null);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// renaming not possible: manifest decoded without resources
|
||||
mApkInfo.getPackageInfo().setRenameManifestPackage(null);
|
||||
}
|
||||
|
||||
if (!mConfig.isAnalysisMode()) {
|
||||
// remove versionCode and versionName,
|
||||
// it will be passed as a parameter to aapt via apktool.yml
|
||||
ResXmlUtils.removeManifestVersions(manifest);
|
||||
}
|
||||
|
||||
// record feature flags
|
||||
String[] flags = ResXmlUtils.pullManifestFeatureFlags(manifest);
|
||||
if (flags != null) {
|
||||
Map<String, Boolean> featureFlags = mApkInfo.getFeatureFlags();
|
||||
for (String flag : flags) {
|
||||
featureFlags.put(flag, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,63 +21,64 @@ import java.util.logging.Logger;
|
||||
public class ResConfigFlags {
|
||||
private static final Logger LOGGER = Logger.getLogger(ResConfigFlags.class.getName());
|
||||
|
||||
public static final byte SDK_BASE = 1;
|
||||
public static final byte SDK_BASE_1_1 = 2;
|
||||
public static final byte SDK_CUPCAKE = 3;
|
||||
public static final byte SDK_DONUT = 4;
|
||||
public static final byte SDK_ECLAIR = 5;
|
||||
public static final byte SDK_ECLAIR_0_1 = 6;
|
||||
public static final byte SDK_ECLAIR_MR1 = 7;
|
||||
public static final byte SDK_FROYO = 8;
|
||||
public static final byte SDK_GINGERBREAD = 9;
|
||||
public static final byte SDK_GINGERBREAD_MR1 = 10;
|
||||
public static final byte SDK_HONEYCOMB = 11;
|
||||
public static final byte SDK_HONEYCOMB_MR1 = 12;
|
||||
public static final byte SDK_HONEYCOMB_MR2 = 13;
|
||||
public static final byte SDK_ICE_CREAM_SANDWICH = 14;
|
||||
public static final byte SDK_ICE_CREAM_SANDWICH_MR1 = 15;
|
||||
public static final byte SDK_JELLY_BEAN = 16;
|
||||
public static final byte SDK_JELLY_BEAN_MR1 = 17;
|
||||
public static final byte SDK_JELLY_BEAN_MR2 = 18;
|
||||
public static final byte SDK_KITKAT = 19;
|
||||
public static final byte SDK_LOLLIPOP = 21;
|
||||
public static final byte SDK_LOLLIPOP_MR1 = 22;
|
||||
public static final byte SDK_MNC = 23;
|
||||
public static final byte SDK_NOUGAT = 24;
|
||||
public static final byte SDK_NOUGAT_MR1 = 25;
|
||||
public static final byte SDK_OREO = 26;
|
||||
public static final byte SDK_OREO_MR1 = 27;
|
||||
public static final byte SDK_P = 28;
|
||||
public static final byte SDK_Q = 29;
|
||||
public static final byte SDK_R = 30;
|
||||
public static final byte SDK_S = 31;
|
||||
public static final byte SDK_S_V2 = 32;
|
||||
public static final byte SDK_TIRAMISU = 33;
|
||||
public static final byte SDK_UPSIDEDOWN_CAKE = 34;
|
||||
public static final byte SDK_VANILLA_ICE_CREAM = 35;
|
||||
public static final int SDK_BASE = 1;
|
||||
public static final int SDK_BASE_1_1 = 2;
|
||||
public static final int SDK_CUPCAKE = 3;
|
||||
public static final int SDK_DONUT = 4;
|
||||
public static final int SDK_ECLAIR = 5;
|
||||
public static final int SDK_ECLAIR_0_1 = 6;
|
||||
public static final int SDK_ECLAIR_MR1 = 7;
|
||||
public static final int SDK_FROYO = 8;
|
||||
public static final int SDK_GINGERBREAD = 9;
|
||||
public static final int SDK_GINGERBREAD_MR1 = 10;
|
||||
public static final int SDK_HONEYCOMB = 11;
|
||||
public static final int SDK_HONEYCOMB_MR1 = 12;
|
||||
public static final int SDK_HONEYCOMB_MR2 = 13;
|
||||
public static final int SDK_ICE_CREAM_SANDWICH = 14;
|
||||
public static final int SDK_ICE_CREAM_SANDWICH_MR1 = 15;
|
||||
public static final int SDK_JELLY_BEAN = 16;
|
||||
public static final int SDK_JELLY_BEAN_MR1 = 17;
|
||||
public static final int SDK_JELLY_BEAN_MR2 = 18;
|
||||
public static final int SDK_KITKAT = 19;
|
||||
public static final int SDK_LOLLIPOP = 21;
|
||||
public static final int SDK_LOLLIPOP_MR1 = 22;
|
||||
public static final int SDK_MNC = 23;
|
||||
public static final int SDK_NOUGAT = 24;
|
||||
public static final int SDK_NOUGAT_MR1 = 25;
|
||||
public static final int SDK_OREO = 26;
|
||||
public static final int SDK_OREO_MR1 = 27;
|
||||
public static final int SDK_P = 28;
|
||||
public static final int SDK_Q = 29;
|
||||
public static final int SDK_R = 30;
|
||||
public static final int SDK_S = 31;
|
||||
public static final int SDK_S_V2 = 32;
|
||||
public static final int SDK_TIRAMISU = 33;
|
||||
public static final int SDK_UPSIDEDOWN_CAKE = 34;
|
||||
public static final int SDK_VANILLA_ICE_CREAM = 35;
|
||||
|
||||
// AOSP changed Build IDs during QPR2 of API 34 (Upsidedown Cake), restarting at A.
|
||||
// However, API 35 (Vanilla) took letter A (AP2A), so we start at B.
|
||||
public static final byte SDK_BAKLAVA = 36;
|
||||
public static final int SDK_BAKLAVA = 36;
|
||||
|
||||
// AOSP has this as 10,000 for dev purposes.
|
||||
// platform_frameworks_base/commit/c7a1109a1fe0771d4c9b572dcf178e2779fc4f2d
|
||||
public static final int SDK_DEVELOPMENT = 10000;
|
||||
|
||||
public static final byte ORIENTATION_ANY = 0;
|
||||
public static final byte ORIENTATION_PORT = 1;
|
||||
public static final byte ORIENTATION_LAND = 2;
|
||||
public static final byte ORIENTATION_SQUARE = 3;
|
||||
public static final int MNC_ZERO = 0xFFFF;
|
||||
|
||||
public static final byte TOUCHSCREEN_ANY = 0;
|
||||
public static final byte TOUCHSCREEN_NOTOUCH = 1;
|
||||
public static final byte TOUCHSCREEN_STYLUS = 2;
|
||||
public static final byte TOUCHSCREEN_FINGER = 3;
|
||||
public static final int ORIENTATION_ANY = 0x00;
|
||||
public static final int ORIENTATION_PORT = 0x01;
|
||||
public static final int ORIENTATION_LAND = 0x02;
|
||||
public static final int ORIENTATION_SQUARE = 0x03;
|
||||
|
||||
public static final int TOUCHSCREEN_ANY = 0x00;
|
||||
public static final int TOUCHSCREEN_NOTOUCH = 0x01;
|
||||
public static final int TOUCHSCREEN_STYLUS = 0x02;
|
||||
public static final int TOUCHSCREEN_FINGER = 0x03;
|
||||
|
||||
public static final int DENSITY_DEFAULT = 0;
|
||||
public static final int DENSITY_LOW = 120;
|
||||
public static final int DENSITY_MEDIUM = 160;
|
||||
public static final int DENSITY_400 = 190;
|
||||
public static final int DENSITY_TV = 213;
|
||||
public static final int DENSITY_HIGH = 240;
|
||||
public static final int DENSITY_XHIGH = 320;
|
||||
@@ -86,132 +87,125 @@ public class ResConfigFlags {
|
||||
public static final int DENSITY_ANY = 0xFFFE;
|
||||
public static final int DENSITY_NONE = 0xFFFF;
|
||||
|
||||
public static final int MNC_ZERO = -1;
|
||||
public static final int KEYBOARD_ANY = 0x00;
|
||||
public static final int KEYBOARD_NOKEYS = 0x01;
|
||||
public static final int KEYBOARD_QWERTY = 0x02;
|
||||
public static final int KEYBOARD_12KEY = 0x03;
|
||||
|
||||
public static final short MASK_LAYOUTDIR = 0xc0;
|
||||
public static final short SCREENLAYOUT_LAYOUTDIR_ANY = 0x00;
|
||||
public static final short SCREENLAYOUT_LAYOUTDIR_LTR = 0x40;
|
||||
public static final short SCREENLAYOUT_LAYOUTDIR_RTL = 0x80;
|
||||
public static final short SCREENLAYOUT_LAYOUTDIR_SHIFT = 0x06;
|
||||
public static final int NAVIGATION_ANY = 0x00;
|
||||
public static final int NAVIGATION_NONAV = 0x01;
|
||||
public static final int NAVIGATION_DPAD = 0x02;
|
||||
public static final int NAVIGATION_TRACKBALL = 0x03;
|
||||
public static final int NAVIGATION_WHEEL = 0x04;
|
||||
|
||||
public static final short MASK_SCREENROUND = 0x03;
|
||||
public static final short SCREENLAYOUT_ROUND_ANY = 0;
|
||||
public static final short SCREENLAYOUT_ROUND_NO = 0x1;
|
||||
public static final short SCREENLAYOUT_ROUND_YES = 0x2;
|
||||
public static final int MASK_KEYSHIDDEN = 0x03;
|
||||
public static final int KEYSHIDDEN_ANY = 0x00;
|
||||
public static final int KEYSHIDDEN_NO = 0x01;
|
||||
public static final int KEYSHIDDEN_YES = 0x02;
|
||||
public static final int KEYSHIDDEN_SOFT = 0x03;
|
||||
|
||||
public static final byte GRAMMATICAL_GENDER_ANY = 0;
|
||||
public static final byte GRAMMATICAL_GENDER_NEUTER = 1;
|
||||
public static final byte GRAMMATICAL_GENDER_FEMININE = 2;
|
||||
public static final byte GRAMMATICAL_GENDER_MASCULINE = 3;
|
||||
public static final int SHIFT_NAVHIDDEN = 2;
|
||||
public static final int MASK_NAVHIDDEN = 0x03 << SHIFT_NAVHIDDEN; // 0x0C
|
||||
public static final int NAVHIDDEN_ANY = 0x00 << SHIFT_NAVHIDDEN; // 0x00
|
||||
public static final int NAVHIDDEN_NO = 0x01 << SHIFT_NAVHIDDEN; // 0x04
|
||||
public static final int NAVHIDDEN_YES = 0x02 << SHIFT_NAVHIDDEN; // 0x08
|
||||
|
||||
public static final byte KEYBOARD_ANY = 0;
|
||||
public static final byte KEYBOARD_NOKEYS = 1;
|
||||
public static final byte KEYBOARD_QWERTY = 2;
|
||||
public static final byte KEYBOARD_12KEY = 3;
|
||||
public static final int MASK_GRAMMATICAL_GENDER = 0x03;
|
||||
public static final int GRAMMATICAL_GENDER_ANY = 0x00;
|
||||
public static final int GRAMMATICAL_GENDER_NEUTER = 0x01;
|
||||
public static final int GRAMMATICAL_GENDER_FEMININE = 0x02;
|
||||
public static final int GRAMMATICAL_GENDER_MASCULINE = 0x03;
|
||||
|
||||
public static final byte NAVIGATION_ANY = 0;
|
||||
public static final byte NAVIGATION_NONAV = 1;
|
||||
public static final byte NAVIGATION_DPAD = 2;
|
||||
public static final byte NAVIGATION_TRACKBALL = 3;
|
||||
public static final byte NAVIGATION_WHEEL = 4;
|
||||
public static final int MASK_SCREENSIZE = 0x0F;
|
||||
public static final int SCREENSIZE_ANY = 0x00;
|
||||
public static final int SCREENSIZE_SMALL = 0x01;
|
||||
public static final int SCREENSIZE_NORMAL = 0x02;
|
||||
public static final int SCREENSIZE_LARGE = 0x03;
|
||||
public static final int SCREENSIZE_XLARGE = 0x04;
|
||||
|
||||
public static final byte MASK_KEYSHIDDEN = 0x3;
|
||||
public static final byte KEYSHIDDEN_ANY = 0x0;
|
||||
public static final byte KEYSHIDDEN_NO = 0x1;
|
||||
public static final byte KEYSHIDDEN_YES = 0x2;
|
||||
public static final byte KEYSHIDDEN_SOFT = 0x3;
|
||||
public static final int SHIFT_SCREENLONG = 4;
|
||||
public static final int MASK_SCREENLONG = 0x03 << SHIFT_SCREENLONG; // 0x30
|
||||
public static final int SCREENLONG_ANY = 0x00 << SHIFT_SCREENLONG; // 0x00
|
||||
public static final int SCREENLONG_NO = 0x01 << SHIFT_SCREENLONG; // 0x10
|
||||
public static final int SCREENLONG_YES = 0x02 << SHIFT_SCREENLONG; // 0x20
|
||||
|
||||
public static final byte MASK_NAVHIDDEN = 0xc;
|
||||
public static final byte NAVHIDDEN_ANY = 0x0;
|
||||
public static final byte NAVHIDDEN_NO = 0x4;
|
||||
public static final byte NAVHIDDEN_YES = 0x8;
|
||||
public static final int SHIFT_LAYOUTDIR = 6;
|
||||
public static final int MASK_LAYOUTDIR = 0x03 << SHIFT_LAYOUTDIR; // 0xC0
|
||||
public static final int LAYOUTDIR_ANY = 0x00 << SHIFT_LAYOUTDIR; // 0x00
|
||||
public static final int LAYOUTDIR_LTR = 0x01 << SHIFT_LAYOUTDIR; // 0x40
|
||||
public static final int LAYOUTDIR_RTL = 0x02 << SHIFT_LAYOUTDIR; // 0x80
|
||||
|
||||
public static final byte MASK_SCREENSIZE = 0x0f;
|
||||
public static final byte SCREENSIZE_ANY = 0x00;
|
||||
public static final byte SCREENSIZE_SMALL = 0x01;
|
||||
public static final byte SCREENSIZE_NORMAL = 0x02;
|
||||
public static final byte SCREENSIZE_LARGE = 0x03;
|
||||
public static final byte SCREENSIZE_XLARGE = 0x04;
|
||||
public static final int MASK_UI_MODE_TYPE = 0x0F;
|
||||
public static final int UI_MODE_TYPE_ANY = 0x00;
|
||||
public static final int UI_MODE_TYPE_NORMAL = 0x01;
|
||||
public static final int UI_MODE_TYPE_DESK = 0x02;
|
||||
public static final int UI_MODE_TYPE_CAR = 0x03;
|
||||
public static final int UI_MODE_TYPE_TELEVISION = 0x04;
|
||||
public static final int UI_MODE_TYPE_APPLIANCE = 0x05;
|
||||
public static final int UI_MODE_TYPE_WATCH = 0x06;
|
||||
public static final int UI_MODE_TYPE_VR_HEADSET = 0x07;
|
||||
public static final int UI_MODE_TYPE_GODZILLAUI = 0x0B; // MIUI
|
||||
public static final int UI_MODE_TYPE_SMALLUI = 0x0C; // MIUI
|
||||
public static final int UI_MODE_TYPE_MEDIUMUI = 0x0D; // MIUI
|
||||
public static final int UI_MODE_TYPE_LARGEUI = 0x0E; // MIUI
|
||||
public static final int UI_MODE_TYPE_HUGEUI = 0x0F; // MIUI
|
||||
|
||||
public static final byte MASK_SCREENLONG = 0x30;
|
||||
public static final byte SCREENLONG_ANY = 0x00;
|
||||
public static final byte SCREENLONG_NO = 0x10;
|
||||
public static final byte SCREENLONG_YES = 0x20;
|
||||
public static final int SHIFT_UI_MODE_NIGHT = 4;
|
||||
public static final int MASK_UI_MODE_NIGHT = 0x03 << SHIFT_UI_MODE_NIGHT; // 0x30
|
||||
public static final int UI_MODE_NIGHT_ANY = 0x00 << SHIFT_UI_MODE_NIGHT; // 0x00
|
||||
public static final int UI_MODE_NIGHT_NO = 0x01 << SHIFT_UI_MODE_NIGHT; // 0x10
|
||||
public static final int UI_MODE_NIGHT_YES = 0x02 << SHIFT_UI_MODE_NIGHT; // 0x20
|
||||
|
||||
public static final byte MASK_UI_MODE_TYPE = 0x0f;
|
||||
public static final byte UI_MODE_TYPE_ANY = 0x00;
|
||||
public static final byte UI_MODE_TYPE_NORMAL = 0x01;
|
||||
public static final byte UI_MODE_TYPE_DESK = 0x02;
|
||||
public static final byte UI_MODE_TYPE_CAR = 0x03;
|
||||
public static final byte UI_MODE_TYPE_TELEVISION = 0x04;
|
||||
public static final byte UI_MODE_TYPE_APPLIANCE = 0x05;
|
||||
public static final byte UI_MODE_TYPE_WATCH = 0x06;
|
||||
public static final byte UI_MODE_TYPE_VR_HEADSET = 0x07;
|
||||
public static final int MASK_SCREENROUND = 0x03;
|
||||
public static final int SCREENROUND_ANY = 0x00;
|
||||
public static final int SCREENROUND_NO = 0x01;
|
||||
public static final int SCREENROUND_YES = 0x02;
|
||||
|
||||
// start - miui
|
||||
public static final byte UI_MODE_TYPE_GODZILLAUI = 0x0b;
|
||||
public static final byte UI_MODE_TYPE_SMALLUI = 0x0c;
|
||||
public static final byte UI_MODE_TYPE_MEDIUMUI = 0x0d;
|
||||
public static final byte UI_MODE_TYPE_LARGEUI = 0x0e;
|
||||
public static final byte UI_MODE_TYPE_HUGEUI = 0x0f;
|
||||
// end - miui
|
||||
public static final int MASK_COLOR_MODE_WIDECG = 0x03;
|
||||
public static final int COLOR_MODE_WIDECG_ANY = 0x00;
|
||||
public static final int COLOR_MODE_WIDECG_NO = 0x01;
|
||||
public static final int COLOR_MODE_WIDECG_YES = 0x02;
|
||||
|
||||
public static final byte MASK_UI_MODE_NIGHT = 0x30;
|
||||
public static final byte UI_MODE_NIGHT_ANY = 0x00;
|
||||
public static final byte UI_MODE_NIGHT_NO = 0x10;
|
||||
public static final byte UI_MODE_NIGHT_YES = 0x20;
|
||||
public static final int SHIFT_COLOR_MODE_HDR = 2;
|
||||
public static final int MASK_COLOR_MODE_HDR = 0x03 << SHIFT_COLOR_MODE_HDR; // 0x0C
|
||||
public static final int COLOR_MODE_HDR_ANY = 0x00 << SHIFT_COLOR_MODE_HDR; // 0x00
|
||||
public static final int COLOR_MODE_HDR_NO = 0x01 << SHIFT_COLOR_MODE_HDR; // 0x04
|
||||
public static final int COLOR_MODE_HDR_YES = 0x02 << SHIFT_COLOR_MODE_HDR; // 0x08
|
||||
|
||||
public static final byte COLOR_HDR_MASK = 0xC;
|
||||
public static final byte COLOR_HDR_NO = 0x4;
|
||||
public static final byte COLOR_HDR_SHIFT = 0x2;
|
||||
public static final byte COLOR_HDR_UNDEFINED = 0x0;
|
||||
public static final byte COLOR_HDR_YES = 0x8;
|
||||
|
||||
public static final byte COLOR_UNDEFINED = 0x0;
|
||||
|
||||
public static final byte COLOR_WIDE_UNDEFINED = 0x0;
|
||||
public static final byte COLOR_WIDE_NO = 0x1;
|
||||
public static final byte COLOR_WIDE_YES = 0x2;
|
||||
public static final byte COLOR_WIDE_MASK = 0x3;
|
||||
|
||||
// TODO: Dirty static hack. This counter should be a part of ResPackage,
|
||||
// but it would be hard right now and this feature is very rarely used.
|
||||
private static int sErrCounter = 0;
|
||||
|
||||
private final short mMcc;
|
||||
private final short mMnc;
|
||||
private final char[] mLanguage;
|
||||
private final char[] mRegion;
|
||||
private final byte mOrientation;
|
||||
private final byte mTouchscreen;
|
||||
private final int mMcc;
|
||||
private final int mMnc;
|
||||
private final String mLanguage;
|
||||
private final String mRegion;
|
||||
private final int mOrientation;
|
||||
private final int mTouchscreen;
|
||||
private final int mDensity;
|
||||
private final byte mKeyboard;
|
||||
private final byte mNavigation;
|
||||
private final byte mInputFlags;
|
||||
private final byte mGrammaticalInflection;
|
||||
private final short mScreenWidth;
|
||||
private final short mScreenHeight;
|
||||
private final short mSdkVersion;
|
||||
private final byte mScreenLayout;
|
||||
private final byte mUiMode;
|
||||
private final short mSmallestScreenWidthDp;
|
||||
private final short mScreenWidthDp;
|
||||
private final short mScreenHeightDp;
|
||||
private final char[] mLocaleScript;
|
||||
private final char[] mLocaleVariant;
|
||||
private final byte mScreenLayout2;
|
||||
private final byte mColorMode;
|
||||
private final char[] mLocaleNumberingSystem;
|
||||
private final int mSize;
|
||||
private final boolean mIsInvalid;
|
||||
private final int mKeyboard;
|
||||
private final int mNavigation;
|
||||
private final int mInputFlags;
|
||||
private final int mGrammaticalInflection;
|
||||
private final int mScreenWidth;
|
||||
private final int mScreenHeight;
|
||||
private final int mSdkVersion;
|
||||
private final int mMinorVersion;
|
||||
private final int mScreenLayout;
|
||||
private final int mUiMode;
|
||||
private final int mSmallestScreenWidthDp;
|
||||
private final int mScreenWidthDp;
|
||||
private final int mScreenHeightDp;
|
||||
private final String mLocaleScript;
|
||||
private final String mLocaleVariant;
|
||||
private final int mScreenLayout2;
|
||||
private final int mColorMode;
|
||||
private final String mLocaleNumberingSystem;
|
||||
|
||||
private final String mQualifiers;
|
||||
private boolean mIsInvalid;
|
||||
|
||||
public ResConfigFlags() {
|
||||
mMcc = 0;
|
||||
mMnc = 0;
|
||||
mLanguage = new char[] { '\00', '\00' };
|
||||
mRegion = new char[] { '\00', '\00' };
|
||||
mLanguage = "";
|
||||
mRegion = "";
|
||||
mOrientation = ORIENTATION_ANY;
|
||||
mTouchscreen = TOUCHSCREEN_ANY;
|
||||
mDensity = DENSITY_DEFAULT;
|
||||
@@ -222,60 +216,26 @@ public class ResConfigFlags {
|
||||
mScreenWidth = 0;
|
||||
mScreenHeight = 0;
|
||||
mSdkVersion = 0;
|
||||
mScreenLayout = SCREENLONG_ANY | SCREENSIZE_ANY;
|
||||
mMinorVersion = 0;
|
||||
mScreenLayout = SCREENSIZE_ANY | SCREENLONG_ANY;
|
||||
mUiMode = UI_MODE_TYPE_ANY | UI_MODE_NIGHT_ANY;
|
||||
mSmallestScreenWidthDp = 0;
|
||||
mScreenWidthDp = 0;
|
||||
mScreenHeightDp = 0;
|
||||
mLocaleScript = null;
|
||||
mLocaleVariant = null;
|
||||
mLocaleScript = "";
|
||||
mLocaleVariant = "";
|
||||
mScreenLayout2 = 0;
|
||||
mColorMode = COLOR_WIDE_UNDEFINED;
|
||||
mLocaleNumberingSystem = null;
|
||||
mSize = 0;
|
||||
mIsInvalid = false;
|
||||
mColorMode = COLOR_MODE_WIDECG_ANY | COLOR_MODE_HDR_ANY;
|
||||
mLocaleNumberingSystem = "";
|
||||
mQualifiers = "";
|
||||
}
|
||||
|
||||
public ResConfigFlags(short mcc, short mnc, char[] language, char[] region, byte orientation,
|
||||
byte touchscreen, int density, byte keyboard, byte navigation, byte inputFlags,
|
||||
byte grammaticalInflection, short screenWidth, short screenHeight,
|
||||
short sdkVersion, byte screenLayout, byte uiMode, short smallestScreenWidthDp,
|
||||
short screenWidthDp, short screenHeightDp, char[] localeScript, char[] localeVariant,
|
||||
byte screenLayout2, byte colorMode, char[] localeNumberingSystem,
|
||||
int size, boolean isInvalid) {
|
||||
if (orientation < 0 || orientation > 3) {
|
||||
LOGGER.warning("Invalid orientation value: " + orientation);
|
||||
orientation = 0;
|
||||
isInvalid = true;
|
||||
}
|
||||
if (touchscreen < 0 || touchscreen > 3) {
|
||||
LOGGER.warning("Invalid touchscreen value: " + touchscreen);
|
||||
touchscreen = 0;
|
||||
isInvalid = true;
|
||||
}
|
||||
if (density < -1) {
|
||||
LOGGER.warning("Invalid density value: " + density);
|
||||
density = 0;
|
||||
isInvalid = true;
|
||||
}
|
||||
if (keyboard < 0 || keyboard > 3) {
|
||||
LOGGER.warning("Invalid keyboard value: " + keyboard);
|
||||
keyboard = 0;
|
||||
isInvalid = true;
|
||||
}
|
||||
if (navigation < 0 || navigation > 4) {
|
||||
LOGGER.warning("Invalid navigation value: " + navigation);
|
||||
navigation = 0;
|
||||
isInvalid = true;
|
||||
}
|
||||
if (localeScript != null && (localeScript.length == 0 || localeScript[0] == '\00')) {
|
||||
localeScript = null;
|
||||
}
|
||||
if (localeVariant != null && (localeVariant.length == 0 || localeVariant[0] == '\00')) {
|
||||
localeVariant = null;
|
||||
}
|
||||
|
||||
public ResConfigFlags(int mcc, int mnc, String language, String region, int orientation,
|
||||
int touchscreen, int density, int keyboard, int navigation, int inputFlags,
|
||||
int grammaticalInflection, int screenWidth, int screenHeight, int sdkVersion,
|
||||
int minorVersion, int screenLayout, int uiMode, int smallestScreenWidthDp,
|
||||
int screenWidthDp, int screenHeightDp, String localeScript, String localeVariant,
|
||||
int screenLayout2, int colorMode, String localeNumberingSystem) {
|
||||
mMcc = mcc;
|
||||
mMnc = mnc;
|
||||
mLanguage = language;
|
||||
@@ -290,6 +250,7 @@ public class ResConfigFlags {
|
||||
mScreenWidth = screenWidth;
|
||||
mScreenHeight = screenHeight;
|
||||
mSdkVersion = sdkVersion;
|
||||
mMinorVersion = minorVersion;
|
||||
mScreenLayout = screenLayout;
|
||||
mUiMode = uiMode;
|
||||
mSmallestScreenWidthDp = smallestScreenWidthDp;
|
||||
@@ -300,8 +261,6 @@ public class ResConfigFlags {
|
||||
mScreenLayout2 = screenLayout2;
|
||||
mColorMode = colorMode;
|
||||
mLocaleNumberingSystem = localeNumberingSystem;
|
||||
mSize = size;
|
||||
mIsInvalid = isInvalid;
|
||||
mQualifiers = generateQualifiers();
|
||||
}
|
||||
|
||||
@@ -309,30 +268,40 @@ public class ResConfigFlags {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (mMcc != 0) {
|
||||
sb.append("-mcc").append(String.format("%03d", mMcc));
|
||||
if (mMnc != MNC_ZERO) {
|
||||
if (mMnc != 0) {
|
||||
sb.append("-mnc");
|
||||
if (mSize <= 32) {
|
||||
if (mMnc > 0 && mMnc < 10) {
|
||||
sb.append(String.format("%02d", mMnc));
|
||||
} else {
|
||||
sb.append(String.format("%03d", mMnc));
|
||||
}
|
||||
} else {
|
||||
sb.append(mMnc);
|
||||
}
|
||||
}
|
||||
if (mMnc != 0) {
|
||||
sb.append("-mnc").append(String.format("%02d", mMnc == MNC_ZERO ? 0 : mMnc));
|
||||
}
|
||||
if (!mLanguage.isEmpty()) {
|
||||
if (mLocaleScript.isEmpty() && (mRegion.isEmpty() || mRegion.length() == 2)
|
||||
&& mLocaleVariant.isEmpty()/* && mLocaleNumberingSystem.isEmpty()*/) {
|
||||
// legacy format
|
||||
sb.append("-").append(mLanguage);
|
||||
if (!mRegion.isEmpty()) {
|
||||
sb.append("-r").append(mRegion);
|
||||
}
|
||||
} else {
|
||||
sb.append("-mnc00");
|
||||
}
|
||||
} else {
|
||||
if (mMnc != 0) {
|
||||
sb.append("-mnc").append(mMnc);
|
||||
// modified BCP 47 format
|
||||
sb.append("-b+");
|
||||
sb.append(mLanguage);
|
||||
if (!mLocaleScript.isEmpty()) {
|
||||
sb.append("+").append(mLocaleScript);
|
||||
}
|
||||
if (!mRegion.isEmpty()) {
|
||||
sb.append("+").append(mRegion);
|
||||
}
|
||||
if (!mLocaleVariant.isEmpty()) {
|
||||
sb.append("+").append(mLocaleVariant);
|
||||
}
|
||||
// aapt does not support locale extensions
|
||||
//if (!mLocaleNumberingSystem.isEmpty()) {
|
||||
// sb.append("+u+nu+").append(mLocaleNumberingSystem);
|
||||
//}
|
||||
}
|
||||
}
|
||||
sb.append(getLocaleString());
|
||||
|
||||
switch (mGrammaticalInflection) {
|
||||
switch (mGrammaticalInflection & MASK_GRAMMATICAL_GENDER) {
|
||||
case GRAMMATICAL_GENDER_ANY:
|
||||
break;
|
||||
case GRAMMATICAL_GENDER_NEUTER:
|
||||
sb.append("-neuter");
|
||||
break;
|
||||
@@ -342,14 +311,23 @@ public class ResConfigFlags {
|
||||
case GRAMMATICAL_GENDER_MASCULINE:
|
||||
sb.append("-masculine");
|
||||
break;
|
||||
default:
|
||||
sb.append("-grammaticalGender=").append(mGrammaticalInflection & MASK_GRAMMATICAL_GENDER);
|
||||
mIsInvalid = true;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (mScreenLayout & MASK_LAYOUTDIR) {
|
||||
case SCREENLAYOUT_LAYOUTDIR_RTL:
|
||||
case LAYOUTDIR_ANY:
|
||||
break;
|
||||
case LAYOUTDIR_LTR:
|
||||
sb.append("-ldltr");
|
||||
break;
|
||||
case LAYOUTDIR_RTL:
|
||||
sb.append("-ldrtl");
|
||||
break;
|
||||
case SCREENLAYOUT_LAYOUTDIR_LTR:
|
||||
sb.append("-ldltr");
|
||||
default:
|
||||
sb.append("-layoutDir=").append(mScreenLayout & MASK_LAYOUTDIR);
|
||||
mIsInvalid = true;
|
||||
break;
|
||||
}
|
||||
if (mSmallestScreenWidthDp != 0) {
|
||||
@@ -362,6 +340,8 @@ public class ResConfigFlags {
|
||||
sb.append("-h").append(mScreenHeightDp).append("dp");
|
||||
}
|
||||
switch (mScreenLayout & MASK_SCREENSIZE) {
|
||||
case SCREENSIZE_ANY:
|
||||
break;
|
||||
case SCREENSIZE_SMALL:
|
||||
sb.append("-small");
|
||||
break;
|
||||
@@ -374,40 +354,70 @@ public class ResConfigFlags {
|
||||
case SCREENSIZE_XLARGE:
|
||||
sb.append("-xlarge");
|
||||
break;
|
||||
default:
|
||||
sb.append("-screenSize=").append(mScreenLayout & MASK_SCREENSIZE);
|
||||
mIsInvalid = true;
|
||||
break;
|
||||
}
|
||||
switch (mScreenLayout & MASK_SCREENLONG) {
|
||||
case SCREENLONG_YES:
|
||||
sb.append("-long");
|
||||
case SCREENLONG_ANY:
|
||||
break;
|
||||
case SCREENLONG_NO:
|
||||
sb.append("-notlong");
|
||||
break;
|
||||
case SCREENLONG_YES:
|
||||
sb.append("-long");
|
||||
break;
|
||||
default:
|
||||
sb.append("-screenLong=").append(mScreenLayout & MASK_SCREENLONG);
|
||||
mIsInvalid = true;
|
||||
break;
|
||||
}
|
||||
switch (mScreenLayout2 & MASK_SCREENROUND) {
|
||||
case SCREENLAYOUT_ROUND_NO:
|
||||
case SCREENROUND_ANY:
|
||||
break;
|
||||
case SCREENROUND_NO:
|
||||
sb.append("-notround");
|
||||
break;
|
||||
case SCREENLAYOUT_ROUND_YES:
|
||||
case SCREENROUND_YES:
|
||||
sb.append("-round");
|
||||
break;
|
||||
}
|
||||
switch (mColorMode & COLOR_HDR_MASK) {
|
||||
case COLOR_HDR_YES:
|
||||
sb.append("-highdr");
|
||||
break;
|
||||
case COLOR_HDR_NO:
|
||||
sb.append("-lowdr");
|
||||
default:
|
||||
sb.append("-screenRound=").append(mScreenLayout2 & MASK_SCREENROUND);
|
||||
mIsInvalid = true;
|
||||
break;
|
||||
}
|
||||
switch (mColorMode & COLOR_WIDE_MASK) {
|
||||
case COLOR_WIDE_YES:
|
||||
switch (mColorMode & MASK_COLOR_MODE_WIDECG) {
|
||||
case COLOR_MODE_WIDECG_ANY:
|
||||
break;
|
||||
case COLOR_MODE_WIDECG_NO:
|
||||
sb.append("-nowidecg");
|
||||
break;
|
||||
case COLOR_MODE_WIDECG_YES:
|
||||
sb.append("-widecg");
|
||||
break;
|
||||
case COLOR_WIDE_NO:
|
||||
sb.append("-nowidecg");
|
||||
default:
|
||||
sb.append("-colorModeWideCG=").append(mColorMode & MASK_COLOR_MODE_WIDECG);
|
||||
mIsInvalid = true;
|
||||
break;
|
||||
}
|
||||
switch (mColorMode & MASK_COLOR_MODE_HDR) {
|
||||
case COLOR_MODE_HDR_ANY:
|
||||
break;
|
||||
case COLOR_MODE_HDR_NO:
|
||||
sb.append("-lowdr");
|
||||
break;
|
||||
case COLOR_MODE_HDR_YES:
|
||||
sb.append("-highdr");
|
||||
break;
|
||||
default:
|
||||
sb.append("-colorModeHdr=").append(mColorMode & MASK_COLOR_MODE_HDR);
|
||||
mIsInvalid = true;
|
||||
break;
|
||||
}
|
||||
switch (mOrientation) {
|
||||
case ORIENTATION_ANY:
|
||||
break;
|
||||
case ORIENTATION_PORT:
|
||||
sb.append("-port");
|
||||
break;
|
||||
@@ -417,32 +427,24 @@ public class ResConfigFlags {
|
||||
case ORIENTATION_SQUARE:
|
||||
sb.append("-square");
|
||||
break;
|
||||
default:
|
||||
sb.append("-orientation=").append(mOrientation);
|
||||
mIsInvalid = true;
|
||||
break;
|
||||
}
|
||||
switch (mUiMode & MASK_UI_MODE_TYPE) {
|
||||
case UI_MODE_TYPE_CAR:
|
||||
sb.append("-car");
|
||||
case UI_MODE_TYPE_ANY:
|
||||
case UI_MODE_TYPE_NORMAL:
|
||||
break;
|
||||
case UI_MODE_TYPE_DESK:
|
||||
sb.append("-desk");
|
||||
break;
|
||||
case UI_MODE_TYPE_CAR:
|
||||
sb.append("-car");
|
||||
break;
|
||||
case UI_MODE_TYPE_TELEVISION:
|
||||
sb.append("-television");
|
||||
break;
|
||||
case UI_MODE_TYPE_SMALLUI:
|
||||
sb.append("-smallui");
|
||||
break;
|
||||
case UI_MODE_TYPE_MEDIUMUI:
|
||||
sb.append("-mediumui");
|
||||
break;
|
||||
case UI_MODE_TYPE_LARGEUI:
|
||||
sb.append("-largeui");
|
||||
break;
|
||||
case UI_MODE_TYPE_GODZILLAUI:
|
||||
sb.append("-godzillaui");
|
||||
break;
|
||||
case UI_MODE_TYPE_HUGEUI:
|
||||
sb.append("-hugeui");
|
||||
break;
|
||||
case UI_MODE_TYPE_APPLIANCE:
|
||||
sb.append("-appliance");
|
||||
break;
|
||||
@@ -452,14 +454,39 @@ public class ResConfigFlags {
|
||||
case UI_MODE_TYPE_VR_HEADSET:
|
||||
sb.append("-vrheadset");
|
||||
break;
|
||||
case UI_MODE_TYPE_GODZILLAUI:
|
||||
sb.append("-godzillaui");
|
||||
break;
|
||||
case UI_MODE_TYPE_SMALLUI:
|
||||
sb.append("-smallui");
|
||||
break;
|
||||
case UI_MODE_TYPE_MEDIUMUI:
|
||||
sb.append("-mediumui");
|
||||
break;
|
||||
case UI_MODE_TYPE_LARGEUI:
|
||||
sb.append("-largeui");
|
||||
break;
|
||||
case UI_MODE_TYPE_HUGEUI:
|
||||
sb.append("-hugeui");
|
||||
break;
|
||||
default:
|
||||
sb.append("-uiModeType=").append(mUiMode & MASK_UI_MODE_TYPE);
|
||||
mIsInvalid = true;
|
||||
break;
|
||||
}
|
||||
switch (mUiMode & MASK_UI_MODE_NIGHT) {
|
||||
case UI_MODE_NIGHT_YES:
|
||||
sb.append("-night");
|
||||
case UI_MODE_NIGHT_ANY:
|
||||
break;
|
||||
case UI_MODE_NIGHT_NO:
|
||||
sb.append("-notnight");
|
||||
break;
|
||||
case UI_MODE_NIGHT_YES:
|
||||
sb.append("-night");
|
||||
break;
|
||||
default:
|
||||
sb.append("-uiModeNight=").append(mUiMode & MASK_UI_MODE_NIGHT);
|
||||
mIsInvalid = true;
|
||||
break;
|
||||
}
|
||||
switch (mDensity) {
|
||||
case DENSITY_DEFAULT:
|
||||
@@ -470,12 +497,12 @@ public class ResConfigFlags {
|
||||
case DENSITY_MEDIUM:
|
||||
sb.append("-mdpi");
|
||||
break;
|
||||
case DENSITY_HIGH:
|
||||
sb.append("-hdpi");
|
||||
break;
|
||||
case DENSITY_TV:
|
||||
sb.append("-tvdpi");
|
||||
break;
|
||||
case DENSITY_HIGH:
|
||||
sb.append("-hdpi");
|
||||
break;
|
||||
case DENSITY_XHIGH:
|
||||
sb.append("-xhdpi");
|
||||
break;
|
||||
@@ -493,8 +520,11 @@ public class ResConfigFlags {
|
||||
break;
|
||||
default:
|
||||
sb.append('-').append(mDensity).append("dpi");
|
||||
break;
|
||||
}
|
||||
switch (mTouchscreen) {
|
||||
case TOUCHSCREEN_ANY:
|
||||
break;
|
||||
case TOUCHSCREEN_NOTOUCH:
|
||||
sb.append("-notouch");
|
||||
break;
|
||||
@@ -504,8 +534,14 @@ public class ResConfigFlags {
|
||||
case TOUCHSCREEN_FINGER:
|
||||
sb.append("-finger");
|
||||
break;
|
||||
default:
|
||||
sb.append("-touchscreen=").append(mTouchscreen);
|
||||
mIsInvalid = true;
|
||||
break;
|
||||
}
|
||||
switch (mInputFlags & MASK_KEYSHIDDEN) {
|
||||
case KEYSHIDDEN_ANY:
|
||||
break;
|
||||
case KEYSHIDDEN_NO:
|
||||
sb.append("-keysexposed");
|
||||
break;
|
||||
@@ -515,8 +551,14 @@ public class ResConfigFlags {
|
||||
case KEYSHIDDEN_SOFT:
|
||||
sb.append("-keyssoft");
|
||||
break;
|
||||
default:
|
||||
sb.append("-keysHidden=").append(mInputFlags & MASK_KEYSHIDDEN);
|
||||
mIsInvalid = true;
|
||||
break;
|
||||
}
|
||||
switch (mKeyboard) {
|
||||
case KEYBOARD_ANY:
|
||||
break;
|
||||
case KEYBOARD_NOKEYS:
|
||||
sb.append("-nokeys");
|
||||
break;
|
||||
@@ -526,16 +568,28 @@ public class ResConfigFlags {
|
||||
case KEYBOARD_12KEY:
|
||||
sb.append("-12key");
|
||||
break;
|
||||
default:
|
||||
sb.append("-keyboard=").append(mKeyboard);
|
||||
mIsInvalid = true;
|
||||
break;
|
||||
}
|
||||
switch (mInputFlags & MASK_NAVHIDDEN) {
|
||||
case NAVHIDDEN_ANY:
|
||||
break;
|
||||
case NAVHIDDEN_NO:
|
||||
sb.append("-navexposed");
|
||||
break;
|
||||
case NAVHIDDEN_YES:
|
||||
sb.append("-navhidden");
|
||||
break;
|
||||
default:
|
||||
sb.append("-navHidden=").append(mInputFlags & MASK_NAVHIDDEN);
|
||||
mIsInvalid = true;
|
||||
break;
|
||||
}
|
||||
switch (mNavigation) {
|
||||
case NAVIGATION_ANY:
|
||||
break;
|
||||
case NAVIGATION_NONAV:
|
||||
sb.append("-nonav");
|
||||
break;
|
||||
@@ -548,29 +602,31 @@ public class ResConfigFlags {
|
||||
case NAVIGATION_WHEEL:
|
||||
sb.append("-wheel");
|
||||
break;
|
||||
default:
|
||||
sb.append("-navigation=").append(mNavigation);
|
||||
mIsInvalid = true;
|
||||
break;
|
||||
}
|
||||
if (mScreenWidth != 0 && mScreenHeight != 0) {
|
||||
if (mScreenWidth > mScreenHeight) {
|
||||
sb.append(String.format("-%dx%d", mScreenWidth, mScreenHeight));
|
||||
} else {
|
||||
sb.append(String.format("-%dx%d", mScreenHeight, mScreenWidth));
|
||||
}
|
||||
sb.append('-').append(mScreenWidth).append('x').append(mScreenHeight);
|
||||
}
|
||||
if (mSdkVersion > 0 && mSdkVersion >= getNaturalSdkVersionRequirement()) {
|
||||
if (mSdkVersion != 0 && mSdkVersion >= getNaturalSdkVersionRequirement()) {
|
||||
sb.append("-v").append(mSdkVersion);
|
||||
}
|
||||
if (mIsInvalid) {
|
||||
sb.append("-ERR").append(sErrCounter++);
|
||||
// aapt does not support minor version
|
||||
//if (mMinorVersion != 0) {
|
||||
// sb.append('.').append(mMinorVersion);
|
||||
//}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private short getNaturalSdkVersionRequirement() {
|
||||
if (mGrammaticalInflection != 0) {
|
||||
private int getNaturalSdkVersionRequirement() {
|
||||
if ((mGrammaticalInflection & MASK_GRAMMATICAL_GENDER) != 0) {
|
||||
return SDK_UPSIDEDOWN_CAKE;
|
||||
}
|
||||
if ((mUiMode & MASK_UI_MODE_TYPE) == UI_MODE_TYPE_VR_HEADSET || (mColorMode & COLOR_WIDE_MASK) != 0 || ((mColorMode & COLOR_HDR_MASK) != 0)) {
|
||||
if ((mUiMode & MASK_UI_MODE_TYPE) == UI_MODE_TYPE_VR_HEADSET
|
||||
|| (mColorMode & MASK_COLOR_MODE_WIDECG) != 0 || ((mColorMode & MASK_COLOR_MODE_HDR) != 0)) {
|
||||
return SDK_OREO;
|
||||
}
|
||||
if ((mScreenLayout2 & MASK_SCREENROUND) != 0) {
|
||||
@@ -591,52 +647,6 @@ public class ResConfigFlags {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private String getLocaleString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
// check for old style non BCP47 tags
|
||||
// allows values-xx-rXX, values-xx, values-xxx-rXX
|
||||
// denies values-xxx, anything else
|
||||
if (mLocaleScript == null && mLocaleVariant == null && (mRegion[0] != '\00' || mLanguage[0] != '\00')
|
||||
&& mRegion.length != 3) {
|
||||
sb.append("-").append(mLanguage);
|
||||
if (mRegion[0] != '\00') {
|
||||
sb.append("-r").append(mRegion);
|
||||
}
|
||||
} else { // BCP47
|
||||
if (mLanguage[0] == '\00' && mRegion[0] == '\00') {
|
||||
return sb.toString(); // early return, no language or region
|
||||
}
|
||||
sb.append("-b+");
|
||||
if (mLanguage[0] != '\00') {
|
||||
sb.append(mLanguage);
|
||||
}
|
||||
if (mLocaleScript != null && mLocaleScript.length == 4) {
|
||||
sb.append("+").append(mLocaleScript);
|
||||
}
|
||||
if ((mRegion.length == 2 || mRegion.length == 3) && mRegion[0] != '\00') {
|
||||
sb.append("+").append(mRegion);
|
||||
}
|
||||
if (mLocaleVariant != null && mLocaleVariant.length >= 5) {
|
||||
sb.append("+").append(toUpper(mLocaleVariant));
|
||||
}
|
||||
|
||||
// If we have a numbering system - it isn't used in qualifiers for build tools, but AOSP understands it
|
||||
// So chances are - this may be valid, but aapt 1/2 will not like it.
|
||||
if (mLocaleNumberingSystem != null && mLocaleNumberingSystem.length > 0) {
|
||||
sb.append("+u+nu+").append(mLocaleNumberingSystem);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static String toUpper(char[] character) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (char ch : character) {
|
||||
sb.append(Character.toUpperCase(ch));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public boolean isInvalid() {
|
||||
return mIsInvalid;
|
||||
}
|
||||
@@ -647,7 +657,7 @@ public class ResConfigFlags {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return !getQualifiers().equals("") ? getQualifiers() : "[DEFAULT]";
|
||||
return !mQualifiers.isEmpty() ? mQualifiers : "[DEFAULT]";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -50,10 +50,6 @@ public class ResPackage {
|
||||
mSynthesizedRes = new HashSet<>();
|
||||
}
|
||||
|
||||
public Config getConfig() {
|
||||
return mResTable.getConfig();
|
||||
}
|
||||
|
||||
public List<ResResSpec> listResSpecs() {
|
||||
return new ArrayList<>(mResSpecs.values());
|
||||
}
|
||||
|
||||
@@ -16,17 +16,14 @@
|
||||
*/
|
||||
package brut.androlib.res.data;
|
||||
|
||||
import brut.androlib.ApkDecoder;
|
||||
import brut.androlib.Config;
|
||||
import brut.androlib.exceptions.AndrolibException;
|
||||
import brut.androlib.exceptions.CantFindFrameworkResException;
|
||||
import brut.androlib.exceptions.UndefinedResObjectException;
|
||||
import brut.androlib.apk.ApkInfo;
|
||||
import brut.androlib.apk.UsesFramework;
|
||||
import brut.androlib.meta.ApkInfo;
|
||||
import brut.androlib.meta.UsesFramework;
|
||||
import brut.androlib.res.Framework;
|
||||
import brut.androlib.res.data.value.ResValue;
|
||||
import brut.androlib.res.decoder.ARSCDecoder;
|
||||
import brut.androlib.res.xml.ResXmlUtils;
|
||||
import brut.directory.DirectoryException;
|
||||
import brut.directory.ExtFile;
|
||||
|
||||
@@ -35,35 +32,25 @@ import java.util.*;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class ResTable {
|
||||
private static final Logger LOGGER = Logger.getLogger(ApkDecoder.class.getName());
|
||||
|
||||
private static final int PACKAGE_TYPE_MAIN = 0;
|
||||
private static final int PACKAGE_TYPE_FRAME = 1;
|
||||
private static final int PACKAGE_TYPE_LIB = 2;
|
||||
private static final Logger LOGGER = Logger.getLogger(ResTable.class.getName());
|
||||
|
||||
private final ApkInfo mApkInfo;
|
||||
private final Config mConfig;
|
||||
private final Map<Integer, ResPackage> mPackagesById;
|
||||
private final Map<String, ResPackage> mPackagesByName;
|
||||
private final Map<Integer, String> mDynamicRefTable;
|
||||
private final Set<ResPackage> mMainPackages;
|
||||
private final Set<ResPackage> mFramePackages;
|
||||
private ResPackage mMainPackage;
|
||||
private final Set<ResPackage> mLibPackages;
|
||||
|
||||
private String mPackageRenamed;
|
||||
private String mPackageOriginal;
|
||||
private int mPackageId;
|
||||
private boolean mMainPkgLoaded;
|
||||
private final Set<ResPackage> mFramePackages;
|
||||
private final Map<Integer, String> mDynamicRefTable;
|
||||
|
||||
public ResTable(ApkInfo apkInfo, Config config) {
|
||||
mApkInfo = apkInfo;
|
||||
mConfig = config;
|
||||
mPackagesById = new HashMap<>();
|
||||
mPackagesByName = new HashMap<>();
|
||||
mDynamicRefTable = new HashMap<>();
|
||||
mMainPackages = new LinkedHashSet<>();
|
||||
mFramePackages = new LinkedHashSet<>();
|
||||
mLibPackages = new LinkedHashSet<>();
|
||||
mLibPackages = new HashSet<>();
|
||||
mFramePackages = new HashSet<>();
|
||||
mDynamicRefTable = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
public ApkInfo getApkInfo() {
|
||||
@@ -74,66 +61,20 @@ public class ResTable {
|
||||
return mConfig;
|
||||
}
|
||||
|
||||
public boolean isMainPkgLoaded() {
|
||||
return mMainPkgLoaded;
|
||||
public boolean isMainPackageLoaded() {
|
||||
return mMainPackage != null;
|
||||
}
|
||||
|
||||
public ResResSpec getResSpec(int resId) throws AndrolibException {
|
||||
if (resId >> 24 == 0) {
|
||||
// The package ID is 0x00. That means that a shared library is accessing its own
|
||||
// local resource, so we fix up this resource with the calling package ID.
|
||||
resId = (0xFFFFFF & resId) | (mPackageId << 24);
|
||||
public ResPackage getMainPackage() throws AndrolibException {
|
||||
if (mMainPackage == null) {
|
||||
throw new AndrolibException("Main package has not been loaded");
|
||||
}
|
||||
return getResSpec(new ResID(resId));
|
||||
return mMainPackage;
|
||||
}
|
||||
|
||||
public ResResSpec getResSpec(ResID resId) throws AndrolibException {
|
||||
return getPackage(resId.getPackageId()).getResSpec(resId);
|
||||
}
|
||||
|
||||
public Set<ResPackage> listMainPackages() {
|
||||
return mMainPackages;
|
||||
}
|
||||
|
||||
public ResPackage getPackage(int id) throws AndrolibException {
|
||||
ResPackage pkg = mPackagesById.get(id);
|
||||
|
||||
if (pkg == null) {
|
||||
try {
|
||||
pkg = loadFrameworkPkg(id);
|
||||
addPackage(pkg, PACKAGE_TYPE_FRAME);
|
||||
} catch (CantFindFrameworkResException ex) {
|
||||
pkg = loadLibraryPkg(id);
|
||||
if (pkg == null) {
|
||||
throw ex;
|
||||
}
|
||||
addPackage(pkg, PACKAGE_TYPE_LIB);
|
||||
}
|
||||
}
|
||||
|
||||
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 pkg = pkgs[i];
|
||||
if (pkg.getResSpecCount() > value && !pkg.getName().equals("android")) {
|
||||
value = pkg.getResSpecCount();
|
||||
id = pkg.getId();
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
|
||||
// if id is still 0, we only have one pkgId which is "android" -> 1
|
||||
return pkgs[id == 0 ? 0 : index];
|
||||
}
|
||||
|
||||
public void loadMainPkg(File apkFile) throws AndrolibException {
|
||||
public void loadMainPackage() throws AndrolibException {
|
||||
LOGGER.info("Loading resource table...");
|
||||
File apkFile = mApkInfo.getApkFile();
|
||||
ResPackage[] pkgs = loadResPackagesFromApk(apkFile, mConfig.isKeepBrokenResources());
|
||||
ResPackage pkg;
|
||||
|
||||
@@ -149,64 +90,12 @@ public class ResTable {
|
||||
pkg = pkgs[1];
|
||||
break;
|
||||
default:
|
||||
pkg = selectPkgWithMostResSpecs(pkgs);
|
||||
pkg = selectPackageWithMostResSpecs(pkgs);
|
||||
break;
|
||||
}
|
||||
|
||||
addPackage(pkg, PACKAGE_TYPE_MAIN);
|
||||
mMainPkgLoaded = true;
|
||||
}
|
||||
|
||||
private ResPackage loadFrameworkPkg(int id) throws AndrolibException {
|
||||
Framework framework = new Framework(mConfig);
|
||||
File apkFile = framework.getApkFile(id, mConfig.getFrameworkTag());
|
||||
|
||||
ResPackage pkg = loadResPackageFromApk(apkFile, true);
|
||||
if (pkg.getId() != id) {
|
||||
throw new AndrolibException("Expected pkg of id: " + id + ", got: " + pkg.getId());
|
||||
}
|
||||
return pkg;
|
||||
}
|
||||
|
||||
private ResPackage loadLibraryPkg(int id) throws AndrolibException {
|
||||
String name = mDynamicRefTable.get(id);
|
||||
String[] libFiles = mConfig.getLibraryFiles();
|
||||
File apkFile = null;
|
||||
|
||||
if (name != null && libFiles != null) {
|
||||
for (String libName : libFiles) {
|
||||
String[] parts = libName.split(":", 2);
|
||||
if (parts.length == 2 && name.equals(parts[0])) {
|
||||
apkFile = new File(parts[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (apkFile == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ResPackage pkg = loadResPackageFromApk(apkFile, true);
|
||||
if (pkg.getId() != id) {
|
||||
throw new AndrolibException("Expected pkg of id: " + id + ", got: " + pkg.getId());
|
||||
}
|
||||
return pkg;
|
||||
}
|
||||
|
||||
private ResPackage loadResPackageFromApk(File apkFile, boolean keepBrokenResources) throws AndrolibException {
|
||||
LOGGER.info("Loading resource table from file: " + apkFile);
|
||||
ResPackage[] pkgs = loadResPackagesFromApk(apkFile, keepBrokenResources);
|
||||
|
||||
ResPackage pkg;
|
||||
if (pkgs.length > 1) {
|
||||
pkg = selectPkgWithMostResSpecs(pkgs);
|
||||
} else if (pkgs.length == 1) {
|
||||
pkg = pkgs[0];
|
||||
} else {
|
||||
throw new AndrolibException("Arsc files with zero packages");
|
||||
}
|
||||
|
||||
return pkg;
|
||||
registerPackage(pkg);
|
||||
mMainPackage = pkg;
|
||||
}
|
||||
|
||||
private ResPackage[] loadResPackagesFromApk(File apkFile, boolean keepBrokenResources)
|
||||
@@ -223,48 +112,22 @@ public class ResTable {
|
||||
}
|
||||
}
|
||||
|
||||
public ResPackage getHighestSpecPackage() throws AndrolibException {
|
||||
int id = 0;
|
||||
int value = 0;
|
||||
private ResPackage selectPackageWithMostResSpecs(ResPackage[] pkgs) {
|
||||
int count = 0;
|
||||
int index = 0;
|
||||
|
||||
for (ResPackage pkg : mPackagesById.values()) {
|
||||
if (pkg.getResSpecCount() > value && !pkg.getName().equals("android")) {
|
||||
id = pkg.getId();
|
||||
value = pkg.getResSpecCount();
|
||||
for (int i = 0; i < pkgs.length; i++) {
|
||||
ResPackage pkg = pkgs[i];
|
||||
if (pkg.getResSpecCount() > count && !pkg.getName().equals("android")) {
|
||||
count = pkg.getResSpecCount();
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
|
||||
// if id is still 0, we only have one pkgId which is "android" -> 1
|
||||
return getPackage(id == 0 ? 1 : id);
|
||||
return pkgs[index];
|
||||
}
|
||||
|
||||
public ResPackage getCurrentResPackage() throws AndrolibException {
|
||||
ResPackage pkg = mPackagesById.get(mPackageId);
|
||||
if (pkg != null) {
|
||||
return pkg;
|
||||
}
|
||||
|
||||
if (mMainPackages.size() == 1) {
|
||||
return mMainPackages.iterator().next();
|
||||
}
|
||||
|
||||
return getHighestSpecPackage();
|
||||
}
|
||||
|
||||
public ResPackage getPackage(String name) throws AndrolibException {
|
||||
ResPackage pkg = mPackagesByName.get(name);
|
||||
if (pkg == null) {
|
||||
throw new UndefinedResObjectException("package: name=" + name);
|
||||
}
|
||||
|
||||
return pkg;
|
||||
}
|
||||
|
||||
public ResValue getValue(String pkg, String type, String name) throws AndrolibException {
|
||||
return getPackage(pkg).getType(type).getResSpec(name).getDefaultResource().getValue();
|
||||
}
|
||||
|
||||
public void addPackage(ResPackage pkg, int type) throws AndrolibException {
|
||||
private void registerPackage(ResPackage pkg) throws AndrolibException {
|
||||
Integer id = pkg.getId();
|
||||
if (mPackagesById.containsKey(id)) {
|
||||
throw new AndrolibException("Multiple packages: id=" + id);
|
||||
@@ -276,52 +139,118 @@ public class ResTable {
|
||||
|
||||
mPackagesById.put(id, pkg);
|
||||
mPackagesByName.put(name, pkg);
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case PACKAGE_TYPE_MAIN:
|
||||
mMainPackages.add(pkg);
|
||||
break;
|
||||
case PACKAGE_TYPE_FRAME:
|
||||
mFramePackages.add(pkg);
|
||||
break;
|
||||
case PACKAGE_TYPE_LIB:
|
||||
mLibPackages.add(pkg);
|
||||
public ResPackage getCurrentPackage() throws AndrolibException {
|
||||
if (mMainPackage == null) {
|
||||
// if no main package, we directly get "android" instead
|
||||
return getPackage(1);
|
||||
}
|
||||
|
||||
return mMainPackage;
|
||||
}
|
||||
|
||||
public ResPackage getPackage(String name) throws AndrolibException {
|
||||
ResPackage pkg = mPackagesByName.get(name);
|
||||
if (pkg == null) {
|
||||
throw new UndefinedResObjectException("package: name=" + name);
|
||||
}
|
||||
|
||||
return pkg;
|
||||
}
|
||||
|
||||
public ResPackage getPackage(int id) throws AndrolibException {
|
||||
ResPackage pkg = mPackagesById.get(id);
|
||||
if (pkg == null) {
|
||||
pkg = loadLibraryPackage(id);
|
||||
if (pkg == null) {
|
||||
pkg = loadFrameworkPackage(id);
|
||||
}
|
||||
}
|
||||
|
||||
return pkg;
|
||||
}
|
||||
|
||||
private ResPackage loadLibraryPackage(int id) throws AndrolibException {
|
||||
String name = mDynamicRefTable.get(id);
|
||||
String[] libFiles = mConfig.getLibraryFiles();
|
||||
File apkFile = null;
|
||||
|
||||
if (name != null && libFiles != null) {
|
||||
for (String libEntry : libFiles) {
|
||||
String[] parts = libEntry.split(":", 2);
|
||||
if (parts.length == 2 && name.equals(parts[0])) {
|
||||
apkFile = new File(parts[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (apkFile == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ResPackage pkg = loadResPackageFromApk(apkFile, true);
|
||||
if (id != pkg.getId()) {
|
||||
throw new AndrolibException("Expected pkg of id: " + id + ", got: " + pkg.getId());
|
||||
}
|
||||
if (!name.equals(pkg.getName())) {
|
||||
throw new AndrolibException("Expected pkg of name: " + name + ", got: " + pkg.getName());
|
||||
}
|
||||
|
||||
registerPackage(pkg);
|
||||
mLibPackages.add(pkg);
|
||||
return pkg;
|
||||
}
|
||||
|
||||
private ResPackage loadFrameworkPackage(int id) throws AndrolibException {
|
||||
Framework framework = new Framework(mConfig);
|
||||
File apkFile = framework.getApkFile(id, mConfig.getFrameworkTag());
|
||||
|
||||
ResPackage pkg = loadResPackageFromApk(apkFile, true);
|
||||
if (id != pkg.getId()) {
|
||||
throw new AndrolibException("Expected pkg of id: " + id + ", got: " + pkg.getId());
|
||||
}
|
||||
|
||||
registerPackage(pkg);
|
||||
mFramePackages.add(pkg);
|
||||
return pkg;
|
||||
}
|
||||
|
||||
private ResPackage loadResPackageFromApk(File apkFile, boolean keepBrokenResources)
|
||||
throws AndrolibException {
|
||||
LOGGER.info("Loading resource table from file: " + apkFile);
|
||||
ResPackage[] pkgs = loadResPackagesFromApk(apkFile, keepBrokenResources);
|
||||
ResPackage pkg;
|
||||
|
||||
switch (pkgs.length) {
|
||||
case 0:
|
||||
throw new AndrolibException("Arsc file with zero packages");
|
||||
case 1:
|
||||
pkg = pkgs[0];
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unexpected package type: " + type);
|
||||
pkg = selectPackageWithMostResSpecs(pkgs);
|
||||
break;
|
||||
}
|
||||
|
||||
return pkg;
|
||||
}
|
||||
|
||||
public void setPackageRenamed(String pkg) {
|
||||
mPackageRenamed = pkg;
|
||||
public ResResSpec getResSpec(int resId) throws AndrolibException {
|
||||
if (resId >> 24 == 0) {
|
||||
// The package ID is 0x00. That means that a shared library is accessing its own
|
||||
// local resource, so we fix up this resource with the calling package ID.
|
||||
resId = (resId & 0xFFFFFF) | (mMainPackage.getId() << 24);
|
||||
}
|
||||
return getResSpec(new ResID(resId));
|
||||
}
|
||||
|
||||
public void setPackageOriginal(String pkg) {
|
||||
mPackageOriginal = pkg;
|
||||
public ResResSpec getResSpec(ResID resId) throws AndrolibException {
|
||||
return getPackage(resId.getPackageId()).getResSpec(resId);
|
||||
}
|
||||
|
||||
public void setPackageId(int id) {
|
||||
mPackageId = id;
|
||||
}
|
||||
|
||||
public void setSparseResources(boolean flag) {
|
||||
mApkInfo.sparseResources = flag;
|
||||
}
|
||||
|
||||
public void setCompactEntries(boolean flag) {
|
||||
mApkInfo.compactEntries = flag;
|
||||
}
|
||||
|
||||
public void addSdkInfo(String key, String value) {
|
||||
mApkInfo.sdkInfo.put(key, value);
|
||||
}
|
||||
|
||||
public void setVersionName(String versionName) {
|
||||
mApkInfo.versionInfo.versionName = versionName;
|
||||
}
|
||||
|
||||
public void setVersionCode(String versionCode) {
|
||||
mApkInfo.versionInfo.versionCode = versionCode;
|
||||
public ResValue getValue(String pkg, String type, String name) throws AndrolibException {
|
||||
return getPackage(pkg).getType(type).getResSpec(name).getDefaultResource().getValue();
|
||||
}
|
||||
|
||||
public void addDynamicRefPackage(int pkgId, String pkgName) {
|
||||
@@ -334,119 +263,46 @@ public class ResTable {
|
||||
return entry.getKey();
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public String getPackageRenamed() {
|
||||
return mPackageRenamed;
|
||||
}
|
||||
|
||||
public String getPackageOriginal() {
|
||||
return mPackageOriginal;
|
||||
}
|
||||
|
||||
public int getPackageId() {
|
||||
return mPackageId;
|
||||
}
|
||||
|
||||
public void initApkInfo(ApkInfo apkInfo, File apkDir) throws AndrolibException {
|
||||
apkInfo.usesFramework = getUsesFramework();
|
||||
apkInfo.usesLibrary = getUsesLibrary();
|
||||
|
||||
if (!mApkInfo.sdkInfo.isEmpty()) {
|
||||
updateSdkInfoFromResources(apkDir);
|
||||
public void updateApkInfo() {
|
||||
if (mMainPackage != null) {
|
||||
mApkInfo.getPackageInfo().setForcedPackageId(Integer.toString(mMainPackage.getId()));
|
||||
}
|
||||
|
||||
initPackageInfo();
|
||||
loadVersionName(apkDir);
|
||||
}
|
||||
if (!mFramePackages.isEmpty()) {
|
||||
UsesFramework usesFramework = mApkInfo.getUsesFramework();
|
||||
List<Integer> frameworkIds = usesFramework.getIds();
|
||||
int[] ids = new int[mFramePackages.size()];
|
||||
|
||||
private UsesFramework getUsesFramework() {
|
||||
UsesFramework usesFramework = new UsesFramework();
|
||||
Integer[] ids = new Integer[mFramePackages.size()];
|
||||
|
||||
int i = 0;
|
||||
for (ResPackage pkg : mFramePackages) {
|
||||
ids[i++] = pkg.getId();
|
||||
}
|
||||
|
||||
Arrays.sort(ids);
|
||||
usesFramework.ids = Arrays.asList(ids);
|
||||
usesFramework.tag = mConfig.getFrameworkTag();
|
||||
|
||||
return usesFramework;
|
||||
}
|
||||
|
||||
private List<String> getUsesLibrary() {
|
||||
List<String> usesLibrary = new ArrayList<>();
|
||||
Integer[] ids = new Integer[mLibPackages.size()];
|
||||
|
||||
int i = 0;
|
||||
for (ResPackage pkg : mLibPackages) {
|
||||
ids[i++] = pkg.getId();
|
||||
}
|
||||
|
||||
Arrays.sort(ids);
|
||||
|
||||
for (Integer id : ids) {
|
||||
usesLibrary.add(mDynamicRefTable.get(id));
|
||||
}
|
||||
|
||||
return usesLibrary;
|
||||
}
|
||||
|
||||
private void updateSdkInfoFromResources(File apkDir) {
|
||||
String minSdkVersion = mApkInfo.getMinSdkVersion();
|
||||
if (minSdkVersion != null) {
|
||||
String refValue = ResXmlUtils.pullValueFromIntegers(apkDir, minSdkVersion);
|
||||
if (refValue != null) {
|
||||
mApkInfo.setMinSdkVersion(refValue);
|
||||
int i = 0;
|
||||
for (ResPackage pkg : mFramePackages) {
|
||||
ids[i++] = pkg.getId();
|
||||
}
|
||||
}
|
||||
Arrays.sort(ids);
|
||||
|
||||
String targetSdkVersion = mApkInfo.getTargetSdkVersion();
|
||||
if (targetSdkVersion != null) {
|
||||
String refValue = ResXmlUtils.pullValueFromIntegers(apkDir, targetSdkVersion);
|
||||
if (refValue != null) {
|
||||
mApkInfo.setTargetSdkVersion(refValue);
|
||||
for (int id : ids) {
|
||||
frameworkIds.add(id);
|
||||
}
|
||||
|
||||
usesFramework.setTag(mConfig.getFrameworkTag());
|
||||
}
|
||||
|
||||
String maxSdkVersion = mApkInfo.getMaxSdkVersion();
|
||||
if (maxSdkVersion != null) {
|
||||
String refValue = ResXmlUtils.pullValueFromIntegers(apkDir, maxSdkVersion);
|
||||
if (refValue != null) {
|
||||
mApkInfo.setMaxSdkVersion(refValue);
|
||||
if (!mLibPackages.isEmpty()) {
|
||||
List<String> usesLibrary = mApkInfo.getUsesLibrary();
|
||||
int[] ids = new int[mLibPackages.size()];
|
||||
|
||||
int i = 0;
|
||||
for (ResPackage pkg : mLibPackages) {
|
||||
ids[i++] = pkg.getId();
|
||||
}
|
||||
}
|
||||
}
|
||||
Arrays.sort(ids);
|
||||
|
||||
private void initPackageInfo() throws AndrolibException {
|
||||
String original = getPackageOriginal();
|
||||
if (original == null || original.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// only put rename-manifest-package into apktool.yml, if the change will be required
|
||||
String renamed = getPackageRenamed();
|
||||
if (renamed != null && !renamed.equals(original)) {
|
||||
mApkInfo.packageInfo.renameManifestPackage = renamed;
|
||||
}
|
||||
|
||||
int id;
|
||||
try {
|
||||
id = getPackage(renamed).getId();
|
||||
} catch (UndefinedResObjectException ex) {
|
||||
id = getPackageId();
|
||||
}
|
||||
mApkInfo.packageInfo.forcedPackageId = String.valueOf(id);
|
||||
}
|
||||
|
||||
private void loadVersionName(File apkDir) {
|
||||
String versionName = mApkInfo.versionInfo.versionName;
|
||||
String refValue = ResXmlUtils.pullValueFromStrings(apkDir, versionName);
|
||||
if (refValue != null) {
|
||||
mApkInfo.versionInfo.versionName = refValue;
|
||||
for (int id : ids) {
|
||||
usesLibrary.add(mDynamicRefTable.get(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,38 +26,38 @@ import java.util.logging.Logger;
|
||||
public class ARSCHeader {
|
||||
private static final Logger LOGGER = Logger.getLogger(ARSCHeader.class.getName());
|
||||
|
||||
public static final short RES_NONE_TYPE = -1;
|
||||
public static final short RES_NULL_TYPE = 0x0000;
|
||||
public static final short RES_STRING_POOL_TYPE = 0x0001;
|
||||
public static final short RES_TABLE_TYPE = 0x0002;
|
||||
public static final short RES_XML_TYPE = 0x0003;
|
||||
public static final int RES_NONE_TYPE = -1;
|
||||
public static final int RES_NULL_TYPE = 0x0000;
|
||||
public static final int RES_STRING_POOL_TYPE = 0x0001;
|
||||
public static final int RES_TABLE_TYPE = 0x0002;
|
||||
public static final int RES_XML_TYPE = 0x0003;
|
||||
|
||||
// RES_TABLE_TYPE Chunks
|
||||
public static final short XML_TYPE_PACKAGE = 0x0200;
|
||||
public static final short XML_TYPE_TYPE = 0x0201;
|
||||
public static final short XML_TYPE_SPEC_TYPE = 0x0202;
|
||||
public static final short XML_TYPE_LIBRARY = 0x0203;
|
||||
public static final short XML_TYPE_OVERLAY = 0x0204;
|
||||
public static final short XML_TYPE_OVERLAY_POLICY = 0x0205;
|
||||
public static final short XML_TYPE_STAGED_ALIAS = 0x0206;
|
||||
// RES_XML_TYPE chunks types
|
||||
public static final int RES_XML_FIRST_CHUNK_TYPE = 0x0100;
|
||||
public static final int RES_XML_START_NAMESPACE_TYPE = 0x0100;
|
||||
public static final int RES_XML_END_NAMESPACE_TYPE = 0x0101;
|
||||
public static final int RES_XML_START_ELEMENT_TYPE = 0x0102;
|
||||
public static final int RES_XML_END_ELEMENT_TYPE = 0x0103;
|
||||
public static final int RES_XML_CDATA_TYPE = 0x0104;
|
||||
public static final int RES_XML_LAST_CHUNK_TYPE = 0x017F;
|
||||
public static final int RES_XML_RESOURCE_MAP_TYPE = 0x0180;
|
||||
|
||||
// RES_XML_TYPE Chunks
|
||||
public static final short RES_XML_FIRST_CHUNK_TYPE = 0x0100;
|
||||
public static final short RES_XML_START_NAMESPACE_TYPE = 0x0100;
|
||||
public static final short RES_XML_END_NAMESPACE_TYPE = 0x0101;
|
||||
public static final short RES_XML_START_ELEMENT_TYPE = 0x0102;
|
||||
public static final short RES_XML_END_ELEMENT_TYPE = 0x0103;
|
||||
public static final short RES_XML_CDATA_TYPE = 0x0104;
|
||||
public static final short RES_XML_LAST_CHUNK_TYPE = 0x017f;
|
||||
public static final short RES_XML_RESOURCE_MAP_TYPE = 0x0180;
|
||||
// RES_TABLE_TYPE chunks types
|
||||
public static final int RES_TABLE_PACKAGE_TYPE = 0x0200;
|
||||
public static final int RES_TABLE_TYPE_TYPE = 0x0201;
|
||||
public static final int RES_TABLE_TYPE_SPEC_TYPE = 0x0202;
|
||||
public static final int RES_TABLE_LIBRARY_TYPE = 0x0203;
|
||||
public static final int RES_TABLE_OVERLAYABLE_TYPE = 0x0204;
|
||||
public static final int RES_TABLE_OVERLAYABLE_POLICY_TYPE = 0x0205;
|
||||
public static final int RES_TABLE_STAGED_ALIAS_TYPE = 0x0206;
|
||||
|
||||
public final short type;
|
||||
public final int type;
|
||||
public final int headerSize;
|
||||
public final int chunkSize;
|
||||
public final long startPosition;
|
||||
public final long endPosition;
|
||||
|
||||
public ARSCHeader(short type, int headerSize, int chunkSize, long headerStart) {
|
||||
public ARSCHeader(int type, int headerSize, int chunkSize, long headerStart) {
|
||||
this.type = type;
|
||||
this.headerSize = headerSize;
|
||||
this.chunkSize = chunkSize;
|
||||
@@ -66,14 +66,14 @@ public class ARSCHeader {
|
||||
}
|
||||
|
||||
public static ARSCHeader read(ExtDataInput in) throws IOException {
|
||||
short type;
|
||||
int type;
|
||||
long start = in.position();
|
||||
try {
|
||||
type = in.readShort();
|
||||
type = in.readUnsignedShort();
|
||||
} catch (EOFException ex) {
|
||||
return new ARSCHeader(RES_NONE_TYPE, 0, 0, in.position());
|
||||
}
|
||||
return new ARSCHeader(type, in.readShort(), in.readInt(), start);
|
||||
return new ARSCHeader(type, in.readUnsignedShort(), in.readInt(), start);
|
||||
}
|
||||
|
||||
public void checkForUnreadHeader(ExtDataInput in) throws IOException {
|
||||
@@ -84,8 +84,7 @@ public class ARSCHeader {
|
||||
int actualHeaderSize = (int) (in.position() - startPosition);
|
||||
int exceedingSize = headerSize - actualHeaderSize;
|
||||
if (exceedingSize > 0) {
|
||||
byte[] buf = new byte[exceedingSize];
|
||||
in.readFully(buf);
|
||||
byte[] buf = in.readBytes(exceedingSize);
|
||||
BigInteger exceedingBI = new BigInteger(1, buf);
|
||||
|
||||
if (exceedingBI.equals(BigInteger.ZERO)) {
|
||||
|
||||
@@ -19,7 +19,7 @@ package brut.androlib.res.data.arsc;
|
||||
import brut.androlib.res.data.value.ResValue;
|
||||
|
||||
public class EntryData {
|
||||
public short flags;
|
||||
public int flags;
|
||||
public int specNamesId;
|
||||
public ResValue value;
|
||||
}
|
||||
|
||||
@@ -21,32 +21,34 @@ import brut.util.ExtDataInput;
|
||||
import java.io.IOException;
|
||||
|
||||
public class NinePatchData {
|
||||
public final int padLeft, padRight, padTop, padBottom;
|
||||
public final int paddingLeft, paddingRight, paddingTop, paddingBottom;
|
||||
public final int[] xDivs, yDivs;
|
||||
|
||||
public NinePatchData(int padLeft, int padRight, int padTop, int padBottom, int[] xDivs, int[] yDivs) {
|
||||
this.padLeft = padLeft;
|
||||
this.padRight = padRight;
|
||||
this.padTop = padTop;
|
||||
this.padBottom = padBottom;
|
||||
public NinePatchData(int paddingLeft, int paddingRight, int paddingTop, int paddingBottom,
|
||||
int[] xDivs, int[] yDivs) {
|
||||
this.paddingLeft = paddingLeft;
|
||||
this.paddingRight = paddingRight;
|
||||
this.paddingTop = paddingTop;
|
||||
this.paddingBottom = paddingBottom;
|
||||
this.xDivs = xDivs;
|
||||
this.yDivs = yDivs;
|
||||
}
|
||||
|
||||
public static NinePatchData decode(ExtDataInput in) throws IOException {
|
||||
in.skipBytes(1); // wasDeserialized
|
||||
byte numXDivs = in.readByte();
|
||||
byte numYDivs = in.readByte();
|
||||
in.skipBytes(1); // numColors
|
||||
in.skipBytes(8); // xDivs/yDivs offset
|
||||
int padLeft = in.readInt();
|
||||
int padRight = in.readInt();
|
||||
int padTop = in.readInt();
|
||||
int padBottom = in.readInt();
|
||||
in.skipBytes(4); // colorsOffset
|
||||
in.skipByte(); // wasDeserialized
|
||||
int numXDivs = in.readUnsignedByte();
|
||||
int numYDivs = in.readUnsignedByte();
|
||||
in.skipByte(); // numColors
|
||||
in.skipInt(); // xDivsOffset
|
||||
in.skipInt(); // yDivsOffset
|
||||
int paddingLeft = in.readInt();
|
||||
int paddingRight = in.readInt();
|
||||
int paddingTop = in.readInt();
|
||||
int paddingBottom = in.readInt();
|
||||
in.skipInt(); // colorsOffset
|
||||
int[] xDivs = in.readIntArray(numXDivs);
|
||||
int[] yDivs = in.readIntArray(numYDivs);
|
||||
|
||||
return new NinePatchData(padLeft, padRight, padTop, padBottom, xDivs, yDivs);
|
||||
return new NinePatchData(paddingLeft, paddingRight, paddingTop, paddingBottom, xDivs, yDivs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ public class ResAttr extends ResBagValue implements ResValuesXmlSerializable {
|
||||
private static final int TYPE_FLOAT = 0x20;
|
||||
private static final int TYPE_DIMEN = 0x40;
|
||||
private static final int TYPE_FRACTION = 0x80;
|
||||
private static final int TYPE_ANY_STRING = 0xee;
|
||||
private static final int TYPE_ANY_STRING = 0xEE;
|
||||
|
||||
private static final int TYPE_ENUM = 0x00010000;
|
||||
private static final int TYPE_FLAGS = 0x00020000;
|
||||
@@ -106,7 +106,7 @@ public class ResAttr extends ResBagValue implements ResValuesXmlSerializable {
|
||||
|
||||
// #2806 - Make sure we handle int-based values and not just ResIntValue
|
||||
int rawValue = items[0].getRight().getRawIntValue();
|
||||
int scalarType = rawValue & 0xffff;
|
||||
int scalarType = rawValue & 0xFFFF;
|
||||
|
||||
if (i == items.length) {
|
||||
return new ResAttr(parent, scalarType, min, max, l10n);
|
||||
@@ -119,7 +119,7 @@ public class ResAttr extends ResBagValue implements ResValuesXmlSerializable {
|
||||
pkg.addSynthesizedRes(resId);
|
||||
attrItems[j] = Pair.of(factory.newReference(resId, null), item.getRight());
|
||||
}
|
||||
switch (rawValue & 0xff0000) {
|
||||
switch (rawValue & 0xFF0000) {
|
||||
case TYPE_ENUM:
|
||||
return new ResEnumAttr(parent, scalarType, min, max, l10n, attrItems);
|
||||
case TYPE_FLAGS:
|
||||
|
||||
@@ -31,7 +31,7 @@ public class ResBagValue extends ResValue implements ResValuesXmlSerializable {
|
||||
|
||||
public ResBagValue(ResReferenceValue parent) {
|
||||
mParent = parent;
|
||||
mConfig = parent.getPackage().getConfig();
|
||||
mConfig = parent.getPackage().getResTable().getConfig();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -62,7 +62,7 @@ public class ResEnumAttr extends ResAttr {
|
||||
|
||||
// #2836 - Support skipping items if the resource cannot be identified.
|
||||
ResResSpec referent = ref.getReferent();
|
||||
if (referent == null && mConfig.getDecodeResolveMode() == Config.DECODE_RES_RESOLVE_REMOVE) {
|
||||
if (referent == null && mConfig.getDecodeResolve() == Config.DecodeResolve.REMOVE) {
|
||||
LOGGER.fine(String.format("null enum reference: ref=0x%08x(%s), val=0x%08x(%s)",
|
||||
ref.getRawIntValue(), ref.getType(), val.getRawIntValue(), val.getType()));
|
||||
continue;
|
||||
@@ -71,7 +71,7 @@ public class ResEnumAttr extends ResAttr {
|
||||
serializer.startTag(null, "enum");
|
||||
serializer.attribute(null, "name", referent != null
|
||||
? referent.getName() : String.format("APKTOOL_MISSING_0x%08x", ref.getRawIntValue()));
|
||||
serializer.attribute(null, "value", String.valueOf(val.getRawIntValue()));
|
||||
serializer.attribute(null, "value", Integer.toString(val.getRawIntValue()));
|
||||
serializer.endTag(null, "enum");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ public class ResFlagsAttr extends ResAttr {
|
||||
ResResSpec referent = item.ref.getReferent();
|
||||
|
||||
// #2836 - Support skipping items if the resource cannot be identified.
|
||||
if (referent == null && mConfig.getDecodeResolveMode() == Config.DECODE_RES_RESOLVE_REMOVE) {
|
||||
if (referent == null && mConfig.getDecodeResolve() == Config.DecodeResolve.REMOVE) {
|
||||
LOGGER.fine(String.format("null flag reference: 0x%08x(%s)", item.ref.getValue(), item.ref.getType()));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,6 @@ public class ResFloatValue extends ResScalarValue {
|
||||
|
||||
@Override
|
||||
protected String encodeAsResXml() {
|
||||
return String.valueOf(mValue);
|
||||
return Float.toString(mValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,19 +27,20 @@ import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.io.*;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class ARSCDecoder {
|
||||
private static final Logger LOGGER = Logger.getLogger(ARSCDecoder.class.getName());
|
||||
|
||||
private static final short ENTRY_FLAG_COMPLEX = 0x0001;
|
||||
private static final short ENTRY_FLAG_PUBLIC = 0x0002;
|
||||
private static final short ENTRY_FLAG_WEAK = 0x0004;
|
||||
private static final short ENTRY_FLAG_COMPACT = 0x0008;
|
||||
private static final int ENTRY_FLAG_COMPLEX = 0x0001;
|
||||
private static final int ENTRY_FLAG_PUBLIC = 0x0002;
|
||||
private static final int ENTRY_FLAG_WEAK = 0x0004;
|
||||
private static final int ENTRY_FLAG_COMPACT = 0x0008;
|
||||
|
||||
private static final short TABLE_TYPE_FLAG_SPARSE = 0x01;
|
||||
private static final short TABLE_TYPE_FLAG_OFFSET16 = 0x02;
|
||||
private static final int TABLE_TYPE_FLAG_SPARSE = 0x01;
|
||||
private static final int TABLE_TYPE_FLAG_OFFSET16 = 0x02;
|
||||
|
||||
private static final int KNOWN_CONFIG_BYTES = 64;
|
||||
|
||||
@@ -51,14 +52,15 @@ public class ARSCDecoder {
|
||||
private final List<FlagsOffset> mFlagsOffsets;
|
||||
private final boolean mKeepBroken;
|
||||
private final Config mConfig;
|
||||
private final Map<Integer, Integer> mMissingResSpecMap;
|
||||
private final Map<Integer, ResTypeSpec> mResTypeSpecs;
|
||||
private final Map<Integer, Integer> mMissingResSpecs;
|
||||
private final Set<ResConfigFlags> mInvalidResConfigFlags;
|
||||
|
||||
private ARSCHeader mHeader;
|
||||
private StringBlock mTableStrings;
|
||||
private StringBlock mTypeNames;
|
||||
private StringBlock mSpecNames;
|
||||
private ResPackage mPkg;
|
||||
private ResPackage mPackage;
|
||||
private ResTypeSpec mTypeSpec;
|
||||
private ResType mType;
|
||||
private int mResId;
|
||||
@@ -70,8 +72,9 @@ public class ARSCDecoder {
|
||||
mFlagsOffsets = storeFlagsOffsets ? new ArrayList<>() : null;
|
||||
mKeepBroken = keepBroken;
|
||||
mConfig = resTable.getConfig();
|
||||
mMissingResSpecMap = new LinkedHashMap<>();
|
||||
mResTypeSpecs = new HashMap<>();
|
||||
mMissingResSpecs = new LinkedHashMap<>();
|
||||
mInvalidResConfigFlags = new HashSet<>();
|
||||
}
|
||||
|
||||
public ARSCData decode() throws AndrolibException {
|
||||
@@ -91,7 +94,7 @@ public class ARSCDecoder {
|
||||
|
||||
chunkLoop:
|
||||
for (;;) {
|
||||
nextChunk();
|
||||
mHeader = ARSCHeader.read(mIn);
|
||||
|
||||
LOGGER.fine(String.format(
|
||||
"Chunk #%d start=0x%08x type=0x%04x chunkSize=0x%08x",
|
||||
@@ -110,27 +113,27 @@ public class ARSCDecoder {
|
||||
break;
|
||||
|
||||
// Chunk types in RES_TABLE_TYPE
|
||||
case ARSCHeader.XML_TYPE_PACKAGE:
|
||||
case ARSCHeader.RES_TABLE_PACKAGE_TYPE:
|
||||
mTypeIdOffset = 0;
|
||||
pkgs.add(readTablePackage());
|
||||
break;
|
||||
case ARSCHeader.XML_TYPE_TYPE:
|
||||
case ARSCHeader.RES_TABLE_TYPE_TYPE:
|
||||
readTableType();
|
||||
break;
|
||||
case ARSCHeader.XML_TYPE_SPEC_TYPE:
|
||||
case ARSCHeader.RES_TABLE_TYPE_SPEC_TYPE:
|
||||
typeSpec = readTableSpecType();
|
||||
addTypeSpec(typeSpec);
|
||||
mResTypeSpecs.put(typeSpec.getId(), typeSpec);
|
||||
break;
|
||||
case ARSCHeader.XML_TYPE_LIBRARY:
|
||||
case ARSCHeader.RES_TABLE_LIBRARY_TYPE:
|
||||
readLibraryType();
|
||||
break;
|
||||
case ARSCHeader.XML_TYPE_OVERLAY:
|
||||
case ARSCHeader.RES_TABLE_OVERLAYABLE_TYPE:
|
||||
readOverlaySpec();
|
||||
break;
|
||||
case ARSCHeader.XML_TYPE_OVERLAY_POLICY:
|
||||
case ARSCHeader.RES_TABLE_OVERLAYABLE_POLICY_TYPE:
|
||||
readOverlayPolicySpec();
|
||||
break;
|
||||
case ARSCHeader.XML_TYPE_STAGED_ALIAS:
|
||||
case ARSCHeader.RES_TABLE_STAGED_ALIAS_TYPE:
|
||||
readStagedAliasSpec();
|
||||
break;
|
||||
default:
|
||||
@@ -141,8 +144,8 @@ public class ARSCDecoder {
|
||||
}
|
||||
}
|
||||
|
||||
if (mConfig.getDecodeResolveMode() == Config.DECODE_RES_RESOLVE_DUMMY
|
||||
&& mPkg != null && mPkg.getResSpecCount() > 0) {
|
||||
if (mConfig.getDecodeResolve() == Config.DecodeResolve.DUMMY
|
||||
&& mPackage != null && mPackage.getResSpecCount() > 0) {
|
||||
addMissingResSpecs();
|
||||
}
|
||||
|
||||
@@ -151,6 +154,7 @@ public class ARSCDecoder {
|
||||
|
||||
private void readStringPoolChunk() throws AndrolibException, IOException {
|
||||
checkChunkType(ARSCHeader.RES_STRING_POOL_TYPE);
|
||||
|
||||
mTableStrings = StringBlock.readWithoutChunk(mIn, mHeader.startPosition, mHeader.headerSize, mHeader.chunkSize);
|
||||
}
|
||||
|
||||
@@ -171,9 +175,9 @@ public class ARSCDecoder {
|
||||
}
|
||||
|
||||
private ResPackage readTablePackage() throws AndrolibException, IOException {
|
||||
checkChunkType(ARSCHeader.XML_TYPE_PACKAGE);
|
||||
checkChunkType(ARSCHeader.RES_TABLE_PACKAGE_TYPE);
|
||||
int id = mIn.readInt();
|
||||
String name = mIn.readNullEndedString(128, true);
|
||||
String name = mIn.readUtf16(128);
|
||||
mIn.skipInt(); // typeStrings
|
||||
mIn.skipInt(); // lastPublicType
|
||||
mIn.skipInt(); // keyStrings
|
||||
@@ -195,27 +199,27 @@ public class ARSCDecoder {
|
||||
mTypeNames = StringBlock.readWithChunk(mIn);
|
||||
mSpecNames = StringBlock.readWithChunk(mIn);
|
||||
|
||||
if (id == 0 && mResTable.isMainPkgLoaded()) {
|
||||
if (id == 0 && mResTable.isMainPackageLoaded()) {
|
||||
// The package ID is 0x00. That means that a shared library is being loaded,
|
||||
// so we change it to the reference package ID defined in the dynamic reference table.
|
||||
id = mResTable.getDynamicRefPackageId(name);
|
||||
}
|
||||
|
||||
mResId = id << 24;
|
||||
mPkg = new ResPackage(mResTable, id, name);
|
||||
mPackage = new ResPackage(mResTable, id, name);
|
||||
|
||||
return mPkg;
|
||||
return mPackage;
|
||||
}
|
||||
|
||||
private void readLibraryType() throws AndrolibException, IOException {
|
||||
checkChunkType(ARSCHeader.XML_TYPE_LIBRARY);
|
||||
checkChunkType(ARSCHeader.RES_TABLE_LIBRARY_TYPE);
|
||||
int libraryCount = mIn.readInt();
|
||||
|
||||
mHeader.checkForUnreadHeader(mIn);
|
||||
|
||||
for (int i = 0; i < libraryCount; i++) {
|
||||
int id = mIn.readInt();
|
||||
String name = mIn.readNullEndedString(128, true);
|
||||
String name = mIn.readUtf16(128);
|
||||
|
||||
mResTable.addDynamicRefPackage(id, name);
|
||||
|
||||
@@ -234,9 +238,9 @@ public class ARSCDecoder {
|
||||
}
|
||||
|
||||
private void readOverlaySpec() throws AndrolibException, IOException {
|
||||
checkChunkType(ARSCHeader.XML_TYPE_OVERLAY);
|
||||
String name = mIn.readNullEndedString(256, true);
|
||||
String actor = mIn.readNullEndedString(256, true);
|
||||
checkChunkType(ARSCHeader.RES_TABLE_OVERLAYABLE_TYPE);
|
||||
String name = mIn.readUtf16(256);
|
||||
String actor = mIn.readUtf16(256);
|
||||
|
||||
mHeader.checkForUnreadHeader(mIn);
|
||||
|
||||
@@ -244,7 +248,7 @@ public class ARSCDecoder {
|
||||
}
|
||||
|
||||
private void readOverlayPolicySpec() throws AndrolibException, IOException {
|
||||
checkChunkType(ARSCHeader.XML_TYPE_OVERLAY_POLICY);
|
||||
checkChunkType(ARSCHeader.RES_TABLE_OVERLAYABLE_POLICY_TYPE);
|
||||
mIn.skipInt(); // policyFlags
|
||||
int count = mIn.readInt();
|
||||
|
||||
@@ -256,10 +260,10 @@ public class ARSCDecoder {
|
||||
}
|
||||
|
||||
private ResTypeSpec readTableSpecType() throws AndrolibException, IOException {
|
||||
checkChunkType(ARSCHeader.XML_TYPE_SPEC_TYPE);
|
||||
checkChunkType(ARSCHeader.RES_TABLE_TYPE_SPEC_TYPE);
|
||||
int id = mIn.readUnsignedByte();
|
||||
mIn.skipBytes(1); // reserved0
|
||||
mIn.skipBytes(2); // reserved1
|
||||
mIn.skipByte(); // reserved0
|
||||
mIn.skipShort(); // typesCount
|
||||
int entryCount = mIn.readInt();
|
||||
|
||||
if (mFlagsOffsets != null) {
|
||||
@@ -268,15 +272,17 @@ public class ARSCDecoder {
|
||||
|
||||
mHeader.checkForUnreadHeader(mIn);
|
||||
|
||||
mIn.skipBytes(entryCount * 4); // flags
|
||||
for (int i = 0; i < entryCount; i++) {
|
||||
mIn.skipInt(); // flags
|
||||
}
|
||||
mTypeSpec = new ResTypeSpec(mTypeNames.getString(id - 1), id);
|
||||
mPkg.addType(mTypeSpec);
|
||||
mPackage.addType(mTypeSpec);
|
||||
|
||||
return mTypeSpec;
|
||||
}
|
||||
|
||||
private ResType readTableType() throws AndrolibException, IOException {
|
||||
checkChunkType(ARSCHeader.XML_TYPE_TYPE);
|
||||
checkChunkType(ARSCHeader.RES_TABLE_TYPE_TYPE);
|
||||
int typeId = mIn.readUnsignedByte() - mTypeIdOffset;
|
||||
|
||||
// #3311 - Some older applications have no TYPE_SPEC chunks, but still define TYPE chunks.
|
||||
@@ -284,13 +290,13 @@ public class ARSCDecoder {
|
||||
mTypeSpec = mResTypeSpecs.get(typeId);
|
||||
} else {
|
||||
mTypeSpec = new ResTypeSpec(mTypeNames.getString(typeId - 1), typeId);
|
||||
addTypeSpec(mTypeSpec);
|
||||
mPkg.addType(mTypeSpec);
|
||||
mResTypeSpecs.put(mTypeSpec.getId(), mTypeSpec);
|
||||
mPackage.addType(mTypeSpec);
|
||||
}
|
||||
mResId = (0xFF000000 & mResId) | mTypeSpec.getId() << 16;
|
||||
mResId = (mResId & 0xFF000000) | mTypeSpec.getId() << 16;
|
||||
|
||||
int typeFlags = mIn.readByte();
|
||||
mIn.skipBytes(2); // reserved
|
||||
int typeFlags = mIn.readUnsignedByte();
|
||||
mIn.skipShort(); // reserved
|
||||
int entryCount = mIn.readInt();
|
||||
int entriesStart = mIn.readInt();
|
||||
long entriesStartAligned = mHeader.startPosition + entriesStart;
|
||||
@@ -303,10 +309,9 @@ public class ARSCDecoder {
|
||||
boolean isOffset16 = (typeFlags & TABLE_TYPE_FLAG_OFFSET16) != 0;
|
||||
boolean isSparse = (typeFlags & TABLE_TYPE_FLAG_SPARSE) != 0;
|
||||
|
||||
// Be sure we don't poison mResTable by marking the application as sparse
|
||||
// Only flag the ResTable as sparse if the main package is not loaded.
|
||||
if (isSparse && !mResTable.isMainPkgLoaded()) {
|
||||
mResTable.setSparseResources(true);
|
||||
// Only flag the application as sparse if the main package is not loaded yet.
|
||||
if (isSparse && !mResTable.isMainPackageLoaded()) {
|
||||
mResTable.getApkInfo().setSparseResources(true);
|
||||
}
|
||||
|
||||
// #3372 - The offsets that are 16bit should be stored as real offsets (* 4u).
|
||||
@@ -322,7 +327,7 @@ public class ARSCDecoder {
|
||||
}
|
||||
}
|
||||
|
||||
if (flags.isInvalid()) {
|
||||
if (mInvalidResConfigFlags.contains(flags)) {
|
||||
String resName = mTypeSpec.getName() + flags.getQualifiers();
|
||||
if (mKeepBroken) {
|
||||
LOGGER.warning("Invalid config flags detected: " + resName);
|
||||
@@ -331,7 +336,7 @@ public class ARSCDecoder {
|
||||
}
|
||||
}
|
||||
|
||||
mType = !flags.isInvalid() || mKeepBroken ? mPkg.getOrCreateConfig(flags) : null;
|
||||
mType = (!mInvalidResConfigFlags.contains(flags) || mKeepBroken) ? mPackage.getOrCreateConfig(flags) : null;
|
||||
|
||||
// #3428 - In some applications the res entries are padded for alignment, but in #3778 it made
|
||||
// sense to align to the start of the entries to handle all cases.
|
||||
@@ -340,7 +345,7 @@ public class ARSCDecoder {
|
||||
mResId = (mResId & 0xFFFF0000) | i;
|
||||
int offset = entryOffsetMap.get(i);
|
||||
if (offset == NO_ENTRY) {
|
||||
mMissingResSpecMap.put(mResId, typeId);
|
||||
mMissingResSpecs.put(mResId, typeId);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -364,7 +369,7 @@ public class ARSCDecoder {
|
||||
if (entryData != null) {
|
||||
parseEntryData(entryData);
|
||||
} else {
|
||||
mMissingResSpecMap.put(mResId, typeId);
|
||||
mMissingResSpecs.put(mResId, typeId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,7 +384,7 @@ public class ARSCDecoder {
|
||||
|
||||
private EntryData readEntryData() throws AndrolibException, IOException {
|
||||
int size = mIn.readUnsignedShort();
|
||||
short flags = mIn.readShort();
|
||||
int flags = mIn.readUnsignedShort();
|
||||
|
||||
boolean isComplex = (flags & ENTRY_FLAG_COMPLEX) != 0;
|
||||
boolean isCompact = (flags & ENTRY_FLAG_COMPACT) != 0;
|
||||
@@ -389,17 +394,16 @@ public class ARSCDecoder {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Be sure we don't poison mResTable by marking the application as compact
|
||||
// Only flag the ResTable as compact if the main package is not loaded.
|
||||
if (isCompact && !mResTable.isMainPkgLoaded()) {
|
||||
mResTable.setCompactEntries(true);
|
||||
// Only flag the application as compact if the main package is not loaded yet.
|
||||
if (isCompact && !mResTable.isMainPackageLoaded()) {
|
||||
mResTable.getApkInfo().setCompactEntries(true);
|
||||
}
|
||||
|
||||
// #3366 - In a compactly packed entry, the key index is the size & type is higher 8 bits on flags.
|
||||
// We assume a size of 8 bytes for compact entries and the specNamesId is the data itself encoded.
|
||||
ResValue value;
|
||||
if (isCompact) {
|
||||
byte type = (byte) ((flags >> 8) & 0xFF);
|
||||
int type = (flags >> 8) & 0xFF;
|
||||
value = readCompactValue(type, specNamesId);
|
||||
|
||||
// To keep code below happy - we know if compact then the size has the key index encoded.
|
||||
@@ -436,11 +440,11 @@ public class ARSCDecoder {
|
||||
|
||||
ResID resId = new ResID(mResId);
|
||||
ResResSpec spec;
|
||||
if (mPkg.hasResSpec(resId)) {
|
||||
spec = mPkg.getResSpec(resId);
|
||||
if (mPackage.hasResSpec(resId)) {
|
||||
spec = mPackage.getResSpec(resId);
|
||||
} else {
|
||||
spec = new ResResSpec(resId, mSpecNames.getString(specNamesId), mPkg, mTypeSpec);
|
||||
mPkg.addResSpec(spec);
|
||||
spec = new ResResSpec(resId, mSpecNames.getString(specNamesId), mPackage, mTypeSpec);
|
||||
mPackage.addResSpec(spec);
|
||||
mTypeSpec.addResSpec(spec);
|
||||
}
|
||||
ResResource res = new ResResource(mType, spec, value);
|
||||
@@ -463,7 +467,7 @@ public class ARSCDecoder {
|
||||
int parentId = mIn.readInt();
|
||||
int count = mIn.readInt();
|
||||
|
||||
ResValueFactory factory = mPkg.getValueFactory();
|
||||
ResValueFactory factory = mPackage.getValueFactory();
|
||||
Pair<Integer, ResScalarValue>[] items = new Pair[count];
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
@@ -485,25 +489,25 @@ public class ARSCDecoder {
|
||||
return factory.bagFactory(parentId, items, mTypeSpec);
|
||||
}
|
||||
|
||||
private ResIntBasedValue readCompactValue(byte type, int data) throws AndrolibException {
|
||||
private ResIntBasedValue readCompactValue(int type, int data) throws AndrolibException {
|
||||
return type == TypedValue.TYPE_STRING
|
||||
? mPkg.getValueFactory().factory(mTableStrings.getHTML(data), data)
|
||||
: mPkg.getValueFactory().factory(type, data, null);
|
||||
? mPackage.getValueFactory().factory(mTableStrings.getHTML(data), data)
|
||||
: mPackage.getValueFactory().factory(type, data, null);
|
||||
}
|
||||
|
||||
private ResIntBasedValue readValue() throws AndrolibException, IOException {
|
||||
short size = mIn.readShort();
|
||||
int size = mIn.readUnsignedShort();
|
||||
if (size < 8) {
|
||||
return null;
|
||||
}
|
||||
|
||||
mIn.skipCheckByte((byte) 0); // zero
|
||||
byte type = mIn.readByte();
|
||||
mIn.skipByte(); // reserved0
|
||||
int type = mIn.readUnsignedByte();
|
||||
int data = mIn.readInt();
|
||||
|
||||
return type == TypedValue.TYPE_STRING
|
||||
? mPkg.getValueFactory().factory(mTableStrings.getHTML(data), data)
|
||||
: mPkg.getValueFactory().factory(type, data, null);
|
||||
? mPackage.getValueFactory().factory(mTableStrings.getHTML(data), data)
|
||||
: mPackage.getValueFactory().factory(type, data, null);
|
||||
}
|
||||
|
||||
private ResConfigFlags readConfigFlags() throws AndrolibException, IOException {
|
||||
@@ -516,22 +520,22 @@ public class ARSCDecoder {
|
||||
|
||||
boolean isInvalid = false;
|
||||
|
||||
short mcc = mIn.readShort();
|
||||
short mnc = mIn.readShort();
|
||||
int mcc = mIn.readUnsignedShort();
|
||||
int mnc = mIn.readUnsignedShort();
|
||||
|
||||
char[] language = new char[0];
|
||||
char[] country = new char[0];
|
||||
String language = "";
|
||||
String region = "";
|
||||
if (size >= 12) {
|
||||
language = unpackLanguageOrRegion(mIn.readByte(), mIn.readByte(), 'a');
|
||||
country = unpackLanguageOrRegion(mIn.readByte(), mIn.readByte(), '0');
|
||||
language = unpackLanguageOrRegion(mIn.readBytes(2), 'a');
|
||||
region = unpackLanguageOrRegion(mIn.readBytes(2), '0');
|
||||
read = 12;
|
||||
}
|
||||
|
||||
byte orientation = 0;
|
||||
byte touchscreen = 0;
|
||||
int orientation = 0;
|
||||
int touchscreen = 0;
|
||||
if (size >= 14) {
|
||||
orientation = mIn.readByte();
|
||||
touchscreen = mIn.readByte();
|
||||
orientation = mIn.readUnsignedByte();
|
||||
touchscreen = mIn.readUnsignedByte();
|
||||
read = 14;
|
||||
}
|
||||
|
||||
@@ -541,77 +545,76 @@ public class ARSCDecoder {
|
||||
read = 16;
|
||||
}
|
||||
|
||||
byte keyboard = 0;
|
||||
byte navigation = 0;
|
||||
byte inputFlags = 0;
|
||||
byte grammaticalInflection = 0;
|
||||
int keyboard = 0;
|
||||
int navigation = 0;
|
||||
int inputFlags = 0;
|
||||
int grammaticalInflection = 0;
|
||||
if (size >= 20) {
|
||||
keyboard = mIn.readByte();
|
||||
navigation = mIn.readByte();
|
||||
inputFlags = mIn.readByte();
|
||||
grammaticalInflection = mIn.readByte();
|
||||
keyboard = mIn.readUnsignedByte();
|
||||
navigation = mIn.readUnsignedByte();
|
||||
inputFlags = mIn.readUnsignedByte();
|
||||
grammaticalInflection = mIn.readUnsignedByte();
|
||||
read = 20;
|
||||
}
|
||||
|
||||
short screenWidth = 0;
|
||||
short screenHeight = 0;
|
||||
short sdkVersion = 0;
|
||||
int screenWidth = 0;
|
||||
int screenHeight = 0;
|
||||
int sdkVersion = 0;
|
||||
int minorVersion = 0;
|
||||
if (size >= 28) {
|
||||
screenWidth = mIn.readShort();
|
||||
screenHeight = mIn.readShort();
|
||||
|
||||
sdkVersion = mIn.readShort();
|
||||
mIn.skipBytes(2); // minorVersion
|
||||
screenWidth = mIn.readUnsignedShort();
|
||||
screenHeight = mIn.readUnsignedShort();
|
||||
sdkVersion = mIn.readUnsignedShort();
|
||||
minorVersion = mIn.readUnsignedShort();
|
||||
read = 28;
|
||||
}
|
||||
|
||||
byte screenLayout = 0;
|
||||
byte uiMode = 0;
|
||||
short smallestScreenWidthDp = 0;
|
||||
int screenLayout = 0;
|
||||
int uiMode = 0;
|
||||
int smallestScreenWidthDp = 0;
|
||||
if (size >= 32) {
|
||||
screenLayout = mIn.readByte();
|
||||
uiMode = mIn.readByte();
|
||||
smallestScreenWidthDp = mIn.readShort();
|
||||
screenLayout = mIn.readUnsignedByte();
|
||||
uiMode = mIn.readUnsignedByte();
|
||||
smallestScreenWidthDp = mIn.readUnsignedShort();
|
||||
read = 32;
|
||||
}
|
||||
|
||||
short screenWidthDp = 0;
|
||||
short screenHeightDp = 0;
|
||||
int screenWidthDp = 0;
|
||||
int screenHeightDp = 0;
|
||||
if (size >= 36) {
|
||||
screenWidthDp = mIn.readShort();
|
||||
screenHeightDp = mIn.readShort();
|
||||
screenWidthDp = mIn.readUnsignedShort();
|
||||
screenHeightDp = mIn.readUnsignedShort();
|
||||
read = 36;
|
||||
}
|
||||
|
||||
char[] localeScript = null;
|
||||
char[] localeVariant = null;
|
||||
String localeScript = "";
|
||||
String localeVariant = "";
|
||||
if (size >= 48) {
|
||||
localeScript = readVariantLengthString(4).toCharArray();
|
||||
localeVariant = readVariantLengthString(8).toCharArray();
|
||||
localeScript = mIn.readAscii(4);
|
||||
localeVariant = mIn.readAscii(8);
|
||||
read = 48;
|
||||
}
|
||||
|
||||
byte screenLayout2 = 0;
|
||||
byte colorMode = 0;
|
||||
int screenLayout2 = 0;
|
||||
int colorMode = 0;
|
||||
if (size >= 52) {
|
||||
screenLayout2 = mIn.readByte();
|
||||
colorMode = mIn.readByte();
|
||||
mIn.skipBytes(2); // screenConfigPad2
|
||||
screenLayout2 = mIn.readUnsignedByte();
|
||||
colorMode = mIn.readUnsignedByte();
|
||||
mIn.skipShort(); // screenConfigPad2
|
||||
read = 52;
|
||||
}
|
||||
|
||||
char[] localeNumberingSystem = null;
|
||||
String localeNumberingSystem = "";
|
||||
if (size >= 60) {
|
||||
localeNumberingSystem = readVariantLengthString(8).toCharArray();
|
||||
localeNumberingSystem = mIn.readAscii(8);
|
||||
read = 60;
|
||||
}
|
||||
|
||||
int exceedingKnownSize = size - KNOWN_CONFIG_BYTES;
|
||||
|
||||
if (exceedingKnownSize > 0) {
|
||||
byte[] buf = new byte[exceedingKnownSize];
|
||||
byte[] buf = mIn.readBytes(exceedingKnownSize);
|
||||
read += exceedingKnownSize;
|
||||
mIn.readFully(buf);
|
||||
BigInteger exceedingBI = new BigInteger(1, buf);
|
||||
|
||||
if (exceedingBI.equals(BigInteger.ZERO)) {
|
||||
@@ -630,63 +633,53 @@ public class ARSCDecoder {
|
||||
mIn.skipBytes(remainingSize);
|
||||
}
|
||||
|
||||
return new ResConfigFlags(mcc, mnc, language, country,
|
||||
orientation, touchscreen, density, keyboard, navigation,
|
||||
inputFlags, grammaticalInflection, screenWidth, screenHeight, sdkVersion,
|
||||
screenLayout, uiMode, smallestScreenWidthDp, screenWidthDp,
|
||||
screenHeightDp, localeScript, localeVariant, screenLayout2,
|
||||
colorMode, localeNumberingSystem, size, isInvalid);
|
||||
}
|
||||
ResConfigFlags flags = new ResConfigFlags(mcc, mnc, language, region, orientation,
|
||||
touchscreen, density, keyboard, navigation, inputFlags,
|
||||
grammaticalInflection, screenWidth, screenHeight, sdkVersion,
|
||||
minorVersion, screenLayout, uiMode, smallestScreenWidthDp,
|
||||
screenWidthDp, screenHeightDp, localeScript, localeVariant,
|
||||
screenLayout2, colorMode, localeNumberingSystem);
|
||||
|
||||
private char[] unpackLanguageOrRegion(byte in0, byte in1, char base) {
|
||||
// check high bit, if so we have a packed 3 letter code
|
||||
if (((in0 >> 7) & 1) == 1) {
|
||||
int first = in1 & 0x1F;
|
||||
int second = ((in1 & 0xE0) >> 5) + ((in0 & 0x03) << 3);
|
||||
int third = (in0 & 0x7C) >> 2;
|
||||
|
||||
// since this function handles languages & regions, we add the value(s) to the base char
|
||||
// which is usually 'a' or '0' depending on language or region.
|
||||
return new char[] { (char) (first + base), (char) (second + base), (char) (third + base) };
|
||||
if (isInvalid || flags.isInvalid()) {
|
||||
mInvalidResConfigFlags.add(flags);
|
||||
}
|
||||
return new char[] { (char) in0, (char) in1 };
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
private String readVariantLengthString(int maxLength) throws IOException {
|
||||
StringBuilder string = new StringBuilder(16);
|
||||
|
||||
while (maxLength-- != 0) {
|
||||
short ch = mIn.readByte();
|
||||
if (ch == 0) {
|
||||
break;
|
||||
}
|
||||
string.append((char) ch);
|
||||
private String unpackLanguageOrRegion(byte[] value, char base) {
|
||||
assert value.length == 2;
|
||||
// return empty for "any" locale
|
||||
if (value[0] == 0) {
|
||||
return "";
|
||||
}
|
||||
mIn.skipBytes(maxLength);
|
||||
// if high bit is set then we have a packed 3-letter code
|
||||
if ((value[0] & 0x80) != 0) {
|
||||
byte first = (byte) (base + (value[1] & 0x1F));
|
||||
byte second = (byte) (base + ((value[1] & 0xE0) >>> 5) + ((value[0] & 0x03) << 3));
|
||||
byte third = (byte) (base + ((value[0] & 0x7C) >>> 2));
|
||||
|
||||
return string.toString();
|
||||
}
|
||||
|
||||
private void addTypeSpec(ResTypeSpec resTypeSpec) {
|
||||
mResTypeSpecs.put(resTypeSpec.getId(), resTypeSpec);
|
||||
value = new byte[] { first, second, third };
|
||||
}
|
||||
return new String(value, StandardCharsets.US_ASCII);
|
||||
}
|
||||
|
||||
private void addMissingResSpecs() throws AndrolibException {
|
||||
for (int resId : mMissingResSpecMap.keySet()) {
|
||||
int typeId = mMissingResSpecMap.get(resId);
|
||||
for (int resId : mMissingResSpecs.keySet()) {
|
||||
int typeId = mMissingResSpecs.get(resId);
|
||||
String resName = "APKTOOL_DUMMY_" + Integer.toHexString(resId);
|
||||
ResID id = new ResID(resId);
|
||||
ResResSpec spec = new ResResSpec(id, resName, mPkg, mResTypeSpecs.get(typeId));
|
||||
ResResSpec spec = new ResResSpec(id, resName, mPackage, mResTypeSpecs.get(typeId));
|
||||
|
||||
// If we already have this resId don't add it again.
|
||||
if (!mPkg.hasResSpec(id)) {
|
||||
mPkg.addResSpec(spec);
|
||||
if (!mPackage.hasResSpec(id)) {
|
||||
mPackage.addResSpec(spec);
|
||||
spec.getType().addResSpec(spec);
|
||||
ResType resType = mPkg.getOrCreateConfig(new ResConfigFlags());
|
||||
ResType resType = mPackage.getOrCreateConfig(new ResConfigFlags());
|
||||
|
||||
// We are going to make dummy attributes a null reference (@null) now instead of a boolean false.
|
||||
// This is because aapt2 is stricter when it comes to what we can put in an application.
|
||||
ResValue value = new ResReferenceValue(mPkg, 0, "");
|
||||
ResValue value = new ResReferenceValue(mPackage, 0, "");
|
||||
|
||||
ResResource res = new ResResource(resType, spec, value);
|
||||
resType.addResource(res);
|
||||
@@ -695,10 +688,6 @@ public class ARSCDecoder {
|
||||
}
|
||||
}
|
||||
|
||||
private ARSCHeader nextChunk() throws IOException {
|
||||
return mHeader = ARSCHeader.read(mIn);
|
||||
}
|
||||
|
||||
private void checkChunkType(int expectedType) throws AndrolibException {
|
||||
if (mHeader.type != expectedType) {
|
||||
throw new AndrolibException(String.format("Invalid chunk type: expected=0x%08x, got=0x%08x",
|
||||
|
||||
@@ -456,7 +456,7 @@ public class AXmlResourceParser implements XmlResourceParser {
|
||||
|
||||
// try to decode from resource table
|
||||
int attrResId = getAttributeNameResource(index);
|
||||
ResScalarValue resValue = mResTable.getCurrentResPackage()
|
||||
ResScalarValue resValue = mResTable.getCurrentPackage()
|
||||
.getValueFactory().factory(valueType, valueData, value);
|
||||
|
||||
String decoded = null;
|
||||
@@ -761,8 +761,8 @@ public class AXmlResourceParser implements XmlResourceParser {
|
||||
// Fake event, see CHUNK_XML_START_TAG handler.
|
||||
chunkType = ARSCHeader.RES_XML_START_ELEMENT_TYPE;
|
||||
} else {
|
||||
chunkType = mIn.readShort();
|
||||
headerSize = mIn.readShort();
|
||||
chunkType = mIn.readUnsignedShort();
|
||||
headerSize = mIn.readUnsignedShort();
|
||||
}
|
||||
|
||||
if (chunkType == ARSCHeader.RES_XML_RESOURCE_MAP_TYPE) {
|
||||
@@ -827,19 +827,19 @@ public class AXmlResourceParser implements XmlResourceParser {
|
||||
mNamespaceIndex = mIn.readInt();
|
||||
mNameIndex = mIn.readInt();
|
||||
mIn.skipShort(); // attributeStart
|
||||
int attributeSize = mIn.readShort();
|
||||
int attributeCount = mIn.readShort();
|
||||
mIdIndex = mIn.readShort();
|
||||
mClassIndex = mIn.readShort();
|
||||
mStyleIndex = mIn.readShort();
|
||||
int attributeSize = mIn.readUnsignedShort();
|
||||
int attributeCount = mIn.readUnsignedShort();
|
||||
mIdIndex = mIn.readUnsignedShort();
|
||||
mClassIndex = mIn.readUnsignedShort();
|
||||
mStyleIndex = mIn.readUnsignedShort();
|
||||
mAttributes = mIn.readIntArray(attributeCount * ATTRIBUTE_LENGTH);
|
||||
for (int i = ATTRIBUTE_IX_VALUE_TYPE; i < mAttributes.length; ) {
|
||||
mAttributes[i] = (mAttributes[i] >>> 24);
|
||||
mAttributes[i] >>>= 24;
|
||||
i += ATTRIBUTE_LENGTH;
|
||||
}
|
||||
|
||||
int byteAttrSizeRead = (attributeCount * ATTRIBUTE_LENGTH) * 4;
|
||||
int byteAttrSizeReported = (attributeSize * attributeCount);
|
||||
int byteAttrSizeRead = attributeCount * ATTRIBUTE_LENGTH * 4;
|
||||
int byteAttrSizeReported = attributeSize * attributeCount;
|
||||
|
||||
// Check for misleading chunk sizes
|
||||
if (byteAttrSizeRead < byteAttrSizeReported) {
|
||||
|
||||
@@ -19,7 +19,10 @@ package brut.androlib.res.decoder;
|
||||
import brut.androlib.exceptions.AndrolibException;
|
||||
import brut.androlib.exceptions.AXmlDecodingException;
|
||||
import brut.androlib.exceptions.RawXmlEncounteredException;
|
||||
import brut.androlib.res.data.ResTable;
|
||||
import brut.androlib.meta.ApkInfo;
|
||||
import brut.androlib.meta.PackageInfo;
|
||||
import brut.androlib.meta.SdkInfo;
|
||||
import brut.androlib.meta.VersionInfo;
|
||||
import brut.xmlpull.XmlPullUtils;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
@@ -30,10 +33,12 @@ import java.io.*;
|
||||
public class AndroidManifestPullStreamDecoder implements ResStreamDecoder {
|
||||
private final AXmlResourceParser mParser;
|
||||
private final XmlSerializer mSerial;
|
||||
private final EventHandler mEventHandler;
|
||||
|
||||
public AndroidManifestPullStreamDecoder(AXmlResourceParser parser, XmlSerializer serial) {
|
||||
mParser = parser;
|
||||
mSerial = serial;
|
||||
mEventHandler = new EventHandler();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -41,7 +46,7 @@ public class AndroidManifestPullStreamDecoder implements ResStreamDecoder {
|
||||
try {
|
||||
mParser.setInput(in, null);
|
||||
mSerial.setOutput(out, null);
|
||||
XmlPullUtils.copy(mParser, mSerial, new EventHandler(mParser.getResTable()));
|
||||
XmlPullUtils.copy(mParser, mSerial, mEventHandler);
|
||||
} catch (XmlPullParserException ex) {
|
||||
throw new AXmlDecodingException("Could not decode XML", ex);
|
||||
} catch (IOException ex) {
|
||||
@@ -49,13 +54,13 @@ public class AndroidManifestPullStreamDecoder implements ResStreamDecoder {
|
||||
}
|
||||
}
|
||||
|
||||
private static class EventHandler implements XmlPullUtils.EventHandler {
|
||||
private final ResTable mResTable;
|
||||
private class EventHandler implements XmlPullUtils.EventHandler {
|
||||
private final ApkInfo mApkInfo;
|
||||
private final boolean mHideSdkInfo;
|
||||
|
||||
public EventHandler(ResTable resTable) {
|
||||
mResTable = resTable;
|
||||
mHideSdkInfo = !resTable.getConfig().isAnalysisMode();
|
||||
public EventHandler() {
|
||||
mApkInfo = mParser.getResTable().getApkInfo();
|
||||
mHideSdkInfo = !mParser.getResTable().getConfig().isAnalysisMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -88,6 +93,9 @@ public class AndroidManifestPullStreamDecoder implements ResStreamDecoder {
|
||||
}
|
||||
|
||||
private void parseManifest(XmlPullParser in) {
|
||||
PackageInfo packageInfo = mApkInfo.getPackageInfo();
|
||||
VersionInfo versionInfo = mApkInfo.getVersionInfo();
|
||||
|
||||
for (int i = 0; i < in.getAttributeCount(); i++) {
|
||||
String ns = in.getAttributeNamespace(i);
|
||||
String name = in.getAttributeName(i);
|
||||
@@ -98,15 +106,15 @@ public class AndroidManifestPullStreamDecoder implements ResStreamDecoder {
|
||||
}
|
||||
if (ns.isEmpty()) {
|
||||
if (name.equals("package")) {
|
||||
mResTable.setPackageRenamed(value);
|
||||
packageInfo.setRenameManifestPackage(value);
|
||||
}
|
||||
} else if (ns.equals(AXmlResourceParser.ANDROID_RES_NS)) {
|
||||
switch (name) {
|
||||
case "versionCode":
|
||||
mResTable.setVersionCode(value);
|
||||
versionInfo.setVersionCode(value);
|
||||
break;
|
||||
case "versionName":
|
||||
mResTable.setVersionName(value);
|
||||
versionInfo.setVersionName(value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -114,6 +122,8 @@ public class AndroidManifestPullStreamDecoder implements ResStreamDecoder {
|
||||
}
|
||||
|
||||
private void parseUsesSdk(XmlPullParser in) {
|
||||
SdkInfo sdkInfo = mApkInfo.getSdkInfo();
|
||||
|
||||
for (int i = 0; i < in.getAttributeCount(); i++) {
|
||||
String ns = in.getAttributeNamespace(i);
|
||||
String name = in.getAttributeName(i);
|
||||
@@ -125,10 +135,13 @@ public class AndroidManifestPullStreamDecoder implements ResStreamDecoder {
|
||||
if (ns.equals(AXmlResourceParser.ANDROID_RES_NS)) {
|
||||
switch (name) {
|
||||
case "minSdkVersion":
|
||||
sdkInfo.setMinSdkVersion(value);
|
||||
break;
|
||||
case "targetSdkVersion":
|
||||
sdkInfo.setTargetSdkVersion(value);
|
||||
break;
|
||||
case "maxSdkVersion":
|
||||
case "compileSdkVersion":
|
||||
mResTable.addSdkInfo(name, value);
|
||||
sdkInfo.setMaxSdkVersion(value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,10 +31,10 @@ import java.awt.image.WritableRaster;
|
||||
import java.io.*;
|
||||
|
||||
public class Res9patchStreamDecoder implements ResStreamDecoder {
|
||||
private static final int NP_CHUNK_TYPE = 0x6e705463; // npTc
|
||||
private static final int OI_CHUNK_TYPE = 0x6e704c62; // npLb
|
||||
private static final int NP_COLOR = 0xff000000;
|
||||
private static final int OI_COLOR = 0xffff0000;
|
||||
private static final int NP_CHUNK_TYPE = 0x6E705463; // npTc
|
||||
private static final int OI_CHUNK_TYPE = 0x6E704C62; // npLb
|
||||
private static final int NP_COLOR = 0xFF000000;
|
||||
private static final int OI_COLOR = 0xFFFF0000;
|
||||
|
||||
@Override
|
||||
public void decode(InputStream in, OutputStream out) throws AndrolibException {
|
||||
@@ -68,8 +68,8 @@ public class Res9patchStreamDecoder implements ResStreamDecoder {
|
||||
}
|
||||
|
||||
NinePatchData np = getNinePatch(data);
|
||||
drawHLine(im2, h + 1, np.padLeft + 1, w - np.padRight);
|
||||
drawVLine(im2, w + 1, np.padTop + 1, h - np.padBottom);
|
||||
drawHLine(im2, h + 1, np.paddingLeft + 1, w - np.paddingRight);
|
||||
drawVLine(im2, w + 1, np.paddingTop + 1, h - np.paddingBottom);
|
||||
|
||||
int[] xDivs = np.xDivs;
|
||||
if (xDivs.length == 0) {
|
||||
|
||||
@@ -44,35 +44,39 @@ public class StringBlock {
|
||||
private int[] mStyles;
|
||||
private boolean mIsUtf8;
|
||||
|
||||
public static StringBlock readWithChunk(ExtDataInput reader) throws IOException {
|
||||
long startPosition = reader.position();
|
||||
reader.skipCheckShort(ARSCHeader.RES_STRING_POOL_TYPE);
|
||||
int headerSize = reader.readShort();
|
||||
int chunkSize = reader.readInt();
|
||||
public static StringBlock readWithChunk(ExtDataInput in) throws IOException {
|
||||
long startPosition = in.position();
|
||||
int chunkType = in.readUnsignedShort();
|
||||
if (chunkType != ARSCHeader.RES_STRING_POOL_TYPE) {
|
||||
throw new IOException(String.format("Invalid chunk type: expected=0x%08x, got=0x%08x",
|
||||
ARSCHeader.RES_STRING_POOL_TYPE, chunkType));
|
||||
}
|
||||
int headerSize = in.readUnsignedShort();
|
||||
int chunkSize = in.readInt();
|
||||
|
||||
return readWithoutChunk(reader, startPosition, headerSize, chunkSize);
|
||||
return readWithoutChunk(in, startPosition, headerSize, chunkSize);
|
||||
}
|
||||
|
||||
public static StringBlock readWithoutChunk(ExtDataInput reader, long startPosition,
|
||||
int headerSize, int chunkSize) throws IOException {
|
||||
public static StringBlock readWithoutChunk(ExtDataInput in, long startPosition, int headerSize,
|
||||
int chunkSize) throws IOException {
|
||||
// ResStringPool_header
|
||||
int stringCount = reader.readInt();
|
||||
int styleCount = reader.readInt();
|
||||
int flags = reader.readInt();
|
||||
int stringsOffset = reader.readInt();
|
||||
int stylesOffset = reader.readInt();
|
||||
int stringCount = in.readInt();
|
||||
int styleCount = in.readInt();
|
||||
int flags = in.readInt();
|
||||
int stringsOffset = in.readInt();
|
||||
int stylesOffset = in.readInt();
|
||||
|
||||
// For some applications they pack the StringBlock header with more unused data at end.
|
||||
if (headerSize > STRING_BLOCK_HEADER_SIZE) {
|
||||
reader.skipBytes(headerSize - STRING_BLOCK_HEADER_SIZE);
|
||||
in.skipBytes(headerSize - STRING_BLOCK_HEADER_SIZE);
|
||||
}
|
||||
|
||||
StringBlock block = new StringBlock();
|
||||
block.mIsUtf8 = (flags & UTF8_FLAG) != 0;
|
||||
block.mStringOffsets = reader.readSafeIntArray(stringCount, startPosition + stringsOffset);
|
||||
block.mStringOffsets = in.readSafeIntArray(stringCount, startPosition + stringsOffset);
|
||||
|
||||
if (styleCount != 0) {
|
||||
block.mStyleOffsets = reader.readSafeIntArray(styleCount, startPosition + stylesOffset);
|
||||
block.mStyleOffsets = in.readSafeIntArray(styleCount, startPosition + stylesOffset);
|
||||
}
|
||||
|
||||
// #3236 - Some applications give a style offset, but have 0 styles. Make this check more robust.
|
||||
@@ -85,19 +89,18 @@ public class StringBlock {
|
||||
size = stylesOffset - stringsOffset;
|
||||
}
|
||||
|
||||
block.mStrings = new byte[size];
|
||||
reader.readFully(block.mStrings);
|
||||
block.mStrings = in.readBytes(size);
|
||||
|
||||
if (hasStyles) {
|
||||
size = chunkSize - stylesOffset;
|
||||
block.mStyles = reader.readIntArray(size / 4);
|
||||
block.mStyles = in.readIntArray(size / 4);
|
||||
}
|
||||
|
||||
// In case we aren't 4 byte aligned we need to skip the remaining bytes.
|
||||
int remaining = size % 4;
|
||||
if (remaining >= 1) {
|
||||
while (remaining-- > 0) {
|
||||
reader.readByte();
|
||||
in.readByte();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,7 +264,7 @@ public class StringBlock {
|
||||
}
|
||||
|
||||
private static int getShort(byte[] array, int offset) {
|
||||
return (array[offset + 1] & 0xff) << 8 | array[offset] & 0xff;
|
||||
return ((array[offset + 1] & 0xFF) << 8) | (array[offset] & 0xFF);
|
||||
}
|
||||
|
||||
private static int[] getUtf8(byte[] array, int offset) {
|
||||
@@ -287,12 +290,12 @@ public class StringBlock {
|
||||
}
|
||||
|
||||
private static int[] getUtf16(byte[] array, int offset) {
|
||||
int val = (array[offset + 1] & 0xFF) << 8 | array[offset] & 0xFF;
|
||||
int val = ((array[offset + 1] & 0xFF) << 8) | (array[offset] & 0xFF);
|
||||
|
||||
if ((val & 0x8000) != 0) {
|
||||
int high = (array[offset + 3] & 0xFF) << 8;
|
||||
int low = (array[offset + 2] & 0xFF);
|
||||
int len_value = ((val & 0x7FFF) << 16) + (high + low);
|
||||
int low = array[offset + 2] & 0xFF;
|
||||
int len_value = ((val & 0x7FFF) << 16) + high + low;
|
||||
return new int[] { 4, len_value * 2 };
|
||||
|
||||
}
|
||||
|
||||
@@ -64,10 +64,11 @@ public final class ResXmlEncoders {
|
||||
out.append("\\n");
|
||||
continue;
|
||||
default:
|
||||
if (!isPrintableChar(c)) {
|
||||
out.append(String.format("\\u%04x", (int) c));
|
||||
continue;
|
||||
if (isPrintableChar(c)) {
|
||||
break;
|
||||
}
|
||||
out.append(String.format("\\u%04x", (int) c));
|
||||
continue;
|
||||
}
|
||||
out.append(c);
|
||||
}
|
||||
@@ -124,15 +125,15 @@ public final class ResXmlEncoders {
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (!isPrintableChar(c)) {
|
||||
|
||||
// let's not write trailing \u0000 if we are at end of string
|
||||
if ((out.length() + 1) == str.length() && c == '\u0000') {
|
||||
continue;
|
||||
}
|
||||
out.append(String.format("\\u%04x", (int) c));
|
||||
if (isPrintableChar(c)) {
|
||||
break;
|
||||
}
|
||||
// let's not write trailing \u0000 if we are at end of string
|
||||
if ((out.length() + 1) == str.length() && c == '\u0000') {
|
||||
continue;
|
||||
}
|
||||
out.append(String.format("\\u%04x", (int) c));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
out.append(c);
|
||||
|
||||
@@ -14,11 +14,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package brut.androlib.util;
|
||||
package brut.androlib;
|
||||
|
||||
import brut.androlib.BaseTest;
|
||||
import brut.common.BrutException;
|
||||
import brut.util.AaptManager;
|
||||
import brut.androlib.exceptions.AndrolibException;
|
||||
|
||||
import org.junit.*;
|
||||
import static org.junit.Assert.*;
|
||||
@@ -26,7 +24,7 @@ import static org.junit.Assert.*;
|
||||
public class AaptVersionTest extends BaseTest {
|
||||
|
||||
@Test
|
||||
public void testAapt2Iterations() throws BrutException {
|
||||
public void testAapt2Iterations() throws AndrolibException {
|
||||
assertEquals(2, AaptManager.getAaptVersionFromString("Android Asset Packaging Tool (aapt) 2:17"));
|
||||
assertEquals(2, AaptManager.getAaptVersionFromString("Android Asset Packaging Tool (aapt) 2.17"));
|
||||
assertEquals(1, AaptManager.getAaptVersionFromString("Android Asset Packaging Tool, v0.9"));
|
||||
@@ -16,8 +16,8 @@
|
||||
*/
|
||||
package brut.androlib;
|
||||
|
||||
import brut.directory.ExtFile;
|
||||
import brut.common.BrutException;
|
||||
import brut.directory.ExtFile;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@@ -43,8 +43,8 @@ public class AndResGuardTest extends BaseTest {
|
||||
|
||||
@Test
|
||||
public void checkIfAndResDecodeRemapsRFolderInRawMode() throws BrutException {
|
||||
sConfig.setForceDelete(true);
|
||||
sConfig.setDecodeResources(Config.DECODE_RESOURCES_NONE);
|
||||
sConfig.setForced(true);
|
||||
sConfig.setDecodeResources(Config.DecodeResources.NONE);
|
||||
|
||||
ExtFile testApk = new ExtFile(sTmpDir, TEST_APK);
|
||||
ExtFile testDir = new ExtFile(testApk + ".raw.out");
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
*/
|
||||
package brut.androlib;
|
||||
|
||||
import brut.directory.ExtFile;
|
||||
import brut.common.BrutException;
|
||||
import brut.directory.ExtFile;
|
||||
|
||||
import org.junit.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
*/
|
||||
package brut.androlib;
|
||||
|
||||
import brut.directory.ExtFile;
|
||||
import brut.common.BrutException;
|
||||
import brut.directory.ExtFile;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
package brut.androlib;
|
||||
|
||||
import brut.androlib.apk.ApkInfo;
|
||||
import brut.androlib.meta.ApkInfo;
|
||||
import brut.common.BrutException;
|
||||
import brut.directory.ExtFile;
|
||||
import brut.util.OSDetection;
|
||||
@@ -61,20 +61,20 @@ public class BuildAndDecodeApkTest extends BaseTest {
|
||||
@Test
|
||||
public void confirmFeatureFlagsRecorded() throws BrutException {
|
||||
ApkInfo testInfo = ApkInfo.load(sTestNewDir);
|
||||
assertTrue(testInfo.featureFlags.get("brut.feature.permission"));
|
||||
assertTrue(testInfo.featureFlags.get("brut.feature.activity"));
|
||||
assertTrue(testInfo.getFeatureFlags().get("brut.feature.permission"));
|
||||
assertTrue(testInfo.getFeatureFlags().get("brut.feature.activity"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void confirmZeroByteFileExtensionIsNotStored() throws BrutException {
|
||||
ApkInfo testInfo = ApkInfo.load(sTestNewDir);
|
||||
assertFalse(testInfo.doNotCompress.contains("jpg"));
|
||||
assertFalse(testInfo.getDoNotCompress().contains("jpg"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void confirmZeroByteFileIsStored() throws BrutException {
|
||||
ApkInfo testInfo = ApkInfo.load(sTestNewDir);
|
||||
assertTrue(testInfo.doNotCompress.contains("assets/0byte_file.jpg"));
|
||||
assertTrue(testInfo.getDoNotCompress().contains("assets/0byte_file.jpg"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -190,8 +190,9 @@ public class BuildAndDecodeApkTest extends BaseTest {
|
||||
|
||||
// long_string_32767 should be exactly 0x7FFF chars of "a",
|
||||
// which is the longest allowed length for UTF-8 strings.
|
||||
// the valuesExtraLongTest() should handle this
|
||||
// but such an edge case, want a specific test
|
||||
// String longer than that is replaced with "STRING_TOO_LARGE".
|
||||
// valuesExtraLongTest covers this scenario, but we want a specific test
|
||||
// for such an edge case.
|
||||
String expression = "/resources/string[@name='long_string_32767']/text()";
|
||||
String str = evaluateXPath(doc, expression, String.class);
|
||||
assertEquals(0x7FFF, str.length());
|
||||
@@ -222,24 +223,24 @@ public class BuildAndDecodeApkTest extends BaseTest {
|
||||
|
||||
@Test
|
||||
public void qualifiersTest() throws BrutException {
|
||||
compareValuesFiles("values-mcc004-mnc4-en-rUS-ldrtl-sw100dp-w200dp-h300dp"
|
||||
compareValuesFiles("values-mcc004-mnc04-en-rUS-ldrtl-sw100dp-w200dp-h300dp"
|
||||
+ "-long-round-highdr-land-desk-night-xhdpi-finger-keyssoft-12key"
|
||||
+ "-navhidden-dpad-v26/strings.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shortendedMncTest() throws BrutException {
|
||||
compareValuesFiles("values-mcc001-mnc1/strings.xml");
|
||||
compareValuesFiles("values-mcc001-mnc01/strings.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shortMncHtcTest() throws BrutException {
|
||||
compareValuesFiles("values-mnc1/strings.xml");
|
||||
compareValuesFiles("values-mnc01/strings.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shortMncv2Test() throws BrutException {
|
||||
compareValuesFiles("values-mcc238-mnc6/strings.xml");
|
||||
compareValuesFiles("values-mcc238-mnc06/strings.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -299,7 +300,7 @@ public class BuildAndDecodeApkTest extends BaseTest {
|
||||
|
||||
@Test
|
||||
public void variantBcp47Test() throws BrutException {
|
||||
compareValuesFiles("values-b+en+US+POSIX/strings.xml");
|
||||
compareValuesFiles("values-b+en+US+posix/strings.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -309,8 +310,8 @@ public class BuildAndDecodeApkTest extends BaseTest {
|
||||
|
||||
@Test
|
||||
public void valuesBcp47LanguageScriptRegionVariantTest() throws BrutException {
|
||||
compareValuesFiles("values-b+ast+Latn+IT+AREVELA/strings.xml");
|
||||
compareValuesFiles("values-b+ast+Hant+IT+ARABEXT/strings.xml");
|
||||
compareValuesFiles("values-b+ast+Latn+IT+arevela/strings.xml");
|
||||
compareValuesFiles("values-b+ast+Hant+IT+arabext/strings.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -547,7 +548,7 @@ public class BuildAndDecodeApkTest extends BaseTest {
|
||||
}
|
||||
|
||||
private static boolean isTransparent(int pixel) {
|
||||
return pixel >> 24 == 0x00;
|
||||
return pixel >> 24 == 0;
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
*/
|
||||
package brut.androlib;
|
||||
|
||||
import brut.directory.ExtFile;
|
||||
import brut.common.BrutException;
|
||||
import brut.directory.ExtFile;
|
||||
|
||||
import org.junit.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Ryszard Wiśniewski <[email protected]>
|
||||
* Copyright (C) 2010 Connor Tumbleson <[email protected]>
|
||||
*
|
||||
* 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.directory.ExtFile;
|
||||
import brut.common.BrutException;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import org.junit.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual;
|
||||
|
||||
public class DebugTagRetainedTest extends BaseTest {
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
sTestOrigDir = new ExtFile(sTmpDir, "issue1235-orig");
|
||||
sTestNewDir = new ExtFile(sTmpDir, "issue1235-new");
|
||||
|
||||
LOGGER.info("Unpacking issue1235...");
|
||||
TestUtils.copyResourceDir(DebugTagRetainedTest.class, "issue1235", sTestOrigDir);
|
||||
|
||||
sConfig.setDebugMode(true);
|
||||
|
||||
LOGGER.info("Building issue1235.apk...");
|
||||
ExtFile testApk = new ExtFile(sTmpDir, "issue1235.apk");
|
||||
new ApkBuilder(sTestOrigDir, sConfig).build(testApk);
|
||||
|
||||
LOGGER.info("Decoding issue1235.apk...");
|
||||
new ApkDecoder(testApk, sConfig).decode(sTestNewDir);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildAndDecodeTest() {
|
||||
assertTrue(sTestNewDir.isDirectory());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void debugIsTruePriorToBeingFalseTest() throws IOException, SAXException {
|
||||
String expected = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
|
||||
+ "<manifest package=\"com.ibotpeaches.issue1235\" platformBuildVersionCode=\"20\" platformBuildVersionName=\"4.4W.2-1537038\"\n"
|
||||
+ " xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
|
||||
+ " <application android:debuggable=\"true\"/>\n"
|
||||
+ "</manifest>";
|
||||
|
||||
File xml = new File(sTestNewDir, "AndroidManifest.xml");
|
||||
String obtained = new String(Files.readAllBytes(xml.toPath()));
|
||||
|
||||
assertXMLEqual(expected, obtained);
|
||||
}
|
||||
}
|
||||
@@ -16,8 +16,7 @@
|
||||
*/
|
||||
package brut.androlib;
|
||||
|
||||
import brut.androlib.apk.ApkInfo;
|
||||
import brut.androlib.res.ResourcesDecoder;
|
||||
import brut.androlib.meta.ApkInfo;
|
||||
import brut.androlib.res.data.ResTable;
|
||||
import brut.androlib.res.data.value.ResArrayValue;
|
||||
import brut.androlib.res.data.value.ResValue;
|
||||
@@ -37,9 +36,8 @@ public class DecodeArrayTest extends BaseTest {
|
||||
LOGGER.info("Decoding issue1994.apk...");
|
||||
ExtFile testApk = new ExtFile(sTmpDir, "issue1994.apk");
|
||||
ApkInfo testInfo = new ApkInfo(testApk);
|
||||
ResourcesDecoder resDecoder = new ResourcesDecoder(testInfo, sConfig);
|
||||
resDecoder.loadMainPkg();
|
||||
sResTable = resDecoder.getResTable();
|
||||
sResTable = new ResTable(testInfo, sConfig);
|
||||
sResTable.loadMainPackage();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
@@ -49,13 +47,13 @@ public class DecodeArrayTest extends BaseTest {
|
||||
|
||||
@Test
|
||||
public void decodeStringArray() throws BrutException {
|
||||
ResValue value = sResTable.getResSpec(0x7f020001).getDefaultResource().getValue();
|
||||
ResValue value = sResTable.getResSpec(0x7F020001).getDefaultResource().getValue();
|
||||
assertTrue("Not a ResArrayValue. Found: " + value.getClass(), value instanceof ResArrayValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodeArray() throws BrutException {
|
||||
ResValue value = sResTable.getResSpec(0x7f020000).getDefaultResource().getValue();
|
||||
ResValue value = sResTable.getResSpec(0x7F020000).getDefaultResource().getValue();
|
||||
assertTrue("Not a ResArrayValue. Found: " + value.getClass(), value instanceof ResArrayValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
*/
|
||||
package brut.androlib;
|
||||
|
||||
import brut.directory.ExtFile;
|
||||
import brut.common.BrutException;
|
||||
import brut.directory.ExtFile;
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
import java.io.File;
|
||||
@@ -25,17 +25,17 @@ import java.io.File;
|
||||
import org.junit.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class ResourceModeTest extends BaseTest {
|
||||
public class DecodeResolveTest extends BaseTest {
|
||||
private static final String TEST_APK = "issue2836.apk";
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
TestUtils.copyResourceDir(ResourceModeTest.class, "issue2836", sTmpDir);
|
||||
TestUtils.copyResourceDir(DecodeResolveTest.class, "issue2836", sTmpDir);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkDecodingModeAsRemove() throws BrutException {
|
||||
sConfig.setDecodeResolveMode(Config.DECODE_RES_RESOLVE_REMOVE);
|
||||
public void decodeResolveRemoveTest() throws BrutException {
|
||||
sConfig.setDecodeResolve(Config.DecodeResolve.REMOVE);
|
||||
|
||||
ExtFile testApk = new ExtFile(sTmpDir, TEST_APK);
|
||||
ExtFile testDir = new ExtFile(testApk + ".out.remove");
|
||||
@@ -58,8 +58,8 @@ public class ResourceModeTest extends BaseTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkDecodingModeAsDummies() throws BrutException {
|
||||
sConfig.setDecodeResolveMode(Config.DECODE_RES_RESOLVE_DUMMY);
|
||||
public void decodeResolveDummyTest() throws BrutException {
|
||||
sConfig.setDecodeResolve(Config.DecodeResolve.DUMMY);
|
||||
|
||||
ExtFile testApk = new ExtFile(sTmpDir, TEST_APK);
|
||||
ExtFile testDir = new ExtFile(testApk + ".out.dummies");
|
||||
@@ -82,8 +82,8 @@ public class ResourceModeTest extends BaseTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkDecodingModeAsLeave() throws BrutException {
|
||||
sConfig.setDecodeResolveMode(Config.DECODE_RES_RESOLVE_RETAIN);
|
||||
public void decodeResolveKeepTest() throws BrutException {
|
||||
sConfig.setDecodeResolve(Config.DecodeResolve.KEEP);
|
||||
|
||||
ExtFile testApk = new ExtFile(sTmpDir, TEST_APK);
|
||||
ExtFile testDir = new ExtFile(testApk + ".out.leave");
|
||||
@@ -26,7 +26,7 @@ import java.util.Arrays;
|
||||
import org.junit.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class ForceManifestDecodeNoResourcesTest extends BaseTest {
|
||||
public class DecodeResourcesTest extends BaseTest {
|
||||
private static final String TEST_APK = "issue1680.apk";
|
||||
|
||||
private static final byte[] XML_HEADER = {
|
||||
@@ -40,65 +40,13 @@ public class ForceManifestDecodeNoResourcesTest extends BaseTest {
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
TestUtils.copyResourceDir(ForceManifestDecodeNoResourcesTest.class, "issue1680", sTmpDir);
|
||||
TestUtils.copyResourceDir(DecodeResourcesTest.class, "issue1680", sTmpDir);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkIfForceManifestWithNoResourcesWorks() throws BrutException, IOException {
|
||||
sConfig.setForceDelete(true);
|
||||
sConfig.setDecodeResources(Config.DECODE_RESOURCES_NONE);
|
||||
sConfig.setForceDecodeManifest(Config.FORCE_DECODE_MANIFEST_FULL);
|
||||
|
||||
ExtFile testApk = new ExtFile(sTmpDir, TEST_APK);
|
||||
ExtFile testDir = new ExtFile(testApk + ".out");
|
||||
new ApkDecoder(testApk, sConfig).decode(testDir);
|
||||
|
||||
// assert that manifest is XML
|
||||
assertArrayEquals(XML_HEADER, TestUtils.readHeaderOfFile(new File(testDir, "AndroidManifest.xml"), 6));
|
||||
|
||||
// assert that resources.arsc exists
|
||||
assertTrue(new File(testDir, "resources.arsc").isFile());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkIfForceManifestWorksWithNoChangeToResources() throws BrutException, IOException {
|
||||
sConfig.setForceDelete(true);
|
||||
sConfig.setDecodeResources(Config.DECODE_RESOURCES_FULL);
|
||||
sConfig.setForceDecodeManifest(Config.FORCE_DECODE_MANIFEST_FULL);
|
||||
|
||||
ExtFile testApk = new ExtFile(sTmpDir, TEST_APK);
|
||||
ExtFile testDir = new ExtFile(testApk + ".out");
|
||||
new ApkDecoder(testApk, sConfig).decode(testDir);
|
||||
|
||||
// assert that manifest is XML
|
||||
assertArrayEquals(XML_HEADER, TestUtils.readHeaderOfFile(new File(testDir, "AndroidManifest.xml"), 6));
|
||||
|
||||
// assert that resources.arsc does not exist
|
||||
assertFalse(new File(testDir, "resources.arsc").isFile());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkForceManifestToFalseWithResourcesEnabledIsIgnored() throws BrutException, IOException {
|
||||
sConfig.setForceDelete(true);
|
||||
sConfig.setDecodeResources(Config.DECODE_RESOURCES_FULL);
|
||||
sConfig.setForceDecodeManifest(Config.FORCE_DECODE_MANIFEST_NONE);
|
||||
|
||||
ExtFile testApk = new ExtFile(sTmpDir, TEST_APK);
|
||||
ExtFile testDir = new ExtFile(testApk + ".out");
|
||||
new ApkDecoder(testApk, sConfig).decode(testDir);
|
||||
|
||||
// assert that manifest is XML
|
||||
assertArrayEquals(XML_HEADER, TestUtils.readHeaderOfFile(new File(testDir, "AndroidManifest.xml"), 6));
|
||||
|
||||
// assert that resources.arsc does not exist
|
||||
assertFalse(new File(testDir, "resources.arsc").isFile());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkBothManifestAndResourcesSetToNone() throws BrutException, IOException {
|
||||
sConfig.setForceDelete(true);
|
||||
sConfig.setDecodeResources(Config.DECODE_RESOURCES_NONE);
|
||||
sConfig.setForceDecodeManifest(Config.FORCE_DECODE_MANIFEST_NONE);
|
||||
public void decodeResourcesNoneTest() throws BrutException, IOException {
|
||||
sConfig.setForced(true);
|
||||
sConfig.setDecodeResources(Config.DecodeResources.NONE);
|
||||
|
||||
ExtFile testApk = new ExtFile(sTmpDir, TEST_APK);
|
||||
ExtFile testDir = new ExtFile(testApk + ".out");
|
||||
@@ -110,4 +58,36 @@ public class ForceManifestDecodeNoResourcesTest extends BaseTest {
|
||||
// assert that resources.arsc exists
|
||||
assertTrue(new File(testDir, "resources.arsc").isFile());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodeResourcesFullTest() throws BrutException, IOException {
|
||||
sConfig.setForced(true);
|
||||
sConfig.setDecodeResources(Config.DecodeResources.FULL);
|
||||
|
||||
ExtFile testApk = new ExtFile(sTmpDir, TEST_APK);
|
||||
ExtFile testDir = new ExtFile(testApk + ".out");
|
||||
new ApkDecoder(testApk, sConfig).decode(testDir);
|
||||
|
||||
// assert that manifest is XML
|
||||
assertArrayEquals(XML_HEADER, TestUtils.readHeaderOfFile(new File(testDir, "AndroidManifest.xml"), 6));
|
||||
|
||||
// assert that resources.arsc does not exist
|
||||
assertFalse(new File(testDir, "resources.arsc").isFile());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodeResourcesOnlyManifestTest() throws BrutException, IOException {
|
||||
sConfig.setForced(true);
|
||||
sConfig.setDecodeResources(Config.DecodeResources.ONLY_MANIFEST);
|
||||
|
||||
ExtFile testApk = new ExtFile(sTmpDir, TEST_APK);
|
||||
ExtFile testDir = new ExtFile(testApk + ".out");
|
||||
new ApkDecoder(testApk, sConfig).decode(testDir);
|
||||
|
||||
// assert that manifest is XML
|
||||
assertArrayEquals(XML_HEADER, TestUtils.readHeaderOfFile(new File(testDir, "AndroidManifest.xml"), 6));
|
||||
|
||||
// assert that resources.arsc exists
|
||||
assertTrue(new File(testDir, "resources.arsc").isFile());
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,9 @@
|
||||
*/
|
||||
package brut.androlib;
|
||||
|
||||
import brut.androlib.apk.ApkInfo;
|
||||
import brut.directory.ExtFile;
|
||||
import brut.androlib.meta.ApkInfo;
|
||||
import brut.common.BrutException;
|
||||
import brut.directory.ExtFile;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import org.junit.*;
|
||||
@@ -39,7 +39,7 @@ public class DoubleExtensionUnknownFileTest extends BaseTest {
|
||||
new ApkDecoder(testApk, sConfig).decode(testDir);
|
||||
|
||||
ApkInfo testInfo = ApkInfo.load(testDir);
|
||||
for (String path : testInfo.doNotCompress) {
|
||||
for (String path : testInfo.getDoNotCompress()) {
|
||||
if (StringUtils.countMatches(path, ".") > 1) {
|
||||
assertTrue(path.equals("assets/bin/Data/sharedassets1.assets.split0"));
|
||||
}
|
||||
|
||||
@@ -45,8 +45,8 @@ public class DuplicateDexTest extends BaseTest {
|
||||
|
||||
@Test
|
||||
public void decodeUsingOnlyMainClassesMode() throws BrutException {
|
||||
sConfig.setForceDelete(true);
|
||||
sConfig.setDecodeSources(Config.DECODE_SOURCES_SMALI_ONLY_MAIN_CLASSES);
|
||||
sConfig.setForced(true);
|
||||
sConfig.setDecodeSources(Config.DecodeSources.ONLY_MAIN_CLASSES);
|
||||
|
||||
LOGGER.info("Decoding " + TEST_APK + "...");
|
||||
ExtFile testApk = new ExtFile(sTmpDir, TEST_APK);
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
*/
|
||||
package brut.androlib;
|
||||
|
||||
import brut.directory.ExtFile;
|
||||
import brut.common.BrutException;
|
||||
import brut.directory.ExtFile;
|
||||
|
||||
import org.junit.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
@@ -16,8 +16,9 @@
|
||||
*/
|
||||
package brut.androlib;
|
||||
|
||||
import brut.directory.ExtFile;
|
||||
import brut.common.BrutException;
|
||||
import brut.directory.ExtFile;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -25,6 +26,7 @@ import java.nio.file.Files;
|
||||
|
||||
import org.junit.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual;
|
||||
|
||||
public class ExternalEntityTest extends BaseTest {
|
||||
|
||||
@@ -45,9 +47,9 @@ public class ExternalEntityTest extends BaseTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doctypeTest() throws IOException {
|
||||
public void doctypeTest() throws IOException, SAXException {
|
||||
String expected = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
|
||||
+ "<manifest android:versionCode=\"1\" android:versionName=\"1.0\" hardwareAccelerated=\"true\" package=\"com.ibotpeaches.doctype\" platformBuildVersionCode=\"24\" platformBuildVersionName=\"6.0-2456767\"\n"
|
||||
+ "<manifest hardwareAccelerated=\"true\" package=\"com.ibotpeaches.doctype\" platformBuildVersionCode=\"24\" platformBuildVersionName=\"6.0-2456767\"\n"
|
||||
+ " xmlns:android=\"http://schemas.android.com/apk/res/android\">\n"
|
||||
+ " <supports-screens android:anyDensity=\"true\" android:smallScreens=\"true\" android:normalScreens=\"true\" android:largeScreens=\"true\" android:resizeable=\"true\" android:xlargeScreens=\"true\" />\n"
|
||||
+ "</manifest>";
|
||||
@@ -55,6 +57,6 @@ public class ExternalEntityTest extends BaseTest {
|
||||
File xml = new File(sTestNewDir, "AndroidManifest.xml");
|
||||
String obtained = new String(Files.readAllBytes(xml.toPath()));
|
||||
|
||||
assertEquals(TestUtils.replaceNewlines(expected), TestUtils.replaceNewlines(obtained));
|
||||
assertXMLEqual(expected, obtained);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
*/
|
||||
package brut.androlib;
|
||||
|
||||
import brut.directory.ExtFile;
|
||||
import brut.common.BrutException;
|
||||
import brut.directory.ExtFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
*/
|
||||
package brut.androlib;
|
||||
|
||||
import brut.directory.ExtFile;
|
||||
import brut.common.BrutException;
|
||||
import brut.directory.ExtFile;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import java.io.File;
|
||||
@@ -34,7 +34,7 @@ public class MinifiedArscTest extends BaseTest {
|
||||
public static void beforeClass() throws Exception {
|
||||
TestUtils.copyResourceDir(MinifiedArscTest.class, "issue1157", sTmpDir);
|
||||
|
||||
sConfig.setForceDelete(true);
|
||||
sConfig.setForced(true);
|
||||
|
||||
ExtFile testApk = new ExtFile(sTmpDir, "issue1157.apk");
|
||||
sTestNewDir = new ExtFile(testApk + ".out");
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
package brut.androlib;
|
||||
|
||||
import brut.androlib.apk.ApkInfo;
|
||||
import brut.androlib.meta.ApkInfo;
|
||||
import brut.common.BrutException;
|
||||
import brut.directory.ExtFile;
|
||||
|
||||
@@ -38,6 +38,6 @@ public class MissingVersionManifestTest extends BaseTest {
|
||||
new ApkDecoder(testApk, sConfig).decode(testDir);
|
||||
|
||||
ApkInfo testInfo = ApkInfo.load(testDir);
|
||||
assertNull(testInfo.versionInfo.versionName);
|
||||
assertNull(testInfo.getVersionInfo().getVersionName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,19 +32,19 @@ public class NetworkConfigTest extends BaseTest {
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
sTestOrigDir = new ExtFile(sTmpDir, "testapp-orig");
|
||||
sTestNewDir = new ExtFile(sTmpDir, "testapp-new");
|
||||
sTestOrigDir = new ExtFile(sTmpDir, "network_config-orig");
|
||||
sTestNewDir = new ExtFile(sTmpDir, "network_config-new");
|
||||
|
||||
LOGGER.info("Unpacking testapp...");
|
||||
TestUtils.copyResourceDir(NetworkConfigTest.class, "network_config", sTestOrigDir);
|
||||
LOGGER.info("Unpacking network_config...");
|
||||
TestUtils.copyResourceDir(NetworkConfigTest.class, "network_config/existing", sTestOrigDir);
|
||||
|
||||
sConfig.setNetSecConf(true);
|
||||
|
||||
LOGGER.info("Building testapp.apk...");
|
||||
ExtFile testApk = new ExtFile(sTmpDir, "testapp.apk");
|
||||
LOGGER.info("Building network_config.apk...");
|
||||
ExtFile testApk = new ExtFile(sTmpDir, "network_config.apk");
|
||||
new ApkBuilder(sTestOrigDir, sConfig).build(testApk);
|
||||
|
||||
LOGGER.info("Decoding testapp.apk...");
|
||||
LOGGER.info("Decoding network_config.apk...");
|
||||
new ApkDecoder(testApk, sConfig).decode(sTestNewDir);
|
||||
}
|
||||
|
||||
|
||||
@@ -35,19 +35,19 @@ public class NoNetworkConfigTest extends BaseTest {
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
sTestOrigDir = new ExtFile(sTmpDir, "testapp-orig");
|
||||
sTestNewDir = new ExtFile(sTmpDir, "testapp-new");
|
||||
sTestOrigDir = new ExtFile(sTmpDir, "network_config-orig");
|
||||
sTestNewDir = new ExtFile(sTmpDir, "network_config-new");
|
||||
|
||||
LOGGER.info("Unpacking testapp...");
|
||||
TestUtils.copyResourceDir(NoNetworkConfigTest.class, "testapp", sTestOrigDir);
|
||||
LOGGER.info("Unpacking network_config...");
|
||||
TestUtils.copyResourceDir(NoNetworkConfigTest.class, "network_config/none", sTestOrigDir);
|
||||
|
||||
sConfig.setNetSecConf(true);
|
||||
|
||||
LOGGER.info("Building testapp.apk...");
|
||||
ExtFile testApk = new ExtFile(sTmpDir, "testapp.apk");
|
||||
LOGGER.info("Building network_config.apk...");
|
||||
ExtFile testApk = new ExtFile(sTmpDir, "network_config.apk");
|
||||
new ApkBuilder(sTestOrigDir, sConfig).build(testApk);
|
||||
|
||||
LOGGER.info("Decoding testapp.apk...");
|
||||
LOGGER.info("Decoding network_config.apk...");
|
||||
new ApkDecoder(testApk, sConfig).decode(sTestNewDir);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
package brut.androlib;
|
||||
|
||||
import brut.androlib.apk.ApkInfo;
|
||||
import brut.androlib.meta.ApkInfo;
|
||||
import brut.androlib.res.ResourcesDecoder;
|
||||
import brut.androlib.res.data.ResTable;
|
||||
import brut.common.BrutException;
|
||||
@@ -74,7 +74,7 @@ public class NonStandardPkgIdTest extends BaseTest {
|
||||
|
||||
@Test
|
||||
public void confirmResourcesAreFromPkgId8() throws BrutException {
|
||||
assertEquals(0x80, sResTable.getPackageId());
|
||||
assertEquals(0x80, sResTable.getMainPackage().getId());
|
||||
|
||||
assertEquals(0x80, sResTable.getResSpec(0x80020000).getPackage().getId());
|
||||
assertEquals(0x80, sResTable.getResSpec(0x80020001).getPackage().getId());
|
||||
|
||||
@@ -32,8 +32,8 @@ public class ParentDirectoryTraversalTest extends BaseTest {
|
||||
|
||||
@Test
|
||||
public void checkIfDrawableFileDecodesProperly() throws BrutException {
|
||||
sConfig.setForceDelete(true);
|
||||
sConfig.setDecodeResources(Config.DECODE_RESOURCES_NONE);
|
||||
sConfig.setForced(true);
|
||||
sConfig.setDecodeResources(Config.DecodeResources.NONE);
|
||||
|
||||
ExtFile testApk = new ExtFile(sTmpDir, apk);
|
||||
ExtFile testDir = new ExtFile(testApk + ".out");
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
*/
|
||||
package brut.androlib;
|
||||
|
||||
import brut.directory.ExtFile;
|
||||
import brut.common.BrutException;
|
||||
import brut.directory.ExtFile;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
*/
|
||||
package brut.androlib;
|
||||
|
||||
import brut.directory.ExtFile;
|
||||
import brut.common.BrutException;
|
||||
import brut.directory.ExtFile;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
*/
|
||||
package brut.androlib;
|
||||
|
||||
import brut.androlib.apk.ApkInfo;
|
||||
import brut.directory.ExtFile;
|
||||
import brut.androlib.meta.ApkInfo;
|
||||
import brut.common.BrutException;
|
||||
import brut.directory.ExtFile;
|
||||
|
||||
import org.junit.*;
|
||||
import static org.junit.Assert.*;
|
||||
@@ -38,6 +38,6 @@ public class ReferenceVersionCodeTest extends BaseTest {
|
||||
new ApkDecoder(testApk, sConfig).decode(testDir);
|
||||
|
||||
ApkInfo testInfo = ApkInfo.load(testDir);
|
||||
assertEquals("v1.0.0", testInfo.versionInfo.versionName);
|
||||
assertEquals("v1.0.0", testInfo.getVersionInfo().getVersionName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ public class ResourceDirectoryTraversalTest extends BaseTest {
|
||||
|
||||
@Test
|
||||
public void checkIfMaliciousRawFileIsDisassembledProperly() throws BrutException {
|
||||
sConfig.setForceDelete(true);
|
||||
sConfig.setForced(true);
|
||||
|
||||
ExtFile testApk = new ExtFile(sTmpDir, TEST_APK);
|
||||
ExtFile testDir = new ExtFile(testApk + ".out");
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
package brut.androlib;
|
||||
|
||||
import brut.androlib.res.Framework;
|
||||
import brut.directory.ExtFile;
|
||||
import brut.common.BrutException;
|
||||
import brut.directory.ExtFile;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
*/
|
||||
package brut.androlib;
|
||||
|
||||
import brut.directory.ExtFile;
|
||||
import brut.common.BrutException;
|
||||
import brut.directory.ExtFile;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@@ -34,8 +34,8 @@ public class SkipAssetTest extends BaseTest {
|
||||
|
||||
@Test
|
||||
public void checkIfEnablingSkipAssetWorks() throws BrutException {
|
||||
sConfig.setForceDelete(true);
|
||||
sConfig.setDecodeAssets(Config.DECODE_ASSETS_NONE);
|
||||
sConfig.setForced(true);
|
||||
sConfig.setDecodeAssets(Config.DecodeAssets.NONE);
|
||||
|
||||
ExtFile testApk = new ExtFile(sTmpDir, TEST_APK);
|
||||
ExtFile testDir = new ExtFile(testApk + ".out");
|
||||
@@ -47,8 +47,8 @@ public class SkipAssetTest extends BaseTest {
|
||||
|
||||
@Test
|
||||
public void checkControl() throws BrutException {
|
||||
sConfig.setForceDelete(true);
|
||||
sConfig.setDecodeAssets(Config.DECODE_ASSETS_FULL);
|
||||
sConfig.setForced(true);
|
||||
sConfig.setDecodeAssets(Config.DecodeAssets.FULL);
|
||||
|
||||
ExtFile testApk = new ExtFile(sTmpDir, TEST_APK);
|
||||
ExtFile testDir = new ExtFile(testApk + ".out");
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
package brut.androlib;
|
||||
|
||||
import brut.androlib.apk.ApkInfo;
|
||||
import brut.androlib.meta.ApkInfo;
|
||||
import brut.common.BrutException;
|
||||
import brut.directory.ExtFile;
|
||||
|
||||
@@ -40,7 +40,7 @@ public class SparseFlagTest extends BaseTest {
|
||||
ExtFile testDir = new ExtFile(testApk + ".out");
|
||||
ApkInfo apkInfo = new ApkDecoder(testApk, sConfig).decode(testDir);
|
||||
|
||||
assertTrue("Expecting sparse resources", apkInfo.sparseResources);
|
||||
assertTrue("Expecting sparse resources", apkInfo.isSparseResources());
|
||||
|
||||
LOGGER.info("Building sparse.apk...");
|
||||
new ApkBuilder(testDir, sConfig).build(null);
|
||||
@@ -55,7 +55,7 @@ public class SparseFlagTest extends BaseTest {
|
||||
ExtFile testDir = new ExtFile(testApk + ".out");
|
||||
ApkInfo apkInfo = new ApkDecoder(testApk, sConfig).decode(testDir);
|
||||
|
||||
assertFalse("Expecting not-sparse resources", apkInfo.sparseResources);
|
||||
assertFalse("Expecting not-sparse resources", apkInfo.isSparseResources());
|
||||
|
||||
LOGGER.info("Building not-sparse.apk...");
|
||||
new ApkBuilder(testDir, sConfig).build(null);
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
*/
|
||||
package brut.androlib;
|
||||
|
||||
import brut.directory.ExtFile;
|
||||
import brut.common.BrutException;
|
||||
import brut.directory.ExtFile;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Ryszard Wiśniewski <[email protected]>
|
||||
* Copyright (C) 2010 Connor Tumbleson <[email protected]>
|
||||
*
|
||||
* 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.BaseTest;
|
||||
import brut.common.BrutException;
|
||||
|
||||
import org.junit.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class ApkInfoReaderTest extends BaseTest {
|
||||
|
||||
private void checkStandard(ApkInfo apkInfo) {
|
||||
assertEquals("standard.apk", apkInfo.apkFileName);
|
||||
assertEquals(1, apkInfo.doNotCompress.size());
|
||||
assertEquals("arsc", apkInfo.doNotCompress.iterator().next());
|
||||
assertNotNull(apkInfo.packageInfo);
|
||||
assertEquals("127", apkInfo.packageInfo.forcedPackageId);
|
||||
assertNull(apkInfo.packageInfo.renameManifestPackage);
|
||||
assertNotNull(apkInfo.sdkInfo);
|
||||
assertEquals(2, apkInfo.sdkInfo.size());
|
||||
assertEquals("25", apkInfo.sdkInfo.get("minSdkVersion"));
|
||||
assertEquals("30", apkInfo.sdkInfo.get("targetSdkVersion"));
|
||||
assertFalse(apkInfo.sparseResources);
|
||||
assertNotNull(apkInfo.usesFramework);
|
||||
assertNotNull(apkInfo.usesFramework.ids);
|
||||
assertEquals(1, apkInfo.usesFramework.ids.size());
|
||||
assertEquals(1, (long) apkInfo.usesFramework.ids.get(0));
|
||||
assertNull(apkInfo.usesFramework.tag);
|
||||
assertNotNull(apkInfo.versionInfo);
|
||||
assertNull(apkInfo.versionInfo.versionCode);
|
||||
assertNull(apkInfo.versionInfo.versionName);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStandard() throws BrutException {
|
||||
ApkInfo apkInfo = ApkInfo.load(
|
||||
getClass().getResourceAsStream("/apk/standard.yml"));
|
||||
checkStandard(apkInfo);
|
||||
assertEquals("2.8.1", apkInfo.version);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnknownFields() throws BrutException {
|
||||
ApkInfo apkInfo = ApkInfo.load(
|
||||
getClass().getResourceAsStream("/apk/unknown_fields.yml"));
|
||||
checkStandard(apkInfo);
|
||||
assertEquals("2.8.1", apkInfo.version);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipIncorrectIndent() throws BrutException {
|
||||
ApkInfo apkInfo = ApkInfo.load(
|
||||
getClass().getResourceAsStream("/apk/skip_incorrect_indent.yml"));
|
||||
checkStandard(apkInfo);
|
||||
assertNotEquals("2.0.0", apkInfo.version);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFirstIncorrectIndent() throws BrutException {
|
||||
ApkInfo apkInfo = ApkInfo.load(
|
||||
getClass().getResourceAsStream("/apk/first_incorrect_indent.yml"));
|
||||
checkStandard(apkInfo);
|
||||
assertNotEquals("2.0.0", apkInfo.version);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnknownFiles() throws BrutException {
|
||||
ApkInfo apkInfo = ApkInfo.load(
|
||||
getClass().getResourceAsStream("/apk/unknown_files.yml"));
|
||||
assertEquals("2.0.0", apkInfo.version);
|
||||
assertEquals("testapp.apk", apkInfo.apkFileName);
|
||||
assertNotNull(apkInfo.usesFramework);
|
||||
assertEquals(1, apkInfo.usesFramework.ids.size());
|
||||
assertEquals(1, (long) apkInfo.usesFramework.ids.get(0));
|
||||
assertNotNull(apkInfo.packageInfo);
|
||||
assertEquals("127", apkInfo.packageInfo.forcedPackageId);
|
||||
assertNotNull(apkInfo.versionInfo);
|
||||
assertEquals("1", apkInfo.versionInfo.versionCode);
|
||||
assertEquals("1.0", apkInfo.versionInfo.versionName);
|
||||
assertNotNull(apkInfo.doNotCompress);
|
||||
assertEquals(5, apkInfo.doNotCompress.size());
|
||||
assertEquals("assets/0byte_file.jpg", apkInfo.doNotCompress.get(0));
|
||||
assertEquals("arsc", apkInfo.doNotCompress.get(1));
|
||||
assertEquals("png", apkInfo.doNotCompress.get(2));
|
||||
assertEquals("mp3", apkInfo.doNotCompress.get(3));
|
||||
assertEquals("stored.file", apkInfo.doNotCompress.get(4));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUlist_with_indent() throws BrutException {
|
||||
ApkInfo apkInfo = ApkInfo.load(
|
||||
getClass().getResourceAsStream("/apk/list_with_indent.yml"));
|
||||
assertEquals("2.8.0", apkInfo.version);
|
||||
assertEquals("basic.apk", apkInfo.apkFileName);
|
||||
assertNotNull(apkInfo.usesFramework);
|
||||
assertEquals(1, apkInfo.usesFramework.ids.size());
|
||||
assertEquals(1, (long) apkInfo.usesFramework.ids.get(0));
|
||||
assertEquals("tag", apkInfo.usesFramework.tag);
|
||||
assertNotNull(apkInfo.packageInfo);
|
||||
assertEquals("127", apkInfo.packageInfo.forcedPackageId);
|
||||
assertEquals("com.test.basic", apkInfo.packageInfo.renameManifestPackage);
|
||||
assertNotNull(apkInfo.sdkInfo);
|
||||
assertEquals(3, apkInfo.sdkInfo.size());
|
||||
assertEquals("4", apkInfo.sdkInfo.get("minSdkVersion"));
|
||||
assertEquals("30", apkInfo.sdkInfo.get("maxSdkVersion"));
|
||||
assertEquals("22", apkInfo.sdkInfo.get("targetSdkVersion"));
|
||||
assertTrue(apkInfo.sparseResources);
|
||||
assertNotNull(apkInfo.versionInfo);
|
||||
assertEquals("71", apkInfo.versionInfo.versionCode);
|
||||
assertEquals("1.0.70", apkInfo.versionInfo.versionName);
|
||||
assertNotNull(apkInfo.doNotCompress);
|
||||
assertEquals(2, apkInfo.doNotCompress.size());
|
||||
assertEquals("arsc", apkInfo.doNotCompress.get(0));
|
||||
assertEquals("png", apkInfo.doNotCompress.get(1));
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Ryszard Wiśniewski <[email protected]>
|
||||
* Copyright (C) 2010 Connor Tumbleson <[email protected]>
|
||||
*
|
||||
* 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.BaseTest;
|
||||
import brut.common.BrutException;
|
||||
|
||||
import org.junit.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class ConsistentPropertyTest extends BaseTest {
|
||||
|
||||
@Test
|
||||
public void testAssertingAllKnownApkInfoProperties() throws BrutException {
|
||||
ApkInfo apkInfo = ApkInfo.load(
|
||||
getClass().getResourceAsStream("/apk/basic.yml"));
|
||||
|
||||
assertEquals("2.8.0", apkInfo.version);
|
||||
assertEquals("basic.apk", apkInfo.apkFileName);
|
||||
assertEquals(1, apkInfo.usesFramework.ids.size());
|
||||
assertEquals("tag", apkInfo.usesFramework.tag);
|
||||
assertEquals("4", apkInfo.getMinSdkVersion());
|
||||
assertEquals("22", apkInfo.getTargetSdkVersion());
|
||||
assertEquals("30", apkInfo.getMaxSdkVersion());
|
||||
assertEquals("127", apkInfo.packageInfo.forcedPackageId);
|
||||
assertEquals("com.test.basic", apkInfo.packageInfo.renameManifestPackage);
|
||||
assertEquals("71", apkInfo.versionInfo.versionCode);
|
||||
assertEquals("1.0.70", apkInfo.versionInfo.versionName);
|
||||
assertTrue(apkInfo.sparseResources);
|
||||
assertEquals(2, apkInfo.doNotCompress.size());
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Ryszard Wiśniewski <[email protected]>
|
||||
* Copyright (C) 2010 Connor Tumbleson <[email protected]>
|
||||
*
|
||||
* 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.BaseTest;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class InvalidSdkBoundingTest extends BaseTest {
|
||||
|
||||
@Test
|
||||
public void checkIfInvalidValuesPass() {
|
||||
ApkInfo apkInfo = new ApkInfo();
|
||||
|
||||
Map<String, String> sdkInfo = new LinkedHashMap<>();
|
||||
sdkInfo.put("minSdkVersion", "15");
|
||||
sdkInfo.put("targetSdkVersion", "25");
|
||||
sdkInfo.put("maxSdkVersion", "19");
|
||||
|
||||
apkInfo.sdkInfo = sdkInfo;
|
||||
assertEquals("19", apkInfo.checkTargetSdkVersionBounds());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkIfMissingMinPasses() {
|
||||
ApkInfo apkInfo = new ApkInfo();
|
||||
|
||||
Map<String, String> sdkInfo = new LinkedHashMap<>();
|
||||
sdkInfo.put("targetSdkVersion", "25");
|
||||
sdkInfo.put("maxSdkVersion", "19");
|
||||
|
||||
apkInfo.sdkInfo = sdkInfo;
|
||||
assertEquals("19", apkInfo.checkTargetSdkVersionBounds());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkIfMissingMaxPasses() {
|
||||
ApkInfo apkInfo = new ApkInfo();
|
||||
|
||||
Map<String, String> sdkInfo = new LinkedHashMap<>();
|
||||
sdkInfo.put("minSdkVersion", "15");
|
||||
sdkInfo.put("targetSdkVersion", "25");
|
||||
|
||||
apkInfo.sdkInfo = sdkInfo;
|
||||
assertEquals("25", apkInfo.checkTargetSdkVersionBounds());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkIfMissingBothPasses() {
|
||||
ApkInfo apkInfo = new ApkInfo();
|
||||
|
||||
Map<String, String> sdkInfo = new LinkedHashMap<>();
|
||||
sdkInfo.put("targetSdkVersion", "25");
|
||||
|
||||
apkInfo.sdkInfo = sdkInfo;
|
||||
assertEquals("25", apkInfo.checkTargetSdkVersionBounds());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkForShortHandSTag() {
|
||||
ApkInfo apkInfo = new ApkInfo();
|
||||
|
||||
Map<String, String> sdkInfo = new LinkedHashMap<>();
|
||||
sdkInfo.put("targetSdkVersion", "S");
|
||||
|
||||
apkInfo.sdkInfo = sdkInfo;
|
||||
assertEquals("31", apkInfo.checkTargetSdkVersionBounds());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkForShortHandSdkTag() {
|
||||
ApkInfo apkInfo = new ApkInfo();
|
||||
|
||||
Map<String, String> sdkInfo = new LinkedHashMap<>();
|
||||
sdkInfo.put("targetSdkVersion", "O");
|
||||
|
||||
apkInfo.sdkInfo = sdkInfo;
|
||||
assertEquals("26", apkInfo.checkTargetSdkVersionBounds());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkForSdkDevelopmentInsaneTestValue() {
|
||||
ApkInfo apkInfo = new ApkInfo();
|
||||
|
||||
Map<String, String> sdkInfo = new LinkedHashMap<>();
|
||||
sdkInfo.put("targetSdkVersion", "SDK_CUR_DEVELOPMENT");
|
||||
|
||||
apkInfo.sdkInfo = sdkInfo;
|
||||
assertEquals("10000", apkInfo.checkTargetSdkVersionBounds());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Ryszard Wiśniewski <[email protected]>
|
||||
* Copyright (C) 2010 Connor Tumbleson <[email protected]>
|
||||
*
|
||||
* 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.BaseTest;
|
||||
import brut.common.BrutException;
|
||||
|
||||
import org.junit.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class ApkInfoReaderTest extends BaseTest {
|
||||
|
||||
private void checkStandard(ApkInfo apkInfo) {
|
||||
assertEquals("standard.apk", apkInfo.getApkFileName());
|
||||
assertEquals(1, apkInfo.getDoNotCompress().size());
|
||||
assertEquals("arsc", apkInfo.getDoNotCompress().get(0));
|
||||
assertNotNull(apkInfo.getPackageInfo());
|
||||
assertEquals("127", apkInfo.getPackageInfo().getForcedPackageId());
|
||||
assertNull(apkInfo.getPackageInfo().getRenameManifestPackage());
|
||||
assertNotNull(apkInfo.getSdkInfo());
|
||||
assertEquals("25", apkInfo.getSdkInfo().getMinSdkVersion());
|
||||
assertEquals("30", apkInfo.getSdkInfo().getTargetSdkVersion());
|
||||
assertFalse(apkInfo.isSparseResources());
|
||||
assertNotNull(apkInfo.getUsesFramework());
|
||||
assertNotNull(apkInfo.getUsesFramework().getIds());
|
||||
assertEquals(1, apkInfo.getUsesFramework().getIds().size());
|
||||
assertEquals(1, (long) apkInfo.getUsesFramework().getIds().get(0));
|
||||
assertNull(apkInfo.getUsesFramework().getTag());
|
||||
assertNotNull(apkInfo.getVersionInfo());
|
||||
assertNull(apkInfo.getVersionInfo().getVersionCode());
|
||||
assertNull(apkInfo.getVersionInfo().getVersionName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStandard() throws BrutException {
|
||||
ApkInfo apkInfo = ApkInfo.load(getClass().getResourceAsStream("/meta/standard.yml"));
|
||||
checkStandard(apkInfo);
|
||||
assertEquals("2.8.1", apkInfo.getVersion());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnknownFields() throws BrutException {
|
||||
ApkInfo apkInfo = ApkInfo.load(getClass().getResourceAsStream("/meta/unknown_fields.yml"));
|
||||
checkStandard(apkInfo);
|
||||
assertEquals("2.8.1", apkInfo.getVersion());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipIncorrectIndent() throws BrutException {
|
||||
ApkInfo apkInfo = ApkInfo.load(getClass().getResourceAsStream("/meta/skip_incorrect_indent.yml"));
|
||||
checkStandard(apkInfo);
|
||||
assertNotEquals("2.0.0", apkInfo.getVersion());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFirstIncorrectIndent() throws BrutException {
|
||||
ApkInfo apkInfo = ApkInfo.load(getClass().getResourceAsStream("/meta/first_incorrect_indent.yml"));
|
||||
checkStandard(apkInfo);
|
||||
assertNotEquals("2.0.0", apkInfo.getVersion());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnknownFiles() throws BrutException {
|
||||
ApkInfo apkInfo = ApkInfo.load(getClass().getResourceAsStream("/meta/unknown_files.yml"));
|
||||
assertEquals("2.0.0", apkInfo.getVersion());
|
||||
assertEquals("testapp.apk", apkInfo.getApkFileName());
|
||||
assertNotNull(apkInfo.getUsesFramework());
|
||||
assertEquals(1, apkInfo.getUsesFramework().getIds().size());
|
||||
assertEquals(1, (long) apkInfo.getUsesFramework().getIds().get(0));
|
||||
assertNotNull(apkInfo.getPackageInfo());
|
||||
assertEquals("127", apkInfo.getPackageInfo().getForcedPackageId());
|
||||
assertNotNull(apkInfo.getVersionInfo());
|
||||
assertEquals("1", apkInfo.getVersionInfo().getVersionCode());
|
||||
assertEquals("1.0", apkInfo.getVersionInfo().getVersionName());
|
||||
assertNotNull(apkInfo.getDoNotCompress());
|
||||
assertEquals(5, apkInfo.getDoNotCompress().size());
|
||||
assertEquals("assets/0byte_file.jpg", apkInfo.getDoNotCompress().get(0));
|
||||
assertEquals("arsc", apkInfo.getDoNotCompress().get(1));
|
||||
assertEquals("png", apkInfo.getDoNotCompress().get(2));
|
||||
assertEquals("mp3", apkInfo.getDoNotCompress().get(3));
|
||||
assertEquals("stored.file", apkInfo.getDoNotCompress().get(4));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUlist_with_indent() throws BrutException {
|
||||
ApkInfo apkInfo = ApkInfo.load(getClass().getResourceAsStream("/meta/list_with_indent.yml"));
|
||||
assertEquals("2.8.0", apkInfo.getVersion());
|
||||
assertEquals("basic.apk", apkInfo.getApkFileName());
|
||||
assertNotNull(apkInfo.getUsesFramework());
|
||||
assertEquals(1, apkInfo.getUsesFramework().getIds().size());
|
||||
assertEquals(1, (long) apkInfo.getUsesFramework().getIds().get(0));
|
||||
assertEquals("tag", apkInfo.getUsesFramework().getTag());
|
||||
assertNotNull(apkInfo.getPackageInfo());
|
||||
assertEquals("127", apkInfo.getPackageInfo().getForcedPackageId());
|
||||
assertEquals("com.test.basic", apkInfo.getPackageInfo().getRenameManifestPackage());
|
||||
assertNotNull(apkInfo.getSdkInfo());
|
||||
assertEquals("4", apkInfo.getSdkInfo().getMinSdkVersion());
|
||||
assertEquals("22", apkInfo.getSdkInfo().getTargetSdkVersion());
|
||||
assertEquals("30", apkInfo.getSdkInfo().getMaxSdkVersion());
|
||||
assertTrue(apkInfo.isSparseResources());
|
||||
assertNotNull(apkInfo.getVersionInfo());
|
||||
assertEquals("71", apkInfo.getVersionInfo().getVersionCode());
|
||||
assertEquals("1.0.70", apkInfo.getVersionInfo().getVersionName());
|
||||
assertNotNull(apkInfo.getDoNotCompress());
|
||||
assertEquals(2, apkInfo.getDoNotCompress().size());
|
||||
assertEquals("arsc", apkInfo.getDoNotCompress().get(0));
|
||||
assertEquals("png", apkInfo.getDoNotCompress().get(1));
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package brut.androlib.apk;
|
||||
package brut.androlib.meta;
|
||||
|
||||
import brut.androlib.BaseTest;
|
||||
import brut.common.BrutException;
|
||||
@@ -33,29 +33,28 @@ public class ApkInfoSerializationTest extends BaseTest {
|
||||
public TemporaryFolder folder = new TemporaryFolder();
|
||||
|
||||
private void check(ApkInfo apkInfo) {
|
||||
assertEquals("2.0.0", apkInfo.version);
|
||||
assertEquals("testapp.apk", apkInfo.apkFileName);
|
||||
assertNotNull(apkInfo.usesFramework);
|
||||
assertEquals(1, apkInfo.usesFramework.ids.size());
|
||||
assertEquals(1, (long) apkInfo.usesFramework.ids.get(0));
|
||||
assertNotNull(apkInfo.packageInfo);
|
||||
assertEquals("127", apkInfo.packageInfo.forcedPackageId);
|
||||
assertNotNull(apkInfo.versionInfo);
|
||||
assertEquals("1", apkInfo.versionInfo.versionCode);
|
||||
assertEquals("1.0", apkInfo.versionInfo.versionName);
|
||||
assertNotNull(apkInfo.doNotCompress);
|
||||
assertEquals(5, apkInfo.doNotCompress.size());
|
||||
assertEquals("assets/0byte_file.jpg", apkInfo.doNotCompress.get(0));
|
||||
assertEquals("arsc", apkInfo.doNotCompress.get(1));
|
||||
assertEquals("png", apkInfo.doNotCompress.get(2));
|
||||
assertEquals("mp3", apkInfo.doNotCompress.get(3));
|
||||
assertEquals("stored.file", apkInfo.doNotCompress.get(4));
|
||||
assertEquals("2.0.0", apkInfo.getVersion());
|
||||
assertEquals("testapp.apk", apkInfo.getApkFileName());
|
||||
assertNotNull(apkInfo.getUsesFramework());
|
||||
assertEquals(1, apkInfo.getUsesFramework().getIds().size());
|
||||
assertEquals(1, (long) apkInfo.getUsesFramework().getIds().get(0));
|
||||
assertNotNull(apkInfo.getPackageInfo());
|
||||
assertEquals("127", apkInfo.getPackageInfo().getForcedPackageId());
|
||||
assertNotNull(apkInfo.getVersionInfo());
|
||||
assertEquals("1", apkInfo.getVersionInfo().getVersionCode());
|
||||
assertEquals("1.0", apkInfo.getVersionInfo().getVersionName());
|
||||
assertNotNull(apkInfo.getDoNotCompress());
|
||||
assertEquals(5, apkInfo.getDoNotCompress().size());
|
||||
assertEquals("assets/0byte_file.jpg", apkInfo.getDoNotCompress().get(0));
|
||||
assertEquals("arsc", apkInfo.getDoNotCompress().get(1));
|
||||
assertEquals("png", apkInfo.getDoNotCompress().get(2));
|
||||
assertEquals("mp3", apkInfo.getDoNotCompress().get(3));
|
||||
assertEquals("stored.file", apkInfo.getDoNotCompress().get(4));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkApkInfoSerialization() throws BrutException, IOException {
|
||||
ApkInfo control = ApkInfo.load(
|
||||
getClass().getResourceAsStream("/apk/unknown_files.yml"));
|
||||
ApkInfo control = ApkInfo.load(getClass().getResourceAsStream("/meta/unknown_files.yml"));
|
||||
check(control);
|
||||
|
||||
File savedApkInfo = folder.newFile("saved.yml");
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Ryszard Wiśniewski <[email protected]>
|
||||
* Copyright (C) 2010 Connor Tumbleson <[email protected]>
|
||||
*
|
||||
* 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.BaseTest;
|
||||
import brut.common.BrutException;
|
||||
|
||||
import org.junit.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class ConsistentPropertyTest extends BaseTest {
|
||||
|
||||
@Test
|
||||
public void testAssertingAllKnownApkInfoProperties() throws BrutException {
|
||||
ApkInfo apkInfo = ApkInfo.load(getClass().getResourceAsStream("/meta/basic.yml"));
|
||||
assertEquals("2.8.0", apkInfo.getVersion());
|
||||
assertEquals("basic.apk", apkInfo.getApkFileName());
|
||||
assertEquals(1, apkInfo.getUsesFramework().getIds().size());
|
||||
assertEquals("tag", apkInfo.getUsesFramework().getTag());
|
||||
assertEquals("4", apkInfo.getSdkInfo().getMinSdkVersion());
|
||||
assertEquals("22", apkInfo.getSdkInfo().getTargetSdkVersion());
|
||||
assertEquals("30", apkInfo.getSdkInfo().getMaxSdkVersion());
|
||||
assertEquals("127", apkInfo.getPackageInfo().getForcedPackageId());
|
||||
assertEquals("com.test.basic", apkInfo.getPackageInfo().getRenameManifestPackage());
|
||||
assertEquals("71", apkInfo.getVersionInfo().getVersionCode());
|
||||
assertEquals("1.0.70", apkInfo.getVersionInfo().getVersionName());
|
||||
assertTrue(apkInfo.isSparseResources());
|
||||
assertEquals(2, apkInfo.getDoNotCompress().size());
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package brut.androlib.apk;
|
||||
package brut.androlib.meta;
|
||||
|
||||
import brut.androlib.BaseTest;
|
||||
import brut.common.BrutException;
|
||||
@@ -26,12 +26,11 @@ public class DoNotCompressHieroglyphTest extends BaseTest {
|
||||
|
||||
@Test
|
||||
public void testHieroglyph() throws BrutException {
|
||||
ApkInfo apkInfo = ApkInfo.load(
|
||||
getClass().getResourceAsStream("/apk/donotcompress_with_hieroglyph.yml"));
|
||||
assertEquals("2.0.0", apkInfo.version);
|
||||
assertEquals("testapp.apk", apkInfo.apkFileName);
|
||||
assertEquals(2, apkInfo.doNotCompress.size());
|
||||
assertEquals("assets/AllAssetBundles/Andriod/tx_1001_冰原1", apkInfo.doNotCompress.get(0));
|
||||
assertEquals("assets/AllAssetBundles/Andriod/tx_1001_冰原1.manifest", apkInfo.doNotCompress.get(1));
|
||||
ApkInfo apkInfo = ApkInfo.load(getClass().getResourceAsStream("/meta/donotcompress_with_hieroglyph.yml"));
|
||||
assertEquals("2.0.0", apkInfo.getVersion());
|
||||
assertEquals("testapp.apk", apkInfo.getApkFileName());
|
||||
assertEquals(2, apkInfo.getDoNotCompress().size());
|
||||
assertEquals("assets/AllAssetBundles/Andriod/tx_1001_冰原1", apkInfo.getDoNotCompress().get(0));
|
||||
assertEquals("assets/AllAssetBundles/Andriod/tx_1001_冰原1.manifest", apkInfo.getDoNotCompress().get(1));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (C) 2010 Ryszard Wiśniewski <[email protected]>
|
||||
* Copyright (C) 2010 Connor Tumbleson <[email protected]>
|
||||
*
|
||||
* 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.BaseTest;
|
||||
|
||||
import org.junit.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class InvalidSdkBoundingTest extends BaseTest {
|
||||
|
||||
@Test
|
||||
public void checkIfInvalidValuesPass() {
|
||||
SdkInfo sdkInfo = new SdkInfo();
|
||||
sdkInfo.setMinSdkVersion("15");
|
||||
sdkInfo.setTargetSdkVersion("25");
|
||||
sdkInfo.setMaxSdkVersion("19");
|
||||
|
||||
assertEquals("19", sdkInfo.getTargetSdkVersionBounded());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkIfMissingMinPasses() {
|
||||
SdkInfo sdkInfo = new SdkInfo();
|
||||
sdkInfo.setTargetSdkVersion("25");
|
||||
sdkInfo.setMaxSdkVersion("19");
|
||||
|
||||
assertEquals("19", sdkInfo.getTargetSdkVersionBounded());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkIfMissingMaxPasses() {
|
||||
SdkInfo sdkInfo = new SdkInfo();
|
||||
sdkInfo.setMinSdkVersion("15");
|
||||
sdkInfo.setTargetSdkVersion("25");
|
||||
|
||||
assertEquals("25", sdkInfo.getTargetSdkVersionBounded());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkIfMissingBothPasses() {
|
||||
SdkInfo sdkInfo = new SdkInfo();
|
||||
sdkInfo.setTargetSdkVersion("25");
|
||||
|
||||
assertEquals("25", sdkInfo.getTargetSdkVersionBounded());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkForSdkCodenameOTag() {
|
||||
SdkInfo sdkInfo = new SdkInfo();
|
||||
sdkInfo.setTargetSdkVersion("O");
|
||||
|
||||
assertEquals("26", sdkInfo.getTargetSdkVersionBounded());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkForSdkCodenameSTag() {
|
||||
SdkInfo sdkInfo = new SdkInfo();
|
||||
sdkInfo.setTargetSdkVersion("S");
|
||||
|
||||
assertEquals("31", sdkInfo.getTargetSdkVersionBounded());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkForSdkDevelopmentInsaneTestValue() {
|
||||
SdkInfo sdkInfo = new SdkInfo();
|
||||
sdkInfo.setTargetSdkVersion("SDK_CUR_DEVELOPMENT");
|
||||
|
||||
assertEquals("10000", sdkInfo.getTargetSdkVersionBounded());
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package brut.androlib.apk;
|
||||
package brut.androlib.meta;
|
||||
|
||||
import brut.androlib.BaseTest;
|
||||
import brut.common.BrutException;
|
||||
@@ -26,8 +26,7 @@ public class MaliciousYamlTest extends BaseTest {
|
||||
|
||||
@Test
|
||||
public void testMaliciousYaml() throws BrutException {
|
||||
ApkInfo apkInfo = ApkInfo.load(
|
||||
getClass().getResourceAsStream("/apk/cve20220476.yml"));
|
||||
assertEquals("2.6.1-ddc4bb-SNAPSHOT", apkInfo.version);
|
||||
ApkInfo apkInfo = ApkInfo.load(getClass().getResourceAsStream("/meta/cve20220476.yml"));
|
||||
assertEquals("2.6.1-ddc4bb-SNAPSHOT", apkInfo.getVersion());
|
||||
}
|
||||
}
|
||||
@@ -14,9 +14,10 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package brut.androlib.apk;
|
||||
package brut.androlib.meta;
|
||||
|
||||
import brut.androlib.BaseTest;
|
||||
import brut.yaml.YamlLine;
|
||||
|
||||
import org.junit.*;
|
||||
import static org.junit.Assert.*;
|
||||
@@ -33,7 +33,7 @@ import org.junit.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class MissingDiv9PatchTest extends BaseTest {
|
||||
private static final int NP_COLOR = 0xff000000;
|
||||
private static final int NP_COLOR = 0xFF000000;
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="com.ibotpeaches.issue1235" xmlns:android="http://schemas.android.com/apk/res/android" platformBuildVersionCode="20" platformBuildVersionName="4.4W.2-1537038">
|
||||
<application android:debuggable="false"/>
|
||||
</manifest>
|
||||
@@ -1,10 +0,0 @@
|
||||
version: 2.0.0
|
||||
apkFileName: issue1235.apk
|
||||
usesFramework:
|
||||
ids:
|
||||
- 1
|
||||
packageInfo:
|
||||
forcedPackageId: '127'
|
||||
versionInfo:
|
||||
versionCode: '1'
|
||||
versionName: '1.0'
|
||||
@@ -8,8 +8,8 @@ packageInfo:
|
||||
renameManifestPackage: 'com.test.basic'
|
||||
sdkInfo:
|
||||
minSdkVersion: '4'
|
||||
maxSdkVersion: '30'
|
||||
targetSdkVersion: '22'
|
||||
maxSdkVersion: '30'
|
||||
sparseResources: true
|
||||
usesFramework:
|
||||
ids:
|
||||
@@ -8,8 +8,8 @@ packageInfo:
|
||||
renameManifestPackage: 'com.test.basic'
|
||||
sdkInfo:
|
||||
minSdkVersion: '4'
|
||||
maxSdkVersion: '30'
|
||||
targetSdkVersion: '22'
|
||||
maxSdkVersion: '30'
|
||||
sparseResources: true
|
||||
usesFramework:
|
||||
ids:
|
||||
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="brut.apktool.testapp" platformBuildVersionCode="23" platformBuildVersionName="6.0-2438415">
|
||||
<application/>
|
||||
</manifest>
|
||||
@@ -0,0 +1,8 @@
|
||||
version: 2.0.0
|
||||
apkFileName: testapp.apk
|
||||
usesFramework:
|
||||
ids:
|
||||
- 1
|
||||
versionInfo:
|
||||
versionCode: '1'
|
||||
versionName: '1.0'
|
||||