mirror of
https://github.com/revanced/Apktool.git
synced 2025-02-21 16:01:10 +01:00
refactor: ExtDataInput rework, source layout and formatting (#3738)
* refactor: ExtDataInput rework, source layout and formatting Refactor ExtDataInput classes: ExtDataInput is now the extended interface, ExtDataInputStream is an easy-to-use FilterInputStream implementing ExtDataInput with static creator methods for big-endian and little-endian wrappers. Refactor AaptManager class: unify aapt-related verifications to one class. Replace Apache Commons' deprecated CountingInputStream with Google Guava's equivalent with the same name. Apache's BoundedInputStream is an overkill for our use case and its constructors are deprecated as well. Normalize source layout to have a common and somewhat more standard order: Static fields first, instance fields after, methods last. Fix some formatting, like empty spaces or extra spaces and exception messages. Renamed ResXmlPatcher to ResXmlUtils, as it has more purposes than just patching. Renamed DirUtil to DirUtils, to match other utility classes naming convention. Moved "properties/apktool.properties" to jar's root, to match smali/baksmali. Moved Android Framework to "prebuilt", as it is just a prebuilt, looks out of place among .class files. @SuppressWarnings removed from Duo as there are quite a few unsafe assignments of raw Duo[] instances to parameterized Duo<> variables in the project, this is just Java being the primitive boilerplate it is, no point in fighting it. No end-user changes. Tested against a full ROM decompile/recompile, no issues found. * small tweak * last refinement * missed a stream
This commit is contained in:
parent
858c07143d
commit
542b66cbd0
@ -36,6 +36,19 @@ import java.util.logging.*;
|
|||||||
* Main entry point of the apktool.
|
* Main entry point of the apktool.
|
||||||
*/
|
*/
|
||||||
public class Main {
|
public class Main {
|
||||||
|
private enum Verbosity { NORMAL, VERBOSE, QUIET }
|
||||||
|
|
||||||
|
private static final Options normalOptions = new Options();
|
||||||
|
private static final Options decodeOptions = new Options();
|
||||||
|
private static final Options buildOptions = new Options();
|
||||||
|
private static final Options frameOptions = new Options();
|
||||||
|
private static final Options allOptions = new Options();
|
||||||
|
private static final Options emptyOptions = new Options();
|
||||||
|
private static final Options emptyFrameworkOptions = new Options();
|
||||||
|
private static final Options listFrameworkOptions = new Options();
|
||||||
|
|
||||||
|
private static boolean advanceMode = false;
|
||||||
|
|
||||||
public static void main(String[] args) throws BrutException {
|
public static void main(String[] args) throws BrutException {
|
||||||
|
|
||||||
// headless
|
// headless
|
||||||
@ -239,7 +252,7 @@ public class Main {
|
|||||||
System.exit(1);
|
System.exit(1);
|
||||||
} catch (CantFindFrameworkResException ex) {
|
} catch (CantFindFrameworkResException ex) {
|
||||||
System.err
|
System.err
|
||||||
.println("Can't find framework resources for package of id: "
|
.println("Could not find framework resources for package of id: "
|
||||||
+ ex.getPkgId()
|
+ ex.getPkgId()
|
||||||
+ ". You must install proper "
|
+ ". You must install proper "
|
||||||
+ "framework files, see project website for more info.");
|
+ "framework files, see project website for more info.");
|
||||||
@ -271,15 +284,8 @@ public class Main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String aaptPath = cli.getOptionValue("a");
|
config.aaptBinary = new File(cli.getOptionValue("a"));
|
||||||
int aaptVersion = AaptManager.getAaptVersion(aaptPath);
|
config.aaptVersion = AaptManager.getAaptVersion(config.aaptBinary);
|
||||||
if (aaptVersion < AaptManager.AAPT_VERSION_MIN && aaptVersion > AaptManager.AAPT_VERSION_MAX) {
|
|
||||||
System.err.println("AAPT version " + aaptVersion + " is not supported");
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
config.aaptPath = aaptPath;
|
|
||||||
config.aaptVersion = aaptVersion;
|
|
||||||
} catch (BrutException ex) {
|
} catch (BrutException ex) {
|
||||||
System.err.println(ex.getMessage());
|
System.err.println(ex.getMessage());
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
@ -310,7 +316,7 @@ public class Main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (config.netSecConf && config.aaptVersion == 1) {
|
if (config.netSecConf && config.aaptVersion == 1) {
|
||||||
System.err.println("-n / --net-sec-conf is not supported with legacy AAPT.");
|
System.err.println("-n / --net-sec-conf is not supported with legacy aapt.");
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -724,31 +730,4 @@ public class Main {
|
|||||||
private static void setAdvanceMode() {
|
private static void setAdvanceMode() {
|
||||||
Main.advanceMode = true;
|
Main.advanceMode = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum Verbosity {
|
|
||||||
NORMAL, VERBOSE, QUIET
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean advanceMode = false;
|
|
||||||
|
|
||||||
private final static Options normalOptions;
|
|
||||||
private final static Options decodeOptions;
|
|
||||||
private final static Options buildOptions;
|
|
||||||
private final static Options frameOptions;
|
|
||||||
private final static Options allOptions;
|
|
||||||
private final static Options emptyOptions;
|
|
||||||
private final static Options emptyFrameworkOptions;
|
|
||||||
private final static Options listFrameworkOptions;
|
|
||||||
|
|
||||||
static {
|
|
||||||
//normal and advance usage output
|
|
||||||
normalOptions = new Options();
|
|
||||||
buildOptions = new Options();
|
|
||||||
decodeOptions = new Options();
|
|
||||||
frameOptions = new Options();
|
|
||||||
allOptions = new Options();
|
|
||||||
emptyOptions = new Options();
|
|
||||||
emptyFrameworkOptions = new Options();
|
|
||||||
listFrameworkOptions = new Options();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,8 @@ val apktoolVersion: String by rootProject.extra
|
|||||||
|
|
||||||
tasks {
|
tasks {
|
||||||
processResources {
|
processResources {
|
||||||
from("src/main/resources/properties") {
|
from("src/main/resources") {
|
||||||
include("**/*.properties")
|
include("apktool.properties")
|
||||||
into("properties")
|
|
||||||
expand("version" to apktoolVersion, "gitrev" to gitRevision)
|
expand("version" to apktoolVersion, "gitrev" to gitRevision)
|
||||||
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ import org.xmlpull.v1.XmlPullParser;
|
|||||||
public interface XmlResourceParser extends XmlPullParser, AttributeSet {
|
public interface XmlResourceParser extends XmlPullParser, AttributeSet {
|
||||||
/**
|
/**
|
||||||
* Close this interface to the resource. Calls on the interface are no
|
* Close this interface to the resource. Calls on the interface are no
|
||||||
* longer value after this call.
|
* longer valid after this call.
|
||||||
*/
|
*/
|
||||||
void close();
|
void close();
|
||||||
}
|
}
|
||||||
|
@ -216,7 +216,7 @@ public class TypedValue {
|
|||||||
public int type;
|
public int type;
|
||||||
|
|
||||||
private static final float MANTISSA_MULT = 1.0f / (1 << TypedValue.COMPLEX_MANTISSA_SHIFT);
|
private static final float MANTISSA_MULT = 1.0f / (1 << TypedValue.COMPLEX_MANTISSA_SHIFT);
|
||||||
private static final float[] RADIX_MULTS = new float[] {
|
private static final float[] RADIX_MULTS = {
|
||||||
MANTISSA_MULT, 1.0f / (1 << 7) * MANTISSA_MULT,
|
MANTISSA_MULT, 1.0f / (1 << 7) * MANTISSA_MULT,
|
||||||
1.0f / (1 << 15) * MANTISSA_MULT, 1.0f / (1 << 23) * MANTISSA_MULT };
|
1.0f / (1 << 15) * MANTISSA_MULT, 1.0f / (1 << 23) * MANTISSA_MULT };
|
||||||
|
|
||||||
@ -237,9 +237,10 @@ public class TypedValue {
|
|||||||
& TypedValue.COMPLEX_RADIX_MASK];
|
& TypedValue.COMPLEX_RADIX_MASK];
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String[] DIMENSION_UNIT_STRS = new String[] { "px",
|
private static final String[] DIMENSION_UNIT_STRS = {
|
||||||
"dip", "sp", "pt", "in", "mm" };
|
"px", "dip", "sp", "pt", "in", "mm"
|
||||||
private static final String[] FRACTION_UNIT_STRS = new String[] { "%", "%p" };
|
};
|
||||||
|
private static final String[] FRACTION_UNIT_STRS = { "%", "%p" };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform type conversion as per coerceToString on an explicitly
|
* Perform type conversion as per coerceToString on an explicitly
|
||||||
|
@ -27,44 +27,40 @@ import java.util.*;
|
|||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
public class AaptInvoker {
|
public class AaptInvoker {
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(AaptInvoker.class.getName());
|
||||||
|
|
||||||
private final Config mConfig;
|
private final Config mConfig;
|
||||||
private final ApkInfo mApkInfo;
|
private final ApkInfo mApkInfo;
|
||||||
|
|
||||||
private final static Logger LOGGER = Logger.getLogger(AaptInvoker.class.getName());
|
|
||||||
|
|
||||||
public AaptInvoker(Config config, ApkInfo apkInfo) {
|
public AaptInvoker(Config config, ApkInfo apkInfo) {
|
||||||
mConfig = config;
|
mConfig = config;
|
||||||
mApkInfo = apkInfo;
|
mApkInfo = apkInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private File getAaptBinaryFile() throws AndrolibException {
|
|
||||||
try {
|
|
||||||
switch (mConfig.aaptVersion) {
|
|
||||||
case 2:
|
|
||||||
return AaptManager.getAapt2();
|
|
||||||
default:
|
|
||||||
return AaptManager.getAapt1();
|
|
||||||
}
|
|
||||||
} catch (BrutException ex) {
|
|
||||||
throw new AndrolibException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void invoke(File apkFile, File manifest, File resDir, File rawDir, File assetDir, File[] include)
|
public void invoke(File apkFile, File manifest, File resDir, File rawDir, File assetDir, File[] include)
|
||||||
throws AndrolibException {
|
throws AndrolibException {
|
||||||
|
File aaptBinary = mConfig.aaptBinary;
|
||||||
|
|
||||||
String aaptPath = mConfig.aaptPath;
|
|
||||||
boolean customAapt = !aaptPath.isEmpty();
|
|
||||||
List<String> cmd = new ArrayList<>();
|
List<String> cmd = new ArrayList<>();
|
||||||
|
String aaptPath;
|
||||||
|
boolean customAapt;
|
||||||
|
|
||||||
try {
|
if (mConfig.aaptBinary != null) {
|
||||||
String aaptCommand = AaptManager.getAaptExecutionCommand(aaptPath, getAaptBinaryFile());
|
aaptPath = mConfig.aaptBinary.getPath();
|
||||||
cmd.add(aaptCommand);
|
customAapt = true;
|
||||||
} catch (BrutException ex) {
|
} else {
|
||||||
LOGGER.warning("aapt: " + ex.getMessage() + " (defaulting to $PATH binary)");
|
try {
|
||||||
cmd.add(AaptManager.getAaptBinaryName(mConfig.aaptVersion));
|
aaptPath = AaptManager.getAaptBinary(mConfig.aaptVersion).getPath();
|
||||||
|
customAapt = false;
|
||||||
|
} catch (BrutException ex) {
|
||||||
|
aaptPath = AaptManager.getAaptName(mConfig.aaptVersion);
|
||||||
|
customAapt = true;
|
||||||
|
LOGGER.warning(aaptPath + ": " + ex.getMessage() + " (defaulting to $PATH binary)");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmd.add(aaptPath);
|
||||||
|
|
||||||
switch (mConfig.aaptVersion) {
|
switch (mConfig.aaptVersion) {
|
||||||
case 2:
|
case 2:
|
||||||
invokeAapt2(apkFile, manifest, resDir, rawDir, assetDir, include, cmd, customAapt);
|
invokeAapt2(apkFile, manifest, resDir, rawDir, assetDir, include, cmd, customAapt);
|
||||||
@ -77,7 +73,6 @@ public class AaptInvoker {
|
|||||||
|
|
||||||
private void invokeAapt2(File apkFile, File manifest, File resDir, File rawDir, File assetDir, File[] include,
|
private void invokeAapt2(File apkFile, File manifest, File resDir, File rawDir, File assetDir, File[] include,
|
||||||
List<String> cmd, boolean customAapt) throws AndrolibException {
|
List<String> cmd, boolean customAapt) throws AndrolibException {
|
||||||
|
|
||||||
List<String> compileCommand = new ArrayList<>(cmd);
|
List<String> compileCommand = new ArrayList<>(cmd);
|
||||||
File resourcesZip = null;
|
File resourcesZip = null;
|
||||||
|
|
||||||
@ -87,7 +82,6 @@ public class AaptInvoker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (resDir != null && !resourcesZip.exists()) {
|
if (resDir != null && !resourcesZip.exists()) {
|
||||||
|
|
||||||
// Compile the files into flat arsc files
|
// Compile the files into flat arsc files
|
||||||
cmd.add("compile");
|
cmd.add("compile");
|
||||||
|
|
||||||
@ -241,7 +235,6 @@ public class AaptInvoker {
|
|||||||
|
|
||||||
private void invokeAapt1(File apkFile, File manifest, File resDir, File rawDir, File assetDir, File[] include,
|
private void invokeAapt1(File apkFile, File manifest, File resDir, File rawDir, File assetDir, File[] include,
|
||||||
List<String> cmd, boolean customAapt) throws AndrolibException {
|
List<String> cmd, boolean customAapt) throws AndrolibException {
|
||||||
|
|
||||||
cmd.add("p");
|
cmd.add("p");
|
||||||
|
|
||||||
if (mConfig.verbose) { // output aapt verbose
|
if (mConfig.verbose) { // output aapt verbose
|
||||||
|
@ -21,7 +21,7 @@ import brut.androlib.apk.ApkInfo;
|
|||||||
import brut.androlib.apk.UsesFramework;
|
import brut.androlib.apk.UsesFramework;
|
||||||
import brut.androlib.res.Framework;
|
import brut.androlib.res.Framework;
|
||||||
import brut.androlib.res.data.ResConfigFlags;
|
import brut.androlib.res.data.ResConfigFlags;
|
||||||
import brut.androlib.res.xml.ResXmlPatcher;
|
import brut.androlib.res.xml.ResXmlUtils;
|
||||||
import brut.androlib.src.SmaliBuilder;
|
import brut.androlib.src.SmaliBuilder;
|
||||||
import brut.common.BrutException;
|
import brut.common.BrutException;
|
||||||
import brut.common.InvalidUnknownFileException;
|
import brut.common.InvalidUnknownFileException;
|
||||||
@ -47,14 +47,15 @@ import java.util.logging.Logger;
|
|||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
public class ApkBuilder {
|
public class ApkBuilder {
|
||||||
private final static Logger LOGGER = Logger.getLogger(ApkBuilder.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(ApkBuilder.class.getName());
|
||||||
|
|
||||||
private final ExtFile mApkDir;
|
private final ExtFile mApkDir;
|
||||||
private final Config mConfig;
|
private final Config mConfig;
|
||||||
|
private final AtomicReference<AndrolibException> mBuildError;
|
||||||
|
|
||||||
private ApkInfo mApkInfo;
|
private ApkInfo mApkInfo;
|
||||||
private int mMinSdkVersion = 0;
|
private int mMinSdkVersion;
|
||||||
private BackgroundWorker mWorker;
|
private BackgroundWorker mWorker;
|
||||||
private final AtomicReference<AndrolibException> mBuildError = new AtomicReference<>(null);
|
|
||||||
|
|
||||||
public ApkBuilder(ExtFile apkDir) {
|
public ApkBuilder(ExtFile apkDir) {
|
||||||
this(apkDir, Config.getDefaultConfig());
|
this(apkDir, Config.getDefaultConfig());
|
||||||
@ -63,6 +64,7 @@ public class ApkBuilder {
|
|||||||
public ApkBuilder(ExtFile apkDir, Config config) {
|
public ApkBuilder(ExtFile apkDir, Config config) {
|
||||||
mApkDir = apkDir;
|
mApkDir = apkDir;
|
||||||
mConfig = config;
|
mConfig = config;
|
||||||
|
mBuildError = new AtomicReference<>(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void build(File outApk) throws AndrolibException {
|
public void build(File outApk) throws AndrolibException {
|
||||||
@ -122,19 +124,19 @@ public class ApkBuilder {
|
|||||||
|
|
||||||
LOGGER.info("Building apk file...");
|
LOGGER.info("Building apk file...");
|
||||||
|
|
||||||
try (ZipOutputStream outStream = new ZipOutputStream(Files.newOutputStream(outApk.toPath()))) {
|
try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(outApk.toPath()))) {
|
||||||
// zip aapt output files
|
// zip aapt output files
|
||||||
try {
|
try {
|
||||||
ZipUtils.zipDir(outDir, outStream, mApkInfo.doNotCompress);
|
ZipUtils.zipDir(outDir, out, mApkInfo.doNotCompress);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
throw new AndrolibException(ex);
|
throw new AndrolibException(ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// zip remaining standard files
|
// zip remaining standard files
|
||||||
importRawFiles(outStream);
|
importRawFiles(out);
|
||||||
|
|
||||||
// zip unknown files
|
// zip unknown files
|
||||||
importUnknownFiles(outStream);
|
importUnknownFiles(out);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
throw new AndrolibException(ex);
|
throw new AndrolibException(ex);
|
||||||
}
|
}
|
||||||
@ -242,10 +244,10 @@ public class ApkBuilder {
|
|||||||
//noinspection ResultOfMethodCallIgnored
|
//noinspection ResultOfMethodCallIgnored
|
||||||
dex.delete();
|
dex.delete();
|
||||||
|
|
||||||
int apiLevel = mConfig.apiLevel > 0 ? mConfig.apiLevel : mMinSdkVersion;
|
|
||||||
|
|
||||||
LOGGER.info("Smaling " + dirName + " folder into " + fileName + "...");
|
LOGGER.info("Smaling " + dirName + " folder into " + fileName + "...");
|
||||||
SmaliBuilder.build(smaliDir, dex, apiLevel);
|
int apiLevel = mConfig.apiLevel > 0 ? mConfig.apiLevel : mMinSdkVersion;
|
||||||
|
SmaliBuilder builder = new SmaliBuilder(smaliDir, apiLevel);
|
||||||
|
builder.build(dex);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void backupManifestFile(File manifest, File manifestOrig) throws AndrolibException {
|
private void backupManifestFile(File manifest, File manifestOrig) throws AndrolibException {
|
||||||
@ -265,7 +267,7 @@ public class ApkBuilder {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
FileUtils.copyFile(manifest, manifestOrig);
|
FileUtils.copyFile(manifest, manifestOrig);
|
||||||
ResXmlPatcher.fixingPublicAttrsInProviderAttributes(manifest);
|
ResXmlUtils.fixingPublicAttrsInProviderAttributes(manifest);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
throw new AndrolibException(ex);
|
throw new AndrolibException(ex);
|
||||||
}
|
}
|
||||||
@ -327,10 +329,10 @@ public class ApkBuilder {
|
|||||||
try {
|
try {
|
||||||
if (mConfig.debugMode) {
|
if (mConfig.debugMode) {
|
||||||
if (mConfig.aaptVersion == 2) {
|
if (mConfig.aaptVersion == 2) {
|
||||||
LOGGER.info("Using aapt2 - setting 'debuggable' attribute to 'true' in AndroidManifest.xml");
|
LOGGER.info("Setting 'debuggable' attribute to 'true' in AndroidManifest.xml");
|
||||||
ResXmlPatcher.setApplicationDebugTagTrue(manifest);
|
ResXmlUtils.setApplicationDebugTagTrue(manifest);
|
||||||
} else {
|
} else {
|
||||||
ResXmlPatcher.removeApplicationDebugTag(manifest);
|
ResXmlUtils.removeApplicationDebugTag(manifest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -343,8 +345,8 @@ public class ApkBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
File netSecConfOrig = new File(mApkDir, "res/xml/network_security_config.xml");
|
File netSecConfOrig = new File(mApkDir, "res/xml/network_security_config.xml");
|
||||||
ResXmlPatcher.modNetworkSecurityConfig(netSecConfOrig);
|
ResXmlUtils.modNetworkSecurityConfig(netSecConfOrig);
|
||||||
ResXmlPatcher.setNetworkSecurityConfig(manifest);
|
ResXmlUtils.setNetworkSecurityConfig(manifest);
|
||||||
LOGGER.info("Added permissive network security config in manifest");
|
LOGGER.info("Added permissive network security config in manifest");
|
||||||
}
|
}
|
||||||
} catch (IOException | ParserConfigurationException | TransformerException | SAXException ex) {
|
} catch (IOException | ParserConfigurationException | TransformerException | SAXException ex) {
|
||||||
@ -366,7 +368,7 @@ public class ApkBuilder {
|
|||||||
ninePatch = null;
|
ninePatch = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGGER.info("Building resources with " + AaptManager.getAaptBinaryName(mConfig.aaptVersion) + "...");
|
LOGGER.info("Building resources with " + AaptManager.getAaptName(mConfig.aaptVersion) + "...");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
AaptInvoker invoker = new AaptInvoker(mConfig, mApkInfo);
|
AaptInvoker invoker = new AaptInvoker(mConfig, mApkInfo);
|
||||||
@ -406,7 +408,7 @@ public class ApkBuilder {
|
|||||||
ninePatch = null;
|
ninePatch = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGGER.info("Building AndroidManifest.xml with " + AaptManager.getAaptBinaryName(mConfig.aaptVersion) + "...");
|
LOGGER.info("Building AndroidManifest.xml with " + AaptManager.getAaptName(mConfig.aaptVersion) + "...");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
AaptInvoker invoker = new AaptInvoker(mConfig, mApkInfo);
|
AaptInvoker invoker = new AaptInvoker(mConfig, mApkInfo);
|
||||||
@ -460,7 +462,7 @@ public class ApkBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void importRawFiles(ZipOutputStream outStream) throws AndrolibException {
|
private void importRawFiles(ZipOutputStream out) throws AndrolibException {
|
||||||
for (String dirName : ApkInfo.RAW_DIRNAMES) {
|
for (String dirName : ApkInfo.RAW_DIRNAMES) {
|
||||||
File rawDir = new File(mApkDir, dirName);
|
File rawDir = new File(mApkDir, dirName);
|
||||||
if (!rawDir.isDirectory()) {
|
if (!rawDir.isDirectory()) {
|
||||||
@ -469,14 +471,14 @@ public class ApkBuilder {
|
|||||||
|
|
||||||
LOGGER.info("Importing " + dirName + "...");
|
LOGGER.info("Importing " + dirName + "...");
|
||||||
try {
|
try {
|
||||||
ZipUtils.zipDir(mApkDir, dirName, outStream, mApkInfo.doNotCompress);
|
ZipUtils.zipDir(mApkDir, dirName, out, mApkInfo.doNotCompress);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
throw new AndrolibException(ex);
|
throw new AndrolibException(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void importUnknownFiles(ZipOutputStream outStream) throws AndrolibException {
|
private void importUnknownFiles(ZipOutputStream out) throws AndrolibException {
|
||||||
File unknownDir = new File(mApkDir, "unknown");
|
File unknownDir = new File(mApkDir, "unknown");
|
||||||
if (!unknownDir.isDirectory()) {
|
if (!unknownDir.isDirectory()) {
|
||||||
return;
|
return;
|
||||||
@ -484,7 +486,7 @@ public class ApkBuilder {
|
|||||||
|
|
||||||
LOGGER.info("Importing unknown files...");
|
LOGGER.info("Importing unknown files...");
|
||||||
try {
|
try {
|
||||||
ZipUtils.zipDir(unknownDir, outStream, mApkInfo.doNotCompress);
|
ZipUtils.zipDir(unknownDir, out, mApkInfo.doNotCompress);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
throw new AndrolibException(ex);
|
throw new AndrolibException(ex);
|
||||||
}
|
}
|
||||||
|
@ -37,20 +37,21 @@ import java.util.logging.Logger;
|
|||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class ApkDecoder {
|
public class ApkDecoder {
|
||||||
private final static Logger LOGGER = Logger.getLogger(ApkDecoder.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(ApkDecoder.class.getName());
|
||||||
|
|
||||||
// extensions of files that are often packed uncompressed
|
// extensions of files that are often packed uncompressed
|
||||||
private final static Pattern NO_COMPRESS_EXT_PATTERN = Pattern.compile(
|
private static final Pattern NO_COMPRESS_EXT_PATTERN = Pattern.compile(
|
||||||
"dex|arsc|so|jpg|jpeg|png|gif|wav|mp2|mp3|ogg|aac|mpg|mpeg|mid|midi|smf|jet|" +
|
"dex|arsc|so|jpg|jpeg|png|gif|wav|mp2|mp3|ogg|aac|mpg|mpeg|mid|midi|smf|jet|" +
|
||||||
"rtttl|imy|xmf|mp4|m4a|m4v|3gp|3gpp|3g2|3gpp2|amr|awb|wma|wmv|webm|webp|mkv");
|
"rtttl|imy|xmf|mp4|m4a|m4v|3gp|3gpp|3g2|3gpp2|amr|awb|wma|wmv|webm|webp|mkv");
|
||||||
|
|
||||||
private final ExtFile mApkFile;
|
private final ExtFile mApkFile;
|
||||||
private final Config mConfig;
|
private final Config mConfig;
|
||||||
|
private final AtomicReference<AndrolibException> mBuildError;
|
||||||
|
|
||||||
private ApkInfo mApkInfo;
|
private ApkInfo mApkInfo;
|
||||||
private ResourcesDecoder mResDecoder;
|
private ResourcesDecoder mResDecoder;
|
||||||
private volatile int mMinSdkVersion = 0;
|
private volatile int mMinSdkVersion;
|
||||||
private BackgroundWorker mWorker;
|
private BackgroundWorker mWorker;
|
||||||
private final AtomicReference<AndrolibException> mBuildError = new AtomicReference<>(null);
|
|
||||||
|
|
||||||
public ApkDecoder(ExtFile apkFile) {
|
public ApkDecoder(ExtFile apkFile) {
|
||||||
this(apkFile, Config.getDefaultConfig());
|
this(apkFile, Config.getDefaultConfig());
|
||||||
@ -59,6 +60,7 @@ public class ApkDecoder {
|
|||||||
public ApkDecoder(ExtFile apkFile, Config config) {
|
public ApkDecoder(ExtFile apkFile, Config config) {
|
||||||
mApkFile = apkFile;
|
mApkFile = apkFile;
|
||||||
mConfig = config;
|
mConfig = config;
|
||||||
|
mBuildError = new AtomicReference<>(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ApkInfo decode(File outDir) throws AndrolibException {
|
public ApkInfo decode(File outDir) throws AndrolibException {
|
||||||
@ -199,8 +201,9 @@ public class ApkDecoder {
|
|||||||
smaliDir.mkdirs();
|
smaliDir.mkdirs();
|
||||||
|
|
||||||
LOGGER.info("Baksmaling " + fileName + "...");
|
LOGGER.info("Baksmaling " + fileName + "...");
|
||||||
DexFile dexFile = SmaliDecoder.decode(mApkFile, smaliDir, fileName,
|
SmaliDecoder decoder = new SmaliDecoder(mApkFile, fileName,
|
||||||
mConfig.baksmaliDebugMode, mConfig.apiLevel);
|
mConfig.baksmaliDebugMode, mConfig.apiLevel);
|
||||||
|
DexFile dexFile = decoder.decode(smaliDir);
|
||||||
|
|
||||||
// record minSdkVersion for jars
|
// record minSdkVersion for jars
|
||||||
int minSdkVersion = dexFile.getOpcodes().api;
|
int minSdkVersion = dexFile.getOpcodes().api;
|
||||||
|
@ -42,20 +42,20 @@ public class ApktoolProperties {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void loadProps() {
|
private static void loadProps() {
|
||||||
InputStream in = ApktoolProperties.class.getResourceAsStream("/properties/apktool.properties");
|
InputStream in = ApktoolProperties.class.getResourceAsStream("/apktool.properties");
|
||||||
sProps = new Properties();
|
sProps = new Properties();
|
||||||
try {
|
try {
|
||||||
sProps.load(in);
|
sProps.load(in);
|
||||||
in.close();
|
in.close();
|
||||||
} catch (NullPointerException | IOException ex) {
|
} catch (NullPointerException | IOException ex) {
|
||||||
LOGGER.warning("Can't load properties.");
|
LOGGER.warning("Could not load properties.");
|
||||||
}
|
}
|
||||||
|
|
||||||
InputStream templateStream = null;
|
InputStream templateStream = null;
|
||||||
try {
|
try {
|
||||||
templateStream = com.android.tools.smali.baksmali.Main.class.getClassLoader().getResourceAsStream("baksmali.properties");
|
templateStream = com.android.tools.smali.baksmali.Main.class.getResourceAsStream("/baksmali.properties");
|
||||||
} catch(NoClassDefFoundError ex) {
|
} catch(NoClassDefFoundError ex) {
|
||||||
LOGGER.warning("Can't load baksmali properties.");
|
LOGGER.warning("Could not load baksmali properties.");
|
||||||
}
|
}
|
||||||
Properties properties = new Properties();
|
Properties properties = new Properties();
|
||||||
String version = "(unknown)";
|
String version = "(unknown)";
|
||||||
@ -71,9 +71,9 @@ public class ApktoolProperties {
|
|||||||
|
|
||||||
templateStream = null;
|
templateStream = null;
|
||||||
try {
|
try {
|
||||||
templateStream = com.android.tools.smali.smali.Main.class.getClassLoader().getResourceAsStream("smali.properties");
|
templateStream = com.android.tools.smali.smali.Main.class.getResourceAsStream("/smali.properties");
|
||||||
} catch(NoClassDefFoundError ex) {
|
} catch(NoClassDefFoundError ex) {
|
||||||
LOGGER.warning("Can't load smali properties.");
|
LOGGER.warning("Could not load smali properties.");
|
||||||
}
|
}
|
||||||
properties = new Properties();
|
properties = new Properties();
|
||||||
version = "(unknown)";
|
version = "(unknown)";
|
||||||
|
@ -17,15 +17,18 @@
|
|||||||
package brut.androlib;
|
package brut.androlib;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
public class BackgroundWorker {
|
public class BackgroundWorker {
|
||||||
private final ArrayList<Future<?>> mWorkerFutures = new ArrayList<>();
|
|
||||||
private final ExecutorService mExecutor;
|
private final ExecutorService mExecutor;
|
||||||
private volatile boolean mSubmitAllowed = true;
|
private final List<Future<?>> mWorkerFutures;
|
||||||
|
private volatile boolean mSubmitAllowed;
|
||||||
|
|
||||||
public BackgroundWorker(int threads) {
|
public BackgroundWorker(int threads) {
|
||||||
mExecutor = Executors.newFixedThreadPool(threads);
|
mExecutor = Executors.newFixedThreadPool(threads);
|
||||||
|
mWorkerFutures = new ArrayList<>();
|
||||||
|
mSubmitAllowed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void waitForFinish() {
|
public void waitForFinish() {
|
||||||
|
@ -22,26 +22,27 @@ import brut.util.OSDetection;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
public class Config {
|
public final class Config {
|
||||||
private static Config instance = null;
|
private static final Logger LOGGER = Logger.getLogger(Config.class.getName());
|
||||||
private final static Logger LOGGER = Logger.getLogger(Config.class.getName());
|
|
||||||
|
|
||||||
public final static short DECODE_SOURCES_NONE = 0x0000;
|
public static final short DECODE_SOURCES_NONE = 0x0000;
|
||||||
public final static short DECODE_SOURCES_SMALI = 0x0001;
|
public static final short DECODE_SOURCES_SMALI = 0x0001;
|
||||||
public final static short DECODE_SOURCES_SMALI_ONLY_MAIN_CLASSES = 0x0010;
|
public static final short DECODE_SOURCES_SMALI_ONLY_MAIN_CLASSES = 0x0010;
|
||||||
|
|
||||||
public final static short DECODE_RESOURCES_NONE = 0x0100;
|
public static final short DECODE_RESOURCES_NONE = 0x0100;
|
||||||
public final static short DECODE_RESOURCES_FULL = 0x0101;
|
public static final short DECODE_RESOURCES_FULL = 0x0101;
|
||||||
|
|
||||||
public final static short FORCE_DECODE_MANIFEST_NONE = 0x0000;
|
public static final short FORCE_DECODE_MANIFEST_NONE = 0x0000;
|
||||||
public final static short FORCE_DECODE_MANIFEST_FULL = 0x0001;
|
public static final short FORCE_DECODE_MANIFEST_FULL = 0x0001;
|
||||||
|
|
||||||
public final static short DECODE_ASSETS_NONE = 0x0000;
|
public static final short DECODE_ASSETS_NONE = 0x0000;
|
||||||
public final static short DECODE_ASSETS_FULL = 0x0001;
|
public static final short DECODE_ASSETS_FULL = 0x0001;
|
||||||
|
|
||||||
public final static short DECODE_RES_RESOLVE_REMOVE = 0x0000;
|
public static final short DECODE_RES_RESOLVE_REMOVE = 0x0000;
|
||||||
public final static short DECODE_RES_RESOLVE_DUMMY = 0x0001;
|
public static final short DECODE_RES_RESOLVE_DUMMY = 0x0001;
|
||||||
public final static short DECODE_RES_RESOLVE_RETAIN = 0x0002;
|
public static final short DECODE_RES_RESOLVE_RETAIN = 0x0002;
|
||||||
|
|
||||||
|
private static Config sInstance;
|
||||||
|
|
||||||
// Build options
|
// Build options
|
||||||
public boolean forceBuildAll = false;
|
public boolean forceBuildAll = false;
|
||||||
@ -70,7 +71,7 @@ public class Config {
|
|||||||
public int jobs = Math.min(Runtime.getRuntime().availableProcessors(), 8);
|
public int jobs = Math.min(Runtime.getRuntime().availableProcessors(), 8);
|
||||||
public String frameworkDirectory = null;
|
public String frameworkDirectory = null;
|
||||||
public String frameworkTag = null;
|
public String frameworkTag = null;
|
||||||
public String aaptPath = "";
|
public File aaptBinary = null;
|
||||||
public int aaptVersion = 2; // default to v2
|
public int aaptVersion = 2; // default to v2
|
||||||
|
|
||||||
// Utility functions
|
// Utility functions
|
||||||
@ -83,14 +84,14 @@ public class Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Config() {
|
private Config() {
|
||||||
instance = this;
|
sInstance = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Config getInstance() {
|
public static Config getInstance() {
|
||||||
if (instance == null) {
|
if (sInstance == null) {
|
||||||
instance = new Config();
|
sInstance = new Config();
|
||||||
}
|
}
|
||||||
return instance;
|
return sInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setDefaultFrameworkDirectory() {
|
private void setDefaultFrameworkDirectory() {
|
||||||
|
@ -28,13 +28,13 @@ import java.util.*;
|
|||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class ApkInfo implements YamlSerializable {
|
public class ApkInfo implements YamlSerializable {
|
||||||
public final static String[] RESOURCES_DIRNAMES = new String[] { "res", "r", "R" };
|
public static final String[] RESOURCES_DIRNAMES = { "res", "r", "R" };
|
||||||
public final static String[] RAW_DIRNAMES = new String[] { "assets", "lib", "libs", "kotlin", "META-INF/services" };
|
public static final String[] RAW_DIRNAMES = { "assets", "lib", "libs", "kotlin", "META-INF/services" };
|
||||||
|
|
||||||
public final static Pattern ORIGINAL_FILENAMES_PATTERN = Pattern.compile(
|
public static final Pattern ORIGINAL_FILENAMES_PATTERN = Pattern.compile(
|
||||||
"AndroidManifest\\.xml|META-INF/[^/]+\\.(RSA|SF|MF)|stamp-cert-sha256");
|
"AndroidManifest\\.xml|META-INF/[^/]+\\.(RSA|SF|MF)|stamp-cert-sha256");
|
||||||
|
|
||||||
public final static Pattern STANDARD_FILENAMES_PATTERN = Pattern.compile(
|
public static final Pattern STANDARD_FILENAMES_PATTERN = Pattern.compile(
|
||||||
"[^/]+\\.dex|resources\\.arsc|(" + String.join("|", RESOURCES_DIRNAMES) + "|" +
|
"[^/]+\\.dex|resources\\.arsc|(" + String.join("|", RESOURCES_DIRNAMES) + "|" +
|
||||||
String.join("|", RAW_DIRNAMES) + ")/.*|" + ORIGINAL_FILENAMES_PATTERN.pattern());
|
String.join("|", RAW_DIRNAMES) + ")/.*|" + ORIGINAL_FILENAMES_PATTERN.pattern());
|
||||||
|
|
||||||
@ -194,10 +194,7 @@ public class ApkInfo implements YamlSerializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void save(File file) throws AndrolibException {
|
public void save(File file) throws AndrolibException {
|
||||||
try (
|
try (YamlWriter writer = new YamlWriter(Files.newOutputStream(file.toPath()))) {
|
||||||
OutputStream out = Files.newOutputStream(file.toPath());
|
|
||||||
YamlWriter writer = new YamlWriter(out)
|
|
||||||
) {
|
|
||||||
write(writer);
|
write(writer);
|
||||||
} catch (FileNotFoundException ex) {
|
} catch (FileNotFoundException ex) {
|
||||||
throw new AndrolibException("File not found");
|
throw new AndrolibException("File not found");
|
||||||
|
@ -19,7 +19,6 @@ package brut.androlib.apk;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public class YamlLine {
|
public class YamlLine {
|
||||||
|
|
||||||
public int indent = 0;
|
public int indent = 0;
|
||||||
private String key = "";
|
private String key = "";
|
||||||
private String value = "";
|
private String value = "";
|
||||||
@ -62,7 +61,7 @@ public class YamlLine {
|
|||||||
if (isItem) {
|
if (isItem) {
|
||||||
// array item line has only the value
|
// array item line has only the value
|
||||||
value = line.substring(1).trim();
|
value = line.substring(1).trim();
|
||||||
} else {
|
} else {
|
||||||
// split line to key - value
|
// split line to key - value
|
||||||
String[] parts = line.split(":");
|
String[] parts = line.split(":");
|
||||||
if (parts.length > 0) {
|
if (parts.length > 0) {
|
||||||
|
@ -22,9 +22,8 @@ import java.io.InputStream;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public class YamlReader {
|
public class YamlReader {
|
||||||
|
|
||||||
private ArrayList<YamlLine> mLines;
|
private ArrayList<YamlLine> mLines;
|
||||||
private int mCurrent = 0;
|
private int mCurrent;
|
||||||
|
|
||||||
public YamlReader(InputStream in) {
|
public YamlReader(InputStream in) {
|
||||||
mLines = new ArrayList<>();
|
mLines = new ArrayList<>();
|
||||||
|
@ -20,5 +20,6 @@ import brut.androlib.exceptions.AndrolibException;
|
|||||||
|
|
||||||
public interface YamlSerializable {
|
public interface YamlSerializable {
|
||||||
void readItem(YamlReader reader) throws AndrolibException;
|
void readItem(YamlReader reader) throws AndrolibException;
|
||||||
|
|
||||||
void write(YamlWriter writer);
|
void write(YamlWriter writer);
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,11 @@ import java.io.IOException;
|
|||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
|
|
||||||
public class YamlStringEscapeUtils {
|
public final class YamlStringEscapeUtils {
|
||||||
|
|
||||||
|
private YamlStringEscapeUtils() {
|
||||||
|
// Private constructor for utility class
|
||||||
|
}
|
||||||
|
|
||||||
public static String escapeString(String str) {
|
public static String escapeString(String str) {
|
||||||
return escapeJavaStyleString(str);
|
return escapeJavaStyleString(str);
|
||||||
@ -48,12 +52,12 @@ public class YamlStringEscapeUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param out write to receive the escaped string
|
* @param writer Writer to receive the escaped string
|
||||||
* @param str String to escape values in, may be null
|
* @param str String to escape values in, may be null
|
||||||
* @throws IOException if an IOException occurs
|
* @throws IOException if an IOException occurs
|
||||||
*/
|
*/
|
||||||
private static void escapeJavaStyleString(Writer out, String str) throws IOException {
|
private static void escapeJavaStyleString(Writer writer, String str) throws IOException {
|
||||||
if (out == null) {
|
if (writer == null) {
|
||||||
throw new IllegalArgumentException("The Writer must not be null");
|
throw new IllegalArgumentException("The Writer must not be null");
|
||||||
}
|
}
|
||||||
if (str == null) {
|
if (str == null) {
|
||||||
@ -66,51 +70,51 @@ public class YamlStringEscapeUtils {
|
|||||||
// "[^\t\n\r\u0020-\u007E\u0085\u00A0-\uD7FF\uE000-\uFFFD]"
|
// "[^\t\n\r\u0020-\u007E\u0085\u00A0-\uD7FF\uE000-\uFFFD]"
|
||||||
// handle unicode
|
// handle unicode
|
||||||
if (ch > 0xFFFD) {
|
if (ch > 0xFFFD) {
|
||||||
out.write("\\u" + CharSequenceTranslator.hex(ch));
|
writer.write("\\u" + CharSequenceTranslator.hex(ch));
|
||||||
} else if (ch > 0xD7FF && ch < 0xE000) {
|
} else if (ch > 0xD7FF && ch < 0xE000) {
|
||||||
out.write("\\u" + CharSequenceTranslator.hex(ch));
|
writer.write("\\u" + CharSequenceTranslator.hex(ch));
|
||||||
} else if (ch > 0x7E && ch != 0x85 && ch < 0xA0) {
|
} else if (ch > 0x7E && ch != 0x85 && ch < 0xA0) {
|
||||||
out.write("\\u00" + CharSequenceTranslator.hex(ch));
|
writer.write("\\u00" + CharSequenceTranslator.hex(ch));
|
||||||
} else if (ch < 32) {
|
} else if (ch < 32) {
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
case '\t' :
|
case '\t' :
|
||||||
out.write('\\');
|
writer.write('\\');
|
||||||
out.write('t');
|
writer.write('t');
|
||||||
break;
|
break;
|
||||||
case '\n' :
|
case '\n' :
|
||||||
out.write('\\');
|
writer.write('\\');
|
||||||
out.write('n');
|
writer.write('n');
|
||||||
break;
|
break;
|
||||||
case '\r' :
|
case '\r' :
|
||||||
out.write('\\');
|
writer.write('\\');
|
||||||
out.write('r');
|
writer.write('r');
|
||||||
break;
|
break;
|
||||||
default :
|
default :
|
||||||
if (ch > 0xf) {
|
if (ch > 0xf) {
|
||||||
out.write("\\u00" + CharSequenceTranslator.hex(ch));
|
writer.write("\\u00" + CharSequenceTranslator.hex(ch));
|
||||||
} else {
|
} else {
|
||||||
out.write("\\u000" + CharSequenceTranslator.hex(ch));
|
writer.write("\\u000" + CharSequenceTranslator.hex(ch));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
case '\'' :
|
case '\'' :
|
||||||
out.write('\'');
|
writer.write('\'');
|
||||||
break;
|
break;
|
||||||
case '"' :
|
case '"' :
|
||||||
out.write('\\');
|
writer.write('\\');
|
||||||
out.write('"');
|
writer.write('"');
|
||||||
break;
|
break;
|
||||||
case '\\' :
|
case '\\' :
|
||||||
out.write('\\');
|
writer.write('\\');
|
||||||
out.write('\\');
|
writer.write('\\');
|
||||||
break;
|
break;
|
||||||
case '/' :
|
case '/' :
|
||||||
out.write('/');
|
writer.write('/');
|
||||||
break;
|
break;
|
||||||
default :
|
default :
|
||||||
out.write(ch);
|
writer.write(ch);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,10 +21,10 @@ import java.nio.charset.StandardCharsets;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public class YamlWriter implements Closeable {
|
public class YamlWriter implements Closeable {
|
||||||
|
private static final String QUOTE = "'";
|
||||||
|
|
||||||
private int mIndent = 0;
|
|
||||||
private final PrintWriter mWriter;
|
private final PrintWriter mWriter;
|
||||||
private final String QUOTE = "'";
|
private int mIndent;
|
||||||
|
|
||||||
public YamlWriter(OutputStream out) {
|
public YamlWriter(OutputStream out) {
|
||||||
mWriter = new PrintWriter(new BufferedWriter(
|
mWriter = new PrintWriter(new BufferedWriter(
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package brut.androlib.exceptions;
|
package brut.androlib.exceptions;
|
||||||
|
|
||||||
public class AXmlDecodingException extends AndrolibException {
|
public class AXmlDecodingException extends AndrolibException {
|
||||||
|
|
||||||
public AXmlDecodingException(String message, Throwable cause) {
|
public AXmlDecodingException(String message, Throwable cause) {
|
||||||
super(message, cause);
|
super(message, cause);
|
||||||
}
|
}
|
||||||
|
@ -19,18 +19,20 @@ package brut.androlib.exceptions;
|
|||||||
import brut.common.BrutException;
|
import brut.common.BrutException;
|
||||||
|
|
||||||
public class AndrolibException extends BrutException {
|
public class AndrolibException extends BrutException {
|
||||||
|
|
||||||
public AndrolibException() {
|
public AndrolibException() {
|
||||||
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
public AndrolibException(String message) {
|
public AndrolibException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AndrolibException(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AndrolibException(Throwable cause) {
|
public AndrolibException(Throwable cause) {
|
||||||
super(cause);
|
super(cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AndrolibException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package brut.androlib.exceptions;
|
package brut.androlib.exceptions;
|
||||||
|
|
||||||
public class CantFind9PatchChunkException extends AndrolibException {
|
public class CantFind9PatchChunkException extends AndrolibException {
|
||||||
|
|
||||||
public CantFind9PatchChunkException(String message, Throwable cause) {
|
public CantFind9PatchChunkException(String message, Throwable cause) {
|
||||||
super(message, cause);
|
super(message, cause);
|
||||||
}
|
}
|
||||||
|
@ -17,8 +17,10 @@
|
|||||||
package brut.androlib.exceptions;
|
package brut.androlib.exceptions;
|
||||||
|
|
||||||
public class CantFindFrameworkResException extends AndrolibException {
|
public class CantFindFrameworkResException extends AndrolibException {
|
||||||
public CantFindFrameworkResException(int id) {
|
private final int mPkgId;
|
||||||
mPkgId = id;
|
|
||||||
|
public CantFindFrameworkResException(int pkgId) {
|
||||||
|
mPkgId = pkgId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getPkgId() {
|
public int getPkgId() {
|
||||||
@ -27,8 +29,6 @@ public class CantFindFrameworkResException extends AndrolibException {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getMessage() {
|
public String getMessage() {
|
||||||
return String.format("Can't find framework resources for package of id: %d", this.getPkgId());
|
return String.format("Could not find framework resources for package of id: %d", mPkgId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final int mPkgId;
|
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,8 @@
|
|||||||
package brut.androlib.exceptions;
|
package brut.androlib.exceptions;
|
||||||
|
|
||||||
public class InFileNotFoundException extends AndrolibException {
|
public class InFileNotFoundException extends AndrolibException {
|
||||||
|
|
||||||
public InFileNotFoundException() {
|
public InFileNotFoundException() {
|
||||||
|
super();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,8 @@
|
|||||||
package brut.androlib.exceptions;
|
package brut.androlib.exceptions;
|
||||||
|
|
||||||
public class OutDirExistsException extends AndrolibException {
|
public class OutDirExistsException extends AndrolibException {
|
||||||
|
|
||||||
public OutDirExistsException() {
|
public OutDirExistsException() {
|
||||||
|
super();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package brut.androlib.exceptions;
|
package brut.androlib.exceptions;
|
||||||
|
|
||||||
public class RawXmlEncounteredException extends AndrolibException {
|
public class RawXmlEncounteredException extends AndrolibException {
|
||||||
|
|
||||||
public RawXmlEncounteredException(String message, Throwable cause) {
|
public RawXmlEncounteredException(String message, Throwable cause) {
|
||||||
super(message, cause);
|
super(message, cause);
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package brut.androlib.exceptions;
|
package brut.androlib.exceptions;
|
||||||
|
|
||||||
public class UndefinedResObjectException extends AndrolibException {
|
public class UndefinedResObjectException extends AndrolibException {
|
||||||
|
|
||||||
public UndefinedResObjectException(String message) {
|
public UndefinedResObjectException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
@ -30,13 +30,16 @@ import java.io.*;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
|
||||||
public class SmaliMod {
|
public final class SmaliMod {
|
||||||
|
|
||||||
|
private SmaliMod() {
|
||||||
|
// Private constructor for utility class
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean assembleSmaliFile(File smaliFile, DexBuilder dexBuilder, int apiLevel, boolean verboseErrors,
|
public static boolean assembleSmaliFile(File smaliFile, DexBuilder dexBuilder, int apiLevel, boolean verboseErrors,
|
||||||
boolean printTokens) throws IOException, RecognitionException {
|
boolean printTokens) throws IOException, RecognitionException {
|
||||||
try (
|
try (InputStreamReader reader = new InputStreamReader(
|
||||||
InputStream in = Files.newInputStream(smaliFile.toPath());
|
Files.newInputStream(smaliFile.toPath()), StandardCharsets.UTF_8)) {
|
||||||
InputStreamReader reader = new InputStreamReader(in, StandardCharsets.UTF_8)
|
|
||||||
) {
|
|
||||||
smaliFlexLexer lexer = new smaliFlexLexer(reader, apiLevel);
|
smaliFlexLexer lexer = new smaliFlexLexer(reader, apiLevel);
|
||||||
lexer.setSourceFile(smaliFile);
|
lexer.setSourceFile(smaliFile);
|
||||||
CommonTokenStream tokens = new CommonTokenStream(lexer);
|
CommonTokenStream tokens = new CommonTokenStream(lexer);
|
||||||
|
@ -23,7 +23,6 @@ import brut.androlib.res.decoder.ARSCDecoder;
|
|||||||
import brut.androlib.res.data.arsc.ARSCData;
|
import brut.androlib.res.data.arsc.ARSCData;
|
||||||
import brut.androlib.res.data.arsc.FlagsOffset;
|
import brut.androlib.res.data.arsc.FlagsOffset;
|
||||||
import brut.util.BrutIO;
|
import brut.util.BrutIO;
|
||||||
import brut.util.Jar;
|
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@ -35,18 +34,17 @@ import java.util.zip.ZipFile;
|
|||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
public class Framework {
|
public class Framework {
|
||||||
private final Config config;
|
private static final Logger LOGGER = Logger.getLogger(Framework.class.getName());
|
||||||
|
|
||||||
private File mFrameworkDirectory = null;
|
private final Config mConfig;
|
||||||
|
private File mFrameworkDirectory;
|
||||||
private final static Logger LOGGER = Logger.getLogger(Framework.class.getName());
|
|
||||||
|
|
||||||
public Framework(Config config) {
|
public Framework(Config config) {
|
||||||
this.config = config;
|
mConfig = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void installFramework(File frameFile) throws AndrolibException {
|
public void installFramework(File frameFile) throws AndrolibException {
|
||||||
installFramework(frameFile, config.frameworkTag);
|
installFramework(frameFile, mConfig.frameworkTag);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void installFramework(File frameFile, String tag) throws AndrolibException {
|
public void installFramework(File frameFile, String tag) throws AndrolibException {
|
||||||
@ -54,17 +52,16 @@ public class Framework {
|
|||||||
ZipEntry entry = zip.getEntry("resources.arsc");
|
ZipEntry entry = zip.getEntry("resources.arsc");
|
||||||
|
|
||||||
if (entry == null) {
|
if (entry == null) {
|
||||||
throw new AndrolibException("Can't find resources.arsc file");
|
throw new AndrolibException("Could not find resources.arsc file");
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] data = BrutIO.readAndClose(zip.getInputStream(entry));
|
byte[] data = BrutIO.readAndClose(zip.getInputStream(entry));
|
||||||
ARSCData arsc = ARSCDecoder.decode(new ByteArrayInputStream(data), true, true);
|
ARSCDecoder decoder = new ARSCDecoder(new ByteArrayInputStream(data), null, true, true);
|
||||||
|
ARSCData arsc = decoder.decode();
|
||||||
publicizeResources(data, arsc.getFlagsOffsets());
|
publicizeResources(data, arsc.getFlagsOffsets());
|
||||||
|
|
||||||
File outFile = new File(getFrameworkDirectory(), arsc
|
File outFile = new File(getFrameworkDirectory(),
|
||||||
.getOnePackage().getId()
|
arsc.getOnePackage().getId() + (tag == null ? "" : '-' + tag) + ".apk");
|
||||||
+ (tag == null ? "" : '-' + tag)
|
|
||||||
+ ".apk");
|
|
||||||
|
|
||||||
try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(outFile.toPath()))) {
|
try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(outFile.toPath()))) {
|
||||||
out.setMethod(ZipOutputStream.STORED);
|
out.setMethod(ZipOutputStream.STORED);
|
||||||
@ -116,8 +113,10 @@ public class Framework {
|
|||||||
public void publicizeResources(File arscFile) throws AndrolibException {
|
public void publicizeResources(File arscFile) throws AndrolibException {
|
||||||
byte[] data = new byte[(int) arscFile.length()];
|
byte[] data = new byte[(int) arscFile.length()];
|
||||||
|
|
||||||
try(InputStream in = Files.newInputStream(arscFile.toPath());
|
try (
|
||||||
OutputStream out = Files.newOutputStream(arscFile.toPath())) {
|
InputStream in = Files.newInputStream(arscFile.toPath());
|
||||||
|
OutputStream out = Files.newOutputStream(arscFile.toPath())
|
||||||
|
) {
|
||||||
//noinspection ResultOfMethodCallIgnored
|
//noinspection ResultOfMethodCallIgnored
|
||||||
in.read(data);
|
in.read(data);
|
||||||
publicizeResources(data);
|
publicizeResources(data);
|
||||||
@ -127,16 +126,18 @@ public class Framework {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void publicizeResources(byte[] arsc) throws AndrolibException {
|
private void publicizeResources(byte[] data) throws AndrolibException {
|
||||||
publicizeResources(arsc, ARSCDecoder.decode(new ByteArrayInputStream(arsc), true, true).getFlagsOffsets());
|
ARSCDecoder decoder = new ARSCDecoder(new ByteArrayInputStream(data), null, true, true);
|
||||||
|
ARSCData arsc = decoder.decode();
|
||||||
|
publicizeResources(data, arsc.getFlagsOffsets());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void publicizeResources(byte[] arsc, FlagsOffset[] flagsOffsets) {
|
public void publicizeResources(byte[] data, FlagsOffset[] flagsOffsets) {
|
||||||
for (FlagsOffset flags : flagsOffsets) {
|
for (FlagsOffset flags : flagsOffsets) {
|
||||||
int offset = flags.offset + 3;
|
int offset = flags.offset + 3;
|
||||||
int end = offset + 4 * flags.count;
|
int end = offset + 4 * flags.count;
|
||||||
while (offset < end) {
|
while (offset < end) {
|
||||||
arsc[offset] |= (byte) 0x40;
|
data[offset] |= (byte) 0x40;
|
||||||
offset += 4;
|
offset += 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -150,7 +151,7 @@ public class Framework {
|
|||||||
String path;
|
String path;
|
||||||
|
|
||||||
// use default framework path or specified on the command line
|
// use default framework path or specified on the command line
|
||||||
path = config.frameworkDirectory;
|
path = mConfig.frameworkDirectory;
|
||||||
|
|
||||||
File dir = new File(path);
|
File dir = new File(path);
|
||||||
|
|
||||||
@ -162,19 +163,19 @@ public class Framework {
|
|||||||
throw new AndrolibException("Please remove file at " + dir.getParentFile());
|
throw new AndrolibException("Please remove file at " + dir.getParentFile());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! dir.exists()) {
|
if (!dir.exists()) {
|
||||||
if (! dir.mkdirs()) {
|
if (!dir.mkdirs()) {
|
||||||
if (config.frameworkDirectory != null) {
|
if (mConfig.frameworkDirectory != null) {
|
||||||
LOGGER.severe("Can't create Framework directory: " + dir);
|
LOGGER.severe("Could not create Framework directory: " + dir);
|
||||||
}
|
}
|
||||||
throw new AndrolibException(String.format(
|
throw new AndrolibException(String.format(
|
||||||
"Can't create directory: (%s). Pass a writable path with --frame-path {DIR}. ", dir
|
"Could not create directory: (%s). Pass a writable path with --frame-path {DIR}. ", dir
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.frameworkDirectory == null) {
|
if (mConfig.frameworkDirectory == null) {
|
||||||
if (! dir.canWrite()) {
|
if (!dir.canWrite()) {
|
||||||
LOGGER.severe(String.format("WARNING: Could not write to (%1$s), using %2$s instead...",
|
LOGGER.severe(String.format("WARNING: Could not write to (%1$s), using %2$s instead...",
|
||||||
dir.getAbsolutePath(), System.getProperty("java.io.tmpdir")));
|
dir.getAbsolutePath(), System.getProperty("java.io.tmpdir")));
|
||||||
LOGGER.severe("Please be aware this is a volatile directory and frameworks could go missing, " +
|
LOGGER.severe("Please be aware this is a volatile directory and frameworks could go missing, " +
|
||||||
@ -222,11 +223,11 @@ public class Framework {
|
|||||||
|
|
||||||
apk = new File(dir, "1.apk");
|
apk = new File(dir, "1.apk");
|
||||||
|
|
||||||
if (! apk.exists()) {
|
if (!apk.exists()) {
|
||||||
LOGGER.warning("Can't empty framework directory, no file found at: " + apk.getAbsolutePath());
|
LOGGER.warning("Could not empty framework directory, no file found at: " + apk.getAbsolutePath());
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
if (apk.exists() && Objects.requireNonNull(dir.listFiles()).length > 1 && ! config.forceDeleteFramework) {
|
if (apk.exists() && Objects.requireNonNull(dir.listFiles()).length > 1 && !mConfig.forceDeleteFramework) {
|
||||||
LOGGER.warning("More than default framework detected. Please run command with `--force` parameter to wipe framework directory.");
|
LOGGER.warning("More than default framework detected. Please run command with `--force` parameter to wipe framework directory.");
|
||||||
} else {
|
} else {
|
||||||
for (File file : Objects.requireNonNull(dir.listFiles())) {
|
for (File file : Objects.requireNonNull(dir.listFiles())) {
|
||||||
@ -244,6 +245,6 @@ public class Framework {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private InputStream getAndroidFrameworkResourcesAsStream() {
|
private InputStream getAndroidFrameworkResourcesAsStream() {
|
||||||
return Jar.class.getResourceAsStream("/brut/androlib/android-framework.jar");
|
return Framework.class.getResourceAsStream("/prebuilt/android-framework.jar");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,11 +22,12 @@ import brut.androlib.exceptions.AndrolibException;
|
|||||||
import brut.androlib.res.data.*;
|
import brut.androlib.res.data.*;
|
||||||
import brut.androlib.res.decoder.*;
|
import brut.androlib.res.decoder.*;
|
||||||
import brut.androlib.res.xml.ResValuesXmlSerializable;
|
import brut.androlib.res.xml.ResValuesXmlSerializable;
|
||||||
import brut.androlib.res.xml.ResXmlPatcher;
|
import brut.androlib.res.xml.ResXmlUtils;
|
||||||
import brut.directory.Directory;
|
import brut.directory.Directory;
|
||||||
import brut.directory.DirectoryException;
|
import brut.directory.DirectoryException;
|
||||||
import brut.directory.ExtFile;
|
import brut.directory.ExtFile;
|
||||||
import brut.xmlpull.MXSerializer;
|
import brut.xmlpull.MXSerializer;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
import org.xmlpull.v1.XmlSerializer;
|
import org.xmlpull.v1.XmlSerializer;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
@ -34,21 +35,23 @@ import java.util.*;
|
|||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
public class ResourcesDecoder {
|
public class ResourcesDecoder {
|
||||||
private final static Logger LOGGER = Logger.getLogger(ResourcesDecoder.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(ResourcesDecoder.class.getName());
|
||||||
|
|
||||||
|
private static final Set<String> IGNORED_PACKAGES = Sets.newHashSet(
|
||||||
|
"android", "com.htc", "com.lge", "com.lge.internal", "yi", "flyme", "air.com.adobe.appentry",
|
||||||
|
"FFFFFFFFFFFFFFFFFFFFFF"
|
||||||
|
);
|
||||||
|
|
||||||
private final Config mConfig;
|
private final Config mConfig;
|
||||||
private final ApkInfo mApkInfo;
|
private final ApkInfo mApkInfo;
|
||||||
private final ResTable mResTable;
|
private final ResTable mResTable;
|
||||||
private final Map<String, String> mResFileMapping = new HashMap<>();
|
private final Map<String, String> mResFileMapping;
|
||||||
|
|
||||||
private final static String[] IGNORED_PACKAGES = new String[] {
|
|
||||||
"android", "com.htc", "com.lge", "com.lge.internal", "yi", "flyme", "air.com.adobe.appentry",
|
|
||||||
"FFFFFFFFFFFFFFFFFFFFFF" };
|
|
||||||
|
|
||||||
public ResourcesDecoder(Config config, ApkInfo apkInfo) {
|
public ResourcesDecoder(Config config, ApkInfo apkInfo) {
|
||||||
mConfig = config;
|
mConfig = config;
|
||||||
mApkInfo = apkInfo;
|
mApkInfo = apkInfo;
|
||||||
mResTable = new ResTable(mConfig, mApkInfo);
|
mResTable = new ResTable(mConfig, mApkInfo);
|
||||||
|
mResFileMapping = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResTable getResTable() throws AndrolibException {
|
public ResTable getResTable() throws AndrolibException {
|
||||||
@ -59,7 +62,7 @@ public class ResourcesDecoder {
|
|||||||
return mResTable;
|
return mResTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, String> getResFileMapping() {
|
public Map<String, String> getResFileMapping() {
|
||||||
return mResFileMapping;
|
return mResFileMapping;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +70,7 @@ public class ResourcesDecoder {
|
|||||||
mResTable.loadMainPkg(mApkInfo.getApkFile());
|
mResTable.loadMainPkg(mApkInfo.getApkFile());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void decodeManifest(File outDir) throws AndrolibException {
|
public void decodeManifest(File apkDir) throws AndrolibException {
|
||||||
if (!mApkInfo.hasManifest()) {
|
if (!mApkInfo.hasManifest()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -76,10 +79,10 @@ public class ResourcesDecoder {
|
|||||||
XmlSerializer xmlSerializer = newXmlSerializer();
|
XmlSerializer xmlSerializer = newXmlSerializer();
|
||||||
ResStreamDecoder fileDecoder = new AndroidManifestPullStreamDecoder(axmlParser, xmlSerializer);
|
ResStreamDecoder fileDecoder = new AndroidManifestPullStreamDecoder(axmlParser, xmlSerializer);
|
||||||
|
|
||||||
Directory in, out;
|
Directory inDir, outDir;
|
||||||
try {
|
try {
|
||||||
in = mApkInfo.getApkFile().getDirectory();
|
inDir = mApkInfo.getApkFile().getDirectory();
|
||||||
out = new ExtFile(outDir).getDirectory();
|
outDir = new ExtFile(apkDir).getDirectory();
|
||||||
|
|
||||||
if (mApkInfo.hasResources()) {
|
if (mApkInfo.hasResources()) {
|
||||||
LOGGER.info("Decoding AndroidManifest.xml with resources...");
|
LOGGER.info("Decoding AndroidManifest.xml with resources...");
|
||||||
@ -88,16 +91,16 @@ public class ResourcesDecoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try (
|
try (
|
||||||
InputStream is = in.getFileInput("AndroidManifest.xml");
|
InputStream in = inDir.getFileInput("AndroidManifest.xml");
|
||||||
OutputStream os = out.getFileOutput("AndroidManifest.xml")
|
OutputStream out = outDir.getFileOutput("AndroidManifest.xml")
|
||||||
) {
|
) {
|
||||||
fileDecoder.decode(is, os);
|
fileDecoder.decode(in, out);
|
||||||
}
|
}
|
||||||
} catch (DirectoryException | IOException ex) {
|
} catch (DirectoryException | IOException ex) {
|
||||||
throw new AndrolibException(ex);
|
throw new AndrolibException(ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
File manifest = new File(outDir, "AndroidManifest.xml");
|
File manifest = new File(apkDir, "AndroidManifest.xml");
|
||||||
|
|
||||||
if (mApkInfo.hasResources() && !mConfig.analysisMode) {
|
if (mApkInfo.hasResources() && !mConfig.analysisMode) {
|
||||||
// Remove versionName / versionCode (aapt API 16)
|
// Remove versionName / versionCode (aapt API 16)
|
||||||
@ -108,14 +111,14 @@ public class ResourcesDecoder {
|
|||||||
// it will be passed as a parameter to aapt like "--min-sdk-version" via apktool.yml
|
// it will be passed as a parameter to aapt like "--min-sdk-version" via apktool.yml
|
||||||
adjustPackageManifest(manifest);
|
adjustPackageManifest(manifest);
|
||||||
|
|
||||||
ResXmlPatcher.removeManifestVersions(manifest);
|
ResXmlUtils.removeManifestVersions(manifest);
|
||||||
|
|
||||||
// update apk info
|
// update apk info
|
||||||
mApkInfo.packageInfo.forcedPackageId = String.valueOf(mResTable.getPackageId());
|
mApkInfo.packageInfo.forcedPackageId = String.valueOf(mResTable.getPackageId());
|
||||||
}
|
}
|
||||||
|
|
||||||
// record feature flags
|
// record feature flags
|
||||||
List<String> featureFlags = ResXmlPatcher.pullManifestFeatureFlags(manifest);
|
List<String> featureFlags = ResXmlUtils.pullManifestFeatureFlags(manifest);
|
||||||
if (featureFlags != null) {
|
if (featureFlags != null) {
|
||||||
for (String flag : featureFlags) {
|
for (String flag : featureFlags) {
|
||||||
mApkInfo.addFeatureFlag(flag, true);
|
mApkInfo.addFeatureFlag(flag, true);
|
||||||
@ -123,8 +126,8 @@ public class ResourcesDecoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateApkInfo(File outDir) throws AndrolibException {
|
public void updateApkInfo(File apkDir) throws AndrolibException {
|
||||||
mResTable.initApkInfo(mApkInfo, outDir);
|
mResTable.initApkInfo(mApkInfo, apkDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void adjustPackageManifest(File manifest) throws AndrolibException {
|
private void adjustPackageManifest(File manifest) throws AndrolibException {
|
||||||
@ -141,15 +144,15 @@ public class ResourcesDecoder {
|
|||||||
// 3) Check if pkgOriginal === mPackageRenamed
|
// 3) Check if pkgOriginal === mPackageRenamed
|
||||||
// 4) Check if pkgOriginal is ignored via IGNORED_PACKAGES
|
// 4) Check if pkgOriginal is ignored via IGNORED_PACKAGES
|
||||||
if (pkgOriginal == null || pkgRenamed == null || pkgOriginal.equals(pkgRenamed)
|
if (pkgOriginal == null || pkgRenamed == null || pkgOriginal.equals(pkgRenamed)
|
||||||
|| (Arrays.asList(IGNORED_PACKAGES).contains(pkgOriginal))) {
|
|| IGNORED_PACKAGES.contains(pkgOriginal)) {
|
||||||
LOGGER.info("Regular manifest package...");
|
LOGGER.info("Regular manifest package...");
|
||||||
} else {
|
} else {
|
||||||
LOGGER.info("Renamed manifest package found! Replacing " + pkgRenamed + " with " + pkgOriginal);
|
LOGGER.info("Renamed manifest package found! Replacing " + pkgRenamed + " with " + pkgOriginal);
|
||||||
ResXmlPatcher.renameManifestPackage(manifest, pkgOriginal);
|
ResXmlUtils.renameManifestPackage(manifest, pkgOriginal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void decodeResources(File outDir) throws AndrolibException {
|
public void decodeResources(File apkDir) throws AndrolibException {
|
||||||
if (!mApkInfo.hasResources()) {
|
if (!mApkInfo.hasResources()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -165,11 +168,11 @@ public class ResourcesDecoder {
|
|||||||
decoders.setDecoder("xml", new ResXmlPullStreamDecoder(axmlParser, xmlSerializer));
|
decoders.setDecoder("xml", new ResXmlPullStreamDecoder(axmlParser, xmlSerializer));
|
||||||
|
|
||||||
ResFileDecoder fileDecoder = new ResFileDecoder(decoders);
|
ResFileDecoder fileDecoder = new ResFileDecoder(decoders);
|
||||||
Directory in, out, outRes;
|
Directory inDir, outDir;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
in = mApkInfo.getApkFile().getDirectory();
|
inDir = mApkInfo.getApkFile().getDirectory();
|
||||||
out = new ExtFile(outDir).getDirectory().createDir("res");
|
outDir = new ExtFile(apkDir).getDirectory().createDir("res");
|
||||||
} catch (DirectoryException ex) {
|
} catch (DirectoryException ex) {
|
||||||
throw new AndrolibException(ex);
|
throw new AndrolibException(ex);
|
||||||
}
|
}
|
||||||
@ -177,15 +180,15 @@ public class ResourcesDecoder {
|
|||||||
for (ResPackage pkg : mResTable.listMainPackages()) {
|
for (ResPackage pkg : mResTable.listMainPackages()) {
|
||||||
LOGGER.info("Decoding file-resources...");
|
LOGGER.info("Decoding file-resources...");
|
||||||
for (ResResource res : pkg.listFiles()) {
|
for (ResResource res : pkg.listFiles()) {
|
||||||
fileDecoder.decode(res, in, out, mResFileMapping);
|
fileDecoder.decode(res, inDir, outDir, mResFileMapping);
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGGER.info("Decoding values */* XMLs...");
|
LOGGER.info("Decoding values */* XMLs...");
|
||||||
for (ResValuesFile valuesFile : pkg.listValuesFiles()) {
|
for (ResValuesFile valuesFile : pkg.listValuesFiles()) {
|
||||||
generateValuesFile(valuesFile, out, xmlSerializer);
|
generateValuesFile(valuesFile, outDir, xmlSerializer);
|
||||||
}
|
}
|
||||||
|
|
||||||
generatePublicXml(pkg, out, xmlSerializer);
|
generatePublicXml(pkg, outDir, xmlSerializer);
|
||||||
}
|
}
|
||||||
|
|
||||||
AndrolibException decodeError = axmlParser.getFirstError();
|
AndrolibException decodeError = axmlParser.getFirstError();
|
||||||
@ -207,11 +210,10 @@ public class ResourcesDecoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void generateValuesFile(ResValuesFile valuesFile, Directory out,
|
private void generateValuesFile(ResValuesFile valuesFile, Directory resDir, XmlSerializer serial)
|
||||||
XmlSerializer serial) throws AndrolibException {
|
throws AndrolibException {
|
||||||
try {
|
try (OutputStream out = resDir.getFileOutput(valuesFile.getPath())) {
|
||||||
OutputStream outStream = out.getFileOutput(valuesFile.getPath());
|
serial.setOutput(out, null);
|
||||||
serial.setOutput(outStream, null);
|
|
||||||
serial.startDocument(null, null);
|
serial.startDocument(null, null);
|
||||||
serial.startTag(null, "resources");
|
serial.startTag(null, "resources");
|
||||||
|
|
||||||
@ -225,17 +227,15 @@ public class ResourcesDecoder {
|
|||||||
serial.endTag(null, "resources");
|
serial.endTag(null, "resources");
|
||||||
serial.endDocument();
|
serial.endDocument();
|
||||||
serial.flush();
|
serial.flush();
|
||||||
outStream.close();
|
|
||||||
} catch (DirectoryException | IOException ex) {
|
} catch (DirectoryException | IOException ex) {
|
||||||
throw new AndrolibException("Could not generate: " + valuesFile.getPath(), ex);
|
throw new AndrolibException("Could not generate: " + valuesFile.getPath(), ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void generatePublicXml(ResPackage pkg, Directory out,
|
private void generatePublicXml(ResPackage pkg, Directory resDir, XmlSerializer serial)
|
||||||
XmlSerializer serial) throws AndrolibException {
|
throws AndrolibException {
|
||||||
try {
|
try (OutputStream out = resDir.getFileOutput("values/public.xml")) {
|
||||||
OutputStream outStream = out.getFileOutput("values/public.xml");
|
serial.setOutput(out, null);
|
||||||
serial.setOutput(outStream, null);
|
|
||||||
serial.startDocument(null, null);
|
serial.startDocument(null, null);
|
||||||
serial.startTag(null, "resources");
|
serial.startTag(null, "resources");
|
||||||
|
|
||||||
@ -250,7 +250,6 @@ public class ResourcesDecoder {
|
|||||||
serial.endTag(null, "resources");
|
serial.endTag(null, "resources");
|
||||||
serial.endDocument();
|
serial.endDocument();
|
||||||
serial.flush();
|
serial.flush();
|
||||||
outStream.close();
|
|
||||||
} catch (DirectoryException | IOException ex) {
|
} catch (DirectoryException | IOException ex) {
|
||||||
throw new AndrolibException("Could not generate public.xml file", ex);
|
throw new AndrolibException("Could not generate public.xml file", ex);
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,165 @@ package brut.androlib.res.data;
|
|||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
public class ResConfigFlags {
|
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;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
// 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 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 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;
|
||||||
|
public static final int DENSITY_XXHIGH = 480;
|
||||||
|
public static final int DENSITY_XXXHIGH = 640;
|
||||||
|
public static final int DENSITY_ANY = 0xFFFE;
|
||||||
|
public static final int DENSITY_NONE = 0xFFFF;
|
||||||
|
|
||||||
|
public static final int MNC_ZERO = -1;
|
||||||
|
|
||||||
|
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 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 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 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 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 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 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 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 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 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;
|
||||||
|
|
||||||
|
// 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 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 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;
|
||||||
|
|
||||||
public final short mcc;
|
public final short mcc;
|
||||||
public final short mnc;
|
public final short mnc;
|
||||||
|
|
||||||
@ -137,7 +296,7 @@ public class ResConfigFlags {
|
|||||||
if (localeVariant[0] == '\00') {
|
if (localeVariant[0] == '\00') {
|
||||||
localeVariant = null;
|
localeVariant = null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
localeVariant = null;
|
localeVariant = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -522,172 +681,13 @@ public class ResConfigFlags {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final ResConfigFlags other = (ResConfigFlags) obj;
|
final ResConfigFlags other = (ResConfigFlags) obj;
|
||||||
return this.mQualifiers.equals(other.mQualifiers);
|
return mQualifiers.equals(other.mQualifiers);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
int hash = 17;
|
int hash = 17;
|
||||||
hash = 31 * hash + this.mQualifiers.hashCode();
|
hash = 31 * hash + mQualifiers.hashCode();
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
public final static byte SDK_BASE = 1;
|
|
||||||
public final static byte SDK_BASE_1_1 = 2;
|
|
||||||
public final static byte SDK_CUPCAKE = 3;
|
|
||||||
public final static byte SDK_DONUT = 4;
|
|
||||||
public final static byte SDK_ECLAIR = 5;
|
|
||||||
public final static byte SDK_ECLAIR_0_1 = 6;
|
|
||||||
public final static byte SDK_ECLAIR_MR1 = 7;
|
|
||||||
public final static byte SDK_FROYO = 8;
|
|
||||||
public final static byte SDK_GINGERBREAD = 9;
|
|
||||||
public final static byte SDK_GINGERBREAD_MR1 = 10;
|
|
||||||
public final static byte SDK_HONEYCOMB = 11;
|
|
||||||
public final static byte SDK_HONEYCOMB_MR1 = 12;
|
|
||||||
public final static byte SDK_HONEYCOMB_MR2 = 13;
|
|
||||||
public final static byte SDK_ICE_CREAM_SANDWICH = 14;
|
|
||||||
public final static byte SDK_ICE_CREAM_SANDWICH_MR1 = 15;
|
|
||||||
public final static byte SDK_JELLY_BEAN = 16;
|
|
||||||
public final static byte SDK_JELLY_BEAN_MR1 = 17;
|
|
||||||
public final static byte SDK_JELLY_BEAN_MR2 = 18;
|
|
||||||
public final static byte SDK_KITKAT = 19;
|
|
||||||
public final static byte SDK_LOLLIPOP = 21;
|
|
||||||
public final static byte SDK_LOLLIPOP_MR1 = 22;
|
|
||||||
public final static byte SDK_MNC = 23;
|
|
||||||
public final static byte SDK_NOUGAT = 24;
|
|
||||||
public final static byte SDK_NOUGAT_MR1 = 25;
|
|
||||||
public final static byte SDK_OREO = 26;
|
|
||||||
public final static byte SDK_OREO_MR1 = 27;
|
|
||||||
public final static byte SDK_P = 28;
|
|
||||||
public final static byte SDK_Q = 29;
|
|
||||||
public final static byte SDK_R = 30;
|
|
||||||
public final static byte SDK_S = 31;
|
|
||||||
public final static byte SDK_S_V2 = 32;
|
|
||||||
public final static byte SDK_TIRAMISU = 33;
|
|
||||||
public final static byte SDK_UPSIDEDOWN_CAKE = 34;
|
|
||||||
public final static byte 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 final static byte SDK_BAKLAVA = 36;
|
|
||||||
|
|
||||||
// AOSP has this as 10,000 for dev purposes.
|
|
||||||
// platform_frameworks_base/commit/c7a1109a1fe0771d4c9b572dcf178e2779fc4f2d
|
|
||||||
public final static int SDK_DEVELOPMENT = 10000;
|
|
||||||
|
|
||||||
public final static byte ORIENTATION_ANY = 0;
|
|
||||||
public final static byte ORIENTATION_PORT = 1;
|
|
||||||
public final static byte ORIENTATION_LAND = 2;
|
|
||||||
public final static byte ORIENTATION_SQUARE = 3;
|
|
||||||
|
|
||||||
public final static byte TOUCHSCREEN_ANY = 0;
|
|
||||||
public final static byte TOUCHSCREEN_NOTOUCH = 1;
|
|
||||||
public final static byte TOUCHSCREEN_STYLUS = 2;
|
|
||||||
public final static byte TOUCHSCREEN_FINGER = 3;
|
|
||||||
|
|
||||||
public final static int DENSITY_DEFAULT = 0;
|
|
||||||
public final static int DENSITY_LOW = 120;
|
|
||||||
public final static int DENSITY_MEDIUM = 160;
|
|
||||||
public final static int DENSITY_400 = 190;
|
|
||||||
public final static int DENSITY_TV = 213;
|
|
||||||
public final static int DENSITY_HIGH = 240;
|
|
||||||
public final static int DENSITY_XHIGH = 320;
|
|
||||||
public final static int DENSITY_XXHIGH = 480;
|
|
||||||
public final static int DENSITY_XXXHIGH = 640;
|
|
||||||
public final static int DENSITY_ANY = 0xFFFE;
|
|
||||||
public final static int DENSITY_NONE = 0xFFFF;
|
|
||||||
|
|
||||||
public final static int MNC_ZERO = -1;
|
|
||||||
|
|
||||||
public final static short MASK_LAYOUTDIR = 0xc0;
|
|
||||||
public final static short SCREENLAYOUT_LAYOUTDIR_ANY = 0x00;
|
|
||||||
public final static short SCREENLAYOUT_LAYOUTDIR_LTR = 0x40;
|
|
||||||
public final static short SCREENLAYOUT_LAYOUTDIR_RTL = 0x80;
|
|
||||||
public final static short SCREENLAYOUT_LAYOUTDIR_SHIFT = 0x06;
|
|
||||||
|
|
||||||
public final static short MASK_SCREENROUND = 0x03;
|
|
||||||
public final static short SCREENLAYOUT_ROUND_ANY = 0;
|
|
||||||
public final static short SCREENLAYOUT_ROUND_NO = 0x1;
|
|
||||||
public final static short SCREENLAYOUT_ROUND_YES = 0x2;
|
|
||||||
|
|
||||||
public final static byte GRAMMATICAL_GENDER_ANY = 0;
|
|
||||||
public final static byte GRAMMATICAL_GENDER_NEUTER = 1;
|
|
||||||
public final static byte GRAMMATICAL_GENDER_FEMININE = 2;
|
|
||||||
public final static byte GRAMMATICAL_GENDER_MASCULINE = 3;
|
|
||||||
|
|
||||||
public final static byte KEYBOARD_ANY = 0;
|
|
||||||
public final static byte KEYBOARD_NOKEYS = 1;
|
|
||||||
public final static byte KEYBOARD_QWERTY = 2;
|
|
||||||
public final static byte KEYBOARD_12KEY = 3;
|
|
||||||
|
|
||||||
public final static byte NAVIGATION_ANY = 0;
|
|
||||||
public final static byte NAVIGATION_NONAV = 1;
|
|
||||||
public final static byte NAVIGATION_DPAD = 2;
|
|
||||||
public final static byte NAVIGATION_TRACKBALL = 3;
|
|
||||||
public final static byte NAVIGATION_WHEEL = 4;
|
|
||||||
|
|
||||||
public final static byte MASK_KEYSHIDDEN = 0x3;
|
|
||||||
public final static byte KEYSHIDDEN_ANY = 0x0;
|
|
||||||
public final static byte KEYSHIDDEN_NO = 0x1;
|
|
||||||
public final static byte KEYSHIDDEN_YES = 0x2;
|
|
||||||
public final static byte KEYSHIDDEN_SOFT = 0x3;
|
|
||||||
|
|
||||||
public final static byte MASK_NAVHIDDEN = 0xc;
|
|
||||||
public final static byte NAVHIDDEN_ANY = 0x0;
|
|
||||||
public final static byte NAVHIDDEN_NO = 0x4;
|
|
||||||
public final static byte NAVHIDDEN_YES = 0x8;
|
|
||||||
|
|
||||||
public final static byte MASK_SCREENSIZE = 0x0f;
|
|
||||||
public final static byte SCREENSIZE_ANY = 0x00;
|
|
||||||
public final static byte SCREENSIZE_SMALL = 0x01;
|
|
||||||
public final static byte SCREENSIZE_NORMAL = 0x02;
|
|
||||||
public final static byte SCREENSIZE_LARGE = 0x03;
|
|
||||||
public final static byte SCREENSIZE_XLARGE = 0x04;
|
|
||||||
|
|
||||||
public final static byte MASK_SCREENLONG = 0x30;
|
|
||||||
public final static byte SCREENLONG_ANY = 0x00;
|
|
||||||
public final static byte SCREENLONG_NO = 0x10;
|
|
||||||
public final static byte SCREENLONG_YES = 0x20;
|
|
||||||
|
|
||||||
public final static byte MASK_UI_MODE_TYPE = 0x0f;
|
|
||||||
public final static byte UI_MODE_TYPE_ANY = 0x00;
|
|
||||||
public final static byte UI_MODE_TYPE_NORMAL = 0x01;
|
|
||||||
public final static byte UI_MODE_TYPE_DESK = 0x02;
|
|
||||||
public final static byte UI_MODE_TYPE_CAR = 0x03;
|
|
||||||
public final static byte UI_MODE_TYPE_TELEVISION = 0x04;
|
|
||||||
public final static byte UI_MODE_TYPE_APPLIANCE = 0x05;
|
|
||||||
public final static byte UI_MODE_TYPE_WATCH = 0x06;
|
|
||||||
public final static byte UI_MODE_TYPE_VR_HEADSET = 0x07;
|
|
||||||
|
|
||||||
// start - miui
|
|
||||||
public final static byte UI_MODE_TYPE_GODZILLAUI = 0x0b;
|
|
||||||
public final static byte UI_MODE_TYPE_SMALLUI = 0x0c;
|
|
||||||
public final static byte UI_MODE_TYPE_MEDIUMUI = 0x0d;
|
|
||||||
public final static byte UI_MODE_TYPE_LARGEUI = 0x0e;
|
|
||||||
public final static byte UI_MODE_TYPE_HUGEUI = 0x0f;
|
|
||||||
// end - miui
|
|
||||||
|
|
||||||
public final static byte MASK_UI_MODE_NIGHT = 0x30;
|
|
||||||
public final static byte UI_MODE_NIGHT_ANY = 0x00;
|
|
||||||
public final static byte UI_MODE_NIGHT_NO = 0x10;
|
|
||||||
public final static byte UI_MODE_NIGHT_YES = 0x20;
|
|
||||||
|
|
||||||
public final static byte COLOR_HDR_MASK = 0xC;
|
|
||||||
public final static byte COLOR_HDR_NO = 0x4;
|
|
||||||
public final static byte COLOR_HDR_SHIFT = 0x2;
|
|
||||||
public final static byte COLOR_HDR_UNDEFINED = 0x0;
|
|
||||||
public final static byte COLOR_HDR_YES = 0x8;
|
|
||||||
|
|
||||||
public final static byte COLOR_UNDEFINED = 0x0;
|
|
||||||
|
|
||||||
public final static byte COLOR_WIDE_UNDEFINED = 0x0;
|
|
||||||
public final static byte COLOR_WIDE_NO = 0x1;
|
|
||||||
public final static byte COLOR_WIDE_YES = 0x2;
|
|
||||||
public final static byte COLOR_WIDE_MASK = 0x3;
|
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(ResConfigFlags.class.getName());
|
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ public class ResID {
|
|||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
int hash = 17;
|
int hash = 17;
|
||||||
hash = 31 * hash + this.id;
|
hash = 31 * hash + id;
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,6 +58,6 @@ public class ResID {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final ResID other = (ResID) obj;
|
final ResID other = (ResID) obj;
|
||||||
return this.id == other.id;
|
return id == other.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,20 +26,26 @@ import java.util.*;
|
|||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
public class ResPackage {
|
public class ResPackage {
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(ResPackage.class.getName());
|
||||||
|
|
||||||
private final ResTable mResTable;
|
private final ResTable mResTable;
|
||||||
private final int mId;
|
private final int mId;
|
||||||
private final String mName;
|
private final String mName;
|
||||||
private final Map<ResID, ResResSpec> mResSpecs = new LinkedHashMap<>();
|
private final Map<ResID, ResResSpec> mResSpecs;
|
||||||
private final Map<ResConfigFlags, ResType> mConfigs = new LinkedHashMap<>();
|
private final Map<ResConfigFlags, ResType> mConfigs;
|
||||||
private final Map<String, ResTypeSpec> mTypes = new LinkedHashMap<>();
|
private final Map<String, ResTypeSpec> mTypes;
|
||||||
private final Set<ResID> mSynthesizedRes = new HashSet<>();
|
private final Set<ResID> mSynthesizedRes;
|
||||||
|
|
||||||
private ResValueFactory mValueFactory;
|
private ResValueFactory mValueFactory;
|
||||||
|
|
||||||
public ResPackage(ResTable resTable, int id, String name) {
|
public ResPackage(ResTable resTable, int id, String name) {
|
||||||
this.mResTable = resTable;
|
mResTable = resTable;
|
||||||
this.mId = id;
|
mId = id;
|
||||||
this.mName = name;
|
mName = name;
|
||||||
|
mResSpecs = new LinkedHashMap<>();
|
||||||
|
mConfigs = new LinkedHashMap<>();
|
||||||
|
mTypes = new LinkedHashMap<>();
|
||||||
|
mSynthesizedRes = new HashSet<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ResResSpec> listResSpecs() {
|
public List<ResResSpec> listResSpecs() {
|
||||||
@ -159,17 +165,17 @@ public class ResPackage {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final ResPackage other = (ResPackage) obj;
|
final ResPackage other = (ResPackage) obj;
|
||||||
if (!Objects.equals(this.mResTable, other.mResTable)) {
|
if (!Objects.equals(mResTable, other.mResTable)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return this.mId == other.mId;
|
return mId == other.mId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
int hash = 17;
|
int hash = 17;
|
||||||
hash = 31 * hash + (this.mResTable != null ? this.mResTable.hashCode() : 0);
|
hash = 31 * hash + (mResTable != null ? mResTable.hashCode() : 0);
|
||||||
hash = 31 * hash + this.mId;
|
hash = 31 * hash + mId;
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,6 +185,4 @@ public class ResPackage {
|
|||||||
}
|
}
|
||||||
return mValueFactory;
|
return mValueFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final static Logger LOGGER = Logger.getLogger(ResPackage.class.getName());
|
|
||||||
}
|
}
|
||||||
|
@ -18,37 +18,36 @@ package brut.androlib.res.data;
|
|||||||
|
|
||||||
import brut.androlib.exceptions.AndrolibException;
|
import brut.androlib.exceptions.AndrolibException;
|
||||||
import brut.androlib.exceptions.UndefinedResObjectException;
|
import brut.androlib.exceptions.UndefinedResObjectException;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public class ResResSpec {
|
public class ResResSpec {
|
||||||
|
private static final Set<String> EMPTY_RESOURCE_NAMES = Sets.newHashSet(
|
||||||
|
"0_resource_name_obfuscated", "(name removed)"
|
||||||
|
);
|
||||||
|
|
||||||
private final ResID mId;
|
private final ResID mId;
|
||||||
private final String mName;
|
private final String mName;
|
||||||
private final ResPackage mPackage;
|
private final ResPackage mPackage;
|
||||||
private final ResTypeSpec mType;
|
private final ResTypeSpec mType;
|
||||||
private final Map<ResConfigFlags, ResResource> mResources = new LinkedHashMap<>();
|
private final Map<ResConfigFlags, ResResource> mResources;
|
||||||
private static final Set<String> EMPTY_RESOURCE_NAMES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
|
|
||||||
"0_resource_name_obfuscated",
|
|
||||||
"(name removed)"
|
|
||||||
)));
|
|
||||||
|
|
||||||
public ResResSpec(ResID id, String name, ResPackage pkg, ResTypeSpec type) {
|
public ResResSpec(ResID id, String name, ResPackage pkg, ResTypeSpec type) {
|
||||||
this.mId = id;
|
mId = id;
|
||||||
if (name == null || name.isEmpty() || EMPTY_RESOURCE_NAMES.contains(name)) {
|
if (name == null || name.isEmpty() || EMPTY_RESOURCE_NAMES.contains(name)) {
|
||||||
name = "APKTOOL_DUMMYVAL_" + id.toString();
|
name = "APKTOOL_DUMMYVAL_" + id.toString();
|
||||||
} else if (type.getResSpecUnsafe(name) != null) {
|
} else if (type.getResSpecUnsafe(name) != null) {
|
||||||
name = String.format("APKTOOL_DUPLICATE_%s_%s", type, id.toString());
|
name = String.format("APKTOOL_DUPLICATE_%s_%s", type, id.toString());
|
||||||
}
|
}
|
||||||
this.mName = name;
|
mName = name;
|
||||||
this.mPackage = pkg;
|
mPackage = pkg;
|
||||||
this.mType = type;
|
mType = type;
|
||||||
|
mResources = new LinkedHashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<ResResource> listResources() {
|
public Set<ResResource> listResources() {
|
||||||
|
@ -25,9 +25,9 @@ public class ResResource {
|
|||||||
private final ResValue mValue;
|
private final ResValue mValue;
|
||||||
|
|
||||||
public ResResource(ResType config, ResResSpec spec, ResValue value) {
|
public ResResource(ResType config, ResResSpec spec, ResValue value) {
|
||||||
this.mConfig = config;
|
mConfig = config;
|
||||||
this.mResSpec = spec;
|
mResSpec = spec;
|
||||||
this.mValue = value;
|
mValue = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getFilePath() {
|
public String getFilePath() {
|
||||||
|
@ -25,33 +25,30 @@ import brut.androlib.apk.UsesFramework;
|
|||||||
import brut.androlib.res.Framework;
|
import brut.androlib.res.Framework;
|
||||||
import brut.androlib.res.data.value.ResValue;
|
import brut.androlib.res.data.value.ResValue;
|
||||||
import brut.androlib.res.decoder.ARSCDecoder;
|
import brut.androlib.res.decoder.ARSCDecoder;
|
||||||
import brut.androlib.res.xml.ResXmlPatcher;
|
import brut.androlib.res.xml.ResXmlUtils;
|
||||||
import brut.directory.Directory;
|
import brut.directory.Directory;
|
||||||
import brut.directory.DirectoryException;
|
import brut.directory.DirectoryException;
|
||||||
import brut.directory.ExtFile;
|
import brut.directory.ExtFile;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
import java.io.*;
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
public class ResTable {
|
public class ResTable {
|
||||||
private final static Logger LOGGER = Logger.getLogger(ApkDecoder.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(ApkDecoder.class.getName());
|
||||||
|
|
||||||
private final Config mConfig;
|
private final Config mConfig;
|
||||||
private final ApkInfo mApkInfo;
|
private final ApkInfo mApkInfo;
|
||||||
private final Map<Integer, ResPackage> mPackagesById = new HashMap<>();
|
private final Map<Integer, ResPackage> mPackagesById;
|
||||||
private final Map<String, ResPackage> mPackagesByName = new HashMap<>();
|
private final Map<String, ResPackage> mPackagesByName;
|
||||||
private final Set<ResPackage> mMainPackages = new LinkedHashSet<>();
|
private final Set<ResPackage> mMainPackages;
|
||||||
private final Set<ResPackage> mFramePackages = new LinkedHashSet<>();
|
private final Set<ResPackage> mFramePackages;
|
||||||
|
|
||||||
private String mPackageRenamed;
|
private String mPackageRenamed;
|
||||||
private String mPackageOriginal;
|
private String mPackageOriginal;
|
||||||
private int mPackageId;
|
private int mPackageId;
|
||||||
|
private boolean mMainPkgLoaded;
|
||||||
private boolean mMainPkgLoaded = false;
|
|
||||||
|
|
||||||
public ResTable() {
|
public ResTable() {
|
||||||
this(Config.getDefaultConfig(), new ApkInfo());
|
this(Config.getDefaultConfig(), new ApkInfo());
|
||||||
@ -64,6 +61,10 @@ public class ResTable {
|
|||||||
public ResTable(Config config, ApkInfo apkInfo) {
|
public ResTable(Config config, ApkInfo apkInfo) {
|
||||||
mConfig = config;
|
mConfig = config;
|
||||||
mApkInfo = apkInfo;
|
mApkInfo = apkInfo;
|
||||||
|
mPackagesById = new HashMap<>();
|
||||||
|
mPackagesByName = new HashMap<>();
|
||||||
|
mMainPackages = new LinkedHashSet<>();
|
||||||
|
mFramePackages = new LinkedHashSet<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getAnalysisMode() {
|
public boolean getAnalysisMode() {
|
||||||
@ -175,11 +176,13 @@ public class ResTable {
|
|||||||
return pkg;
|
return pkg;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ResPackage[] loadResPackagesFromApk(ExtFile apkFile, boolean keepBrokenResources) throws AndrolibException {
|
private ResPackage[] loadResPackagesFromApk(ExtFile apkFile, boolean keepBrokenResources)
|
||||||
|
throws AndrolibException {
|
||||||
try {
|
try {
|
||||||
Directory dir = apkFile.getDirectory();
|
Directory dir = apkFile.getDirectory();
|
||||||
try (BufferedInputStream bis = new BufferedInputStream(dir.getFileInput("resources.arsc"))) {
|
try (BufferedInputStream in = new BufferedInputStream(dir.getFileInput("resources.arsc"))) {
|
||||||
return ARSCDecoder.decode(bis, false, keepBrokenResources, this).getPackages();
|
ARSCDecoder decoder = new ARSCDecoder(in, this, false, keepBrokenResources);
|
||||||
|
return decoder.decode().getPackages();
|
||||||
}
|
}
|
||||||
} catch (DirectoryException | IOException ex) {
|
} catch (DirectoryException | IOException ex) {
|
||||||
throw new AndrolibException("Could not load resources.arsc from file: " + apkFile, ex);
|
throw new AndrolibException("Could not load resources.arsc from file: " + apkFile, ex);
|
||||||
@ -220,8 +223,8 @@ public class ResTable {
|
|||||||
return pkg;
|
return pkg;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResValue getValue(String package_, String type, String name) throws AndrolibException {
|
public ResValue getValue(String pkg, String type, String name) throws AndrolibException {
|
||||||
return getPackage(package_).getType(type).getResSpec(name).getDefaultResource().getValue();
|
return getPackage(pkg).getType(type).getResSpec(name).getDefaultResource().getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addPackage(ResPackage pkg, boolean main) throws AndrolibException {
|
public void addPackage(ResPackage pkg, boolean main) throws AndrolibException {
|
||||||
@ -304,14 +307,14 @@ public class ResTable {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initApkInfo(ApkInfo apkInfo, File outDir) throws AndrolibException {
|
public void initApkInfo(ApkInfo apkInfo, File apkDir) throws AndrolibException {
|
||||||
apkInfo.isFrameworkApk = isFrameworkApk();
|
apkInfo.isFrameworkApk = isFrameworkApk();
|
||||||
apkInfo.usesFramework = getUsesFramework();
|
apkInfo.usesFramework = getUsesFramework();
|
||||||
if (!mApkInfo.sdkInfo.isEmpty()) {
|
if (!mApkInfo.sdkInfo.isEmpty()) {
|
||||||
updateSdkInfoFromResources(outDir);
|
updateSdkInfoFromResources(apkDir);
|
||||||
}
|
}
|
||||||
initPackageInfo();
|
initPackageInfo();
|
||||||
loadVersionName(outDir);
|
loadVersionName(apkDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
private UsesFramework getUsesFramework() {
|
private UsesFramework getUsesFramework() {
|
||||||
@ -327,24 +330,24 @@ public class ResTable {
|
|||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateSdkInfoFromResources(File outDir) {
|
private void updateSdkInfoFromResources(File apkDir) {
|
||||||
String minSdkVersion = mApkInfo.getMinSdkVersion();
|
String minSdkVersion = mApkInfo.getMinSdkVersion();
|
||||||
if (minSdkVersion != null) {
|
if (minSdkVersion != null) {
|
||||||
String refValue = ResXmlPatcher.pullValueFromIntegers(outDir, minSdkVersion);
|
String refValue = ResXmlUtils.pullValueFromIntegers(apkDir, minSdkVersion);
|
||||||
if (refValue != null) {
|
if (refValue != null) {
|
||||||
mApkInfo.setMinSdkVersion(refValue);
|
mApkInfo.setMinSdkVersion(refValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
String targetSdkVersion = mApkInfo.getTargetSdkVersion();
|
String targetSdkVersion = mApkInfo.getTargetSdkVersion();
|
||||||
if (targetSdkVersion != null) {
|
if (targetSdkVersion != null) {
|
||||||
String refValue = ResXmlPatcher.pullValueFromIntegers(outDir, targetSdkVersion);
|
String refValue = ResXmlUtils.pullValueFromIntegers(apkDir, targetSdkVersion);
|
||||||
if (refValue != null) {
|
if (refValue != null) {
|
||||||
mApkInfo.setTargetSdkVersion(refValue);
|
mApkInfo.setTargetSdkVersion(refValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
String maxSdkVersion = mApkInfo.getMaxSdkVersion();
|
String maxSdkVersion = mApkInfo.getMaxSdkVersion();
|
||||||
if (maxSdkVersion != null) {
|
if (maxSdkVersion != null) {
|
||||||
String refValue = ResXmlPatcher.pullValueFromIntegers(outDir, maxSdkVersion);
|
String refValue = ResXmlUtils.pullValueFromIntegers(apkDir, maxSdkVersion);
|
||||||
if (refValue != null) {
|
if (refValue != null) {
|
||||||
mApkInfo.setMaxSdkVersion(refValue);
|
mApkInfo.setMaxSdkVersion(refValue);
|
||||||
}
|
}
|
||||||
@ -371,9 +374,9 @@ public class ResTable {
|
|||||||
mApkInfo.packageInfo.forcedPackageId = String.valueOf(id);
|
mApkInfo.packageInfo.forcedPackageId = String.valueOf(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadVersionName(File outDir) {
|
private void loadVersionName(File apkDir) {
|
||||||
String versionName = mApkInfo.versionInfo.versionName;
|
String versionName = mApkInfo.versionInfo.versionName;
|
||||||
String refValue = ResXmlPatcher.pullValueFromStrings(outDir, versionName);
|
String refValue = ResXmlUtils.pullValueFromStrings(apkDir, versionName);
|
||||||
if (refValue != null) {
|
if (refValue != null) {
|
||||||
mApkInfo.versionInfo.versionName = refValue;
|
mApkInfo.versionInfo.versionName = refValue;
|
||||||
}
|
}
|
||||||
|
@ -22,10 +22,11 @@ import java.util.*;
|
|||||||
|
|
||||||
public class ResType {
|
public class ResType {
|
||||||
private final ResConfigFlags mFlags;
|
private final ResConfigFlags mFlags;
|
||||||
private final Map<ResResSpec, ResResource> mResources = new LinkedHashMap<>();
|
private final Map<ResResSpec, ResResource> mResources;
|
||||||
|
|
||||||
public ResType(ResConfigFlags flags) {
|
public ResType(ResConfigFlags flags) {
|
||||||
this.mFlags = flags;
|
mFlags = flags;
|
||||||
|
mResources = new LinkedHashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResResource getResource(ResResSpec spec) throws AndrolibException {
|
public ResResource getResource(ResResSpec spec) throws AndrolibException {
|
||||||
|
@ -21,7 +21,6 @@ import brut.androlib.exceptions.UndefinedResObjectException;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public final class ResTypeSpec {
|
public final class ResTypeSpec {
|
||||||
|
|
||||||
public static final String RES_TYPE_NAME_ARRAY = "array";
|
public static final String RES_TYPE_NAME_ARRAY = "array";
|
||||||
public static final String RES_TYPE_NAME_ATTR = "attr";
|
public static final String RES_TYPE_NAME_ATTR = "attr";
|
||||||
public static final String RES_TYPE_NAME_ATTR_PRIVATE = "^attr-private";
|
public static final String RES_TYPE_NAME_ATTR_PRIVATE = "^attr-private";
|
||||||
@ -30,13 +29,13 @@ public final class ResTypeSpec {
|
|||||||
public static final String RES_TYPE_NAME_STYLES = "style";
|
public static final String RES_TYPE_NAME_STYLES = "style";
|
||||||
|
|
||||||
private final String mName;
|
private final String mName;
|
||||||
private final Map<String, ResResSpec> mResSpecs = new LinkedHashMap<>();
|
|
||||||
|
|
||||||
private final int mId;
|
private final int mId;
|
||||||
|
private final Map<String, ResResSpec> mResSpecs;
|
||||||
|
|
||||||
public ResTypeSpec(String name, int id) {
|
public ResTypeSpec(String name, int id) {
|
||||||
this.mName = name;
|
mName = name;
|
||||||
this.mId = id;
|
mId = id;
|
||||||
|
mResSpecs = new LinkedHashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
|
@ -24,12 +24,13 @@ public class ResValuesFile {
|
|||||||
private final ResPackage mPackage;
|
private final ResPackage mPackage;
|
||||||
private final ResTypeSpec mType;
|
private final ResTypeSpec mType;
|
||||||
private final ResType mConfig;
|
private final ResType mConfig;
|
||||||
private final Set<ResResource> mResources = new LinkedHashSet<>();
|
private final Set<ResResource> mResources;
|
||||||
|
|
||||||
public ResValuesFile(ResPackage pkg, ResTypeSpec type, ResType config) {
|
public ResValuesFile(ResPackage pkg, ResTypeSpec type, ResType config) {
|
||||||
this.mPackage = pkg;
|
mPackage = pkg;
|
||||||
this.mType = type;
|
mType = type;
|
||||||
this.mConfig = config;
|
mConfig = config;
|
||||||
|
mResources = new LinkedHashSet<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPath() {
|
public String getPath() {
|
||||||
@ -63,17 +64,17 @@ public class ResValuesFile {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
final ResValuesFile other = (ResValuesFile) obj;
|
final ResValuesFile other = (ResValuesFile) obj;
|
||||||
if (!Objects.equals(this.mType, other.mType)) {
|
if (!Objects.equals(mType, other.mType)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return Objects.equals(this.mConfig, other.mConfig);
|
return Objects.equals(mConfig, other.mConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
int hash = 17;
|
int hash = 17;
|
||||||
hash = 31 * hash + (this.mType != null ? this.mType.hashCode() : 0);
|
hash = 31 * hash + (mType != null ? mType.hashCode() : 0);
|
||||||
hash = 31 * hash + (this.mConfig != null ? this.mConfig.hashCode() : 0);
|
hash = 31 * hash + (mConfig != null ? mConfig.hashCode() : 0);
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,11 +22,13 @@ import brut.androlib.res.data.ResPackage;
|
|||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
public class ARSCData {
|
public class ARSCData {
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(ARSCData.class.getName());
|
||||||
|
|
||||||
private final ResPackage[] mPackages;
|
private final ResPackage[] mPackages;
|
||||||
private final FlagsOffset[] mFlagsOffsets;
|
private final FlagsOffset[] mFlagsOffsets;
|
||||||
|
|
||||||
public ARSCData(ResPackage[] packages, FlagsOffset[] flagsOffsets) {
|
public ARSCData(ResPackage[] pkgs, FlagsOffset[] flagsOffsets) {
|
||||||
mPackages = packages;
|
mPackages = pkgs;
|
||||||
mFlagsOffsets = flagsOffsets;
|
mFlagsOffsets = flagsOffsets;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,6 +65,4 @@ public class ARSCData {
|
|||||||
}
|
}
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(ARSCData.class.getName());
|
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
*/
|
*/
|
||||||
package brut.androlib.res.data.arsc;
|
package brut.androlib.res.data.arsc;
|
||||||
|
|
||||||
import brut.util.ExtCountingDataInput;
|
|
||||||
import brut.util.ExtDataInput;
|
import brut.util.ExtDataInput;
|
||||||
|
|
||||||
import java.io.EOFException;
|
import java.io.EOFException;
|
||||||
@ -25,13 +24,40 @@ import java.math.BigInteger;
|
|||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
public class ARSCHeader {
|
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;
|
||||||
|
|
||||||
|
// 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
|
||||||
|
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;
|
||||||
|
|
||||||
public final short type;
|
public final short type;
|
||||||
public final int headerSize;
|
public final int headerSize;
|
||||||
public final int chunkSize;
|
public final int chunkSize;
|
||||||
public final int startPosition;
|
public final long startPosition;
|
||||||
public final int endPosition;
|
public final long endPosition;
|
||||||
|
|
||||||
public ARSCHeader(short type, int headerSize, int chunkSize, int headerStart) {
|
public ARSCHeader(short type, int headerSize, int chunkSize, long headerStart) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.headerSize = headerSize;
|
this.headerSize = headerSize;
|
||||||
this.chunkSize = chunkSize;
|
this.chunkSize = chunkSize;
|
||||||
@ -39,9 +65,9 @@ public class ARSCHeader {
|
|||||||
this.endPosition = headerStart + chunkSize;
|
this.endPosition = headerStart + chunkSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ARSCHeader read(ExtCountingDataInput in) throws IOException {
|
public static ARSCHeader read(ExtDataInput in) throws IOException {
|
||||||
short type;
|
short type;
|
||||||
int start = in.position();
|
long start = in.position();
|
||||||
try {
|
try {
|
||||||
type = in.readShort();
|
type = in.readShort();
|
||||||
} catch (EOFException ex) {
|
} catch (EOFException ex) {
|
||||||
@ -50,13 +76,13 @@ public class ARSCHeader {
|
|||||||
return new ARSCHeader(type, in.readShort(), in.readInt(), start);
|
return new ARSCHeader(type, in.readShort(), in.readInt(), start);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void checkForUnreadHeader(ExtCountingDataInput in) throws IOException {
|
public void checkForUnreadHeader(ExtDataInput in) throws IOException {
|
||||||
// Some applications lie about the reported size of their chunk header. Trusting the chunkSize is misleading
|
// Some applications lie about the reported size of their chunk header. Trusting the chunkSize is misleading
|
||||||
// So compare to what we actually read in the header vs reported and skip the rest.
|
// So compare to what we actually read in the header vs reported and skip the rest.
|
||||||
// However, this runs after each chunk and not every chunk reading has a specific distinction between the
|
// However, this runs after each chunk and not every chunk reading has a specific distinction between the
|
||||||
// header and the body.
|
// header and the body.
|
||||||
int actualHeaderSize = in.position() - this.startPosition;
|
int actualHeaderSize = (int) (in.position() - startPosition);
|
||||||
int exceedingSize = this.headerSize - actualHeaderSize;
|
int exceedingSize = headerSize - actualHeaderSize;
|
||||||
if (exceedingSize > 0) {
|
if (exceedingSize > 0) {
|
||||||
byte[] buf = new byte[exceedingSize];
|
byte[] buf = new byte[exceedingSize];
|
||||||
in.readFully(buf);
|
in.readFully(buf);
|
||||||
@ -64,11 +90,11 @@ public class ARSCHeader {
|
|||||||
|
|
||||||
if (exceedingBI.equals(BigInteger.ZERO)) {
|
if (exceedingBI.equals(BigInteger.ZERO)) {
|
||||||
LOGGER.fine(String.format("Chunk header size (%d), read (%d), but exceeding bytes are all zero.",
|
LOGGER.fine(String.format("Chunk header size (%d), read (%d), but exceeding bytes are all zero.",
|
||||||
this.headerSize, actualHeaderSize
|
headerSize, actualHeaderSize
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
LOGGER.warning(String.format("Chunk header size (%d), read (%d). Exceeding bytes: 0x%X.",
|
LOGGER.warning(String.format("Chunk header size (%d), read (%d). Exceeding bytes: 0x%X.",
|
||||||
this.headerSize, actualHeaderSize, exceedingBI
|
headerSize, actualHeaderSize, exceedingBI
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -77,31 +103,4 @@ public class ARSCHeader {
|
|||||||
public void skipChunk(ExtDataInput in) throws IOException {
|
public void skipChunk(ExtDataInput in) throws IOException {
|
||||||
in.skipBytes(chunkSize - headerSize);
|
in.skipBytes(chunkSize - headerSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final static short RES_NONE_TYPE = -1;
|
|
||||||
public final static short RES_NULL_TYPE = 0x0000;
|
|
||||||
public final static short RES_STRING_POOL_TYPE = 0x0001;
|
|
||||||
public final static short RES_TABLE_TYPE = 0x0002;
|
|
||||||
public final static short RES_XML_TYPE = 0x0003;
|
|
||||||
|
|
||||||
// RES_TABLE_TYPE Chunks
|
|
||||||
public final static short XML_TYPE_PACKAGE = 0x0200;
|
|
||||||
public final static short XML_TYPE_TYPE = 0x0201;
|
|
||||||
public final static short XML_TYPE_SPEC_TYPE = 0x0202;
|
|
||||||
public final static short XML_TYPE_LIBRARY = 0x0203;
|
|
||||||
public final static short XML_TYPE_OVERLAY = 0x0204;
|
|
||||||
public final static short XML_TYPE_OVERLAY_POLICY = 0x0205;
|
|
||||||
public final static short XML_TYPE_STAGED_ALIAS = 0x0206;
|
|
||||||
|
|
||||||
// RES_XML_TYPE Chunks
|
|
||||||
public final static short RES_XML_FIRST_CHUNK_TYPE = 0x0100;
|
|
||||||
public final static short RES_XML_START_NAMESPACE_TYPE = 0x0100;
|
|
||||||
public final static short RES_XML_END_NAMESPACE_TYPE = 0x0101;
|
|
||||||
public final static short RES_XML_START_ELEMENT_TYPE = 0x0102;
|
|
||||||
public final static short RES_XML_END_ELEMENT_TYPE = 0x0103;
|
|
||||||
public final static short RES_XML_CDATA_TYPE = 0x0104;
|
|
||||||
public final static short RES_XML_LAST_CHUNK_TYPE = 0x017f;
|
|
||||||
public final static short RES_XML_RESOURCE_MAP_TYPE = 0x0180;
|
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(ARSCHeader.class.getName());
|
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ package brut.androlib.res.data.arsc;
|
|||||||
import brut.androlib.res.data.value.ResValue;
|
import brut.androlib.res.data.value.ResValue;
|
||||||
|
|
||||||
public class EntryData {
|
public class EntryData {
|
||||||
public short mFlags;
|
public short flags;
|
||||||
public int mSpecNamesId;
|
public int specNamesId;
|
||||||
public ResValue mValue;
|
public ResValue value;
|
||||||
}
|
}
|
||||||
|
@ -31,38 +31,38 @@ package brut.androlib.res.data.axml;
|
|||||||
* !! functions expect 'prefix'+'uri' pairs, not 'uri'+'prefix' !!
|
* !! functions expect 'prefix'+'uri' pairs, not 'uri'+'prefix' !!
|
||||||
*/
|
*/
|
||||||
public final class NamespaceStack {
|
public final class NamespaceStack {
|
||||||
private int[] m_data;
|
private int[] mData;
|
||||||
private int m_dataLength;
|
private int mDataLength;
|
||||||
private int m_depth;
|
private int mDepth;
|
||||||
|
|
||||||
public NamespaceStack() {
|
public NamespaceStack() {
|
||||||
m_data = new int[32];
|
mData = new int[32];
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reset() {
|
public void reset() {
|
||||||
m_dataLength = 0;
|
mDataLength = 0;
|
||||||
m_depth = 0;
|
mDepth = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getCurrentCount() {
|
public int getCurrentCount() {
|
||||||
if (m_dataLength == 0) {
|
if (mDataLength == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
int offset = m_dataLength - 1;
|
int offset = mDataLength - 1;
|
||||||
return m_data[offset];
|
return mData[offset];
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getAccumulatedCount(int depth) {
|
public int getAccumulatedCount(int depth) {
|
||||||
if (m_dataLength == 0 || depth < 0) {
|
if (mDataLength == 0 || depth < 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (depth > m_depth) {
|
if (depth > mDepth) {
|
||||||
depth = m_depth;
|
depth = mDepth;
|
||||||
}
|
}
|
||||||
int accumulatedCount = 0;
|
int accumulatedCount = 0;
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
for (; depth != 0; --depth) {
|
for (; depth != 0; --depth) {
|
||||||
int count = m_data[offset];
|
int count = mData[offset];
|
||||||
accumulatedCount += count;
|
accumulatedCount += count;
|
||||||
offset += (2 + count * 2);
|
offset += (2 + count * 2);
|
||||||
}
|
}
|
||||||
@ -70,34 +70,34 @@ public final class NamespaceStack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void push(int prefix, int uri) {
|
public void push(int prefix, int uri) {
|
||||||
if (m_depth == 0) {
|
if (mDepth == 0) {
|
||||||
increaseDepth();
|
increaseDepth();
|
||||||
}
|
}
|
||||||
ensureDataCapacity(2);
|
ensureDataCapacity(2);
|
||||||
int offset = m_dataLength - 1;
|
int offset = mDataLength - 1;
|
||||||
int count = m_data[offset];
|
int count = mData[offset];
|
||||||
m_data[offset - 1 - count * 2] = count + 1;
|
mData[offset - 1 - count * 2] = count + 1;
|
||||||
m_data[offset] = prefix;
|
mData[offset] = prefix;
|
||||||
m_data[offset + 1] = uri;
|
mData[offset + 1] = uri;
|
||||||
m_data[offset + 2] = count + 1;
|
mData[offset + 2] = count + 1;
|
||||||
m_dataLength += 2;
|
mDataLength += 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean pop() {
|
public boolean pop() {
|
||||||
if (m_dataLength == 0) {
|
if (mDataLength == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
int offset = m_dataLength - 1;
|
int offset = mDataLength - 1;
|
||||||
int count = m_data[offset];
|
int count = mData[offset];
|
||||||
if (count == 0) {
|
if (count == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
count -= 1;
|
count -= 1;
|
||||||
offset -= 2;
|
offset -= 2;
|
||||||
m_data[offset] = count;
|
mData[offset] = count;
|
||||||
offset -= (1 + count * 2);
|
offset -= (1 + count * 2);
|
||||||
m_data[offset] = count;
|
mData[offset] = count;
|
||||||
m_dataLength -= 2;
|
mDataLength -= 2;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,58 +114,58 @@ public final class NamespaceStack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int getDepth() {
|
public int getDepth() {
|
||||||
return m_depth;
|
return mDepth;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void increaseDepth() {
|
public void increaseDepth() {
|
||||||
ensureDataCapacity(2);
|
ensureDataCapacity(2);
|
||||||
int offset = m_dataLength;
|
int offset = mDataLength;
|
||||||
m_data[offset] = 0;
|
mData[offset] = 0;
|
||||||
m_data[offset + 1] = 0;
|
mData[offset + 1] = 0;
|
||||||
m_dataLength += 2;
|
mDataLength += 2;
|
||||||
m_depth += 1;
|
mDepth += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void decreaseDepth() {
|
public void decreaseDepth() {
|
||||||
if (m_dataLength == 0) {
|
if (mDataLength == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int offset = m_dataLength - 1;
|
int offset = mDataLength - 1;
|
||||||
int count = m_data[offset];
|
int count = mData[offset];
|
||||||
if ((offset - 1 - count * 2) == 0) {
|
if ((offset - 1 - count * 2) == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_dataLength -= 2 + count * 2;
|
mDataLength -= 2 + count * 2;
|
||||||
m_depth -= 1;
|
mDepth -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ensureDataCapacity(int capacity) {
|
private void ensureDataCapacity(int capacity) {
|
||||||
int available = (m_data.length - m_dataLength);
|
int available = (mData.length - mDataLength);
|
||||||
if (available > capacity) {
|
if (available > capacity) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int newLength = (m_data.length + available) * 2;
|
int newLength = (mData.length + available) * 2;
|
||||||
int[] newData = new int[newLength];
|
int[] newData = new int[newLength];
|
||||||
System.arraycopy(m_data, 0, newData, 0, m_dataLength);
|
System.arraycopy(mData, 0, newData, 0, mDataLength);
|
||||||
m_data = newData;
|
mData = newData;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int find(int prefixOrUri, boolean prefix) {
|
private int find(int prefixOrUri, boolean prefix) {
|
||||||
if (m_dataLength == 0) {
|
if (mDataLength == 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
int offset = m_dataLength - 1;
|
int offset = mDataLength - 1;
|
||||||
for (int i = m_depth; i != 0; --i) {
|
for (int i = mDepth; i != 0; --i) {
|
||||||
int count = m_data[offset];
|
int count = mData[offset];
|
||||||
offset -= 2;
|
offset -= 2;
|
||||||
for (; count != 0; --count) {
|
for (; count != 0; --count) {
|
||||||
if (prefix) {
|
if (prefix) {
|
||||||
if (m_data[offset] == prefixOrUri) {
|
if (mData[offset] == prefixOrUri) {
|
||||||
return m_data[offset + 1];
|
return mData[offset + 1];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (m_data[offset + 1] == prefixOrUri) {
|
if (mData[offset + 1] == prefixOrUri) {
|
||||||
return m_data[offset];
|
return mData[offset];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
offset -= 2;
|
offset -= 2;
|
||||||
@ -175,12 +175,12 @@ public final class NamespaceStack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private int get(int index, boolean prefix) {
|
private int get(int index, boolean prefix) {
|
||||||
if (m_dataLength == 0 || index < 0) {
|
if (mDataLength == 0 || index < 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
for (int i = m_depth; i != 0; --i) {
|
for (int i = mDepth; i != 0; --i) {
|
||||||
int count = m_data[offset];
|
int count = mData[offset];
|
||||||
if (index >= count) {
|
if (index >= count) {
|
||||||
index -= count;
|
index -= count;
|
||||||
offset += (2 + count * 2);
|
offset += (2 + count * 2);
|
||||||
@ -190,7 +190,7 @@ public final class NamespaceStack {
|
|||||||
if (!prefix) {
|
if (!prefix) {
|
||||||
offset += 1;
|
offset += 1;
|
||||||
}
|
}
|
||||||
return m_data[offset];
|
return mData[offset];
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package brut.androlib.res.data.ninepatch;
|
package brut.androlib.res.data.ninepatch;
|
||||||
|
|
||||||
import brut.util.ExtDataInput;
|
import brut.util.ExtDataInput;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
public class NinePatchData {
|
public class NinePatchData {
|
||||||
@ -32,19 +33,19 @@ public class NinePatchData {
|
|||||||
this.yDivs = yDivs;
|
this.yDivs = yDivs;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static NinePatchData decode(ExtDataInput di) throws IOException {
|
public static NinePatchData decode(ExtDataInput in) throws IOException {
|
||||||
di.skipBytes(1); // wasDeserialized
|
in.skipBytes(1); // wasDeserialized
|
||||||
byte numXDivs = di.readByte();
|
byte numXDivs = in.readByte();
|
||||||
byte numYDivs = di.readByte();
|
byte numYDivs = in.readByte();
|
||||||
di.skipBytes(1); // numColors
|
in.skipBytes(1); // numColors
|
||||||
di.skipBytes(8); // xDivs/yDivs offset
|
in.skipBytes(8); // xDivs/yDivs offset
|
||||||
int padLeft = di.readInt();
|
int padLeft = in.readInt();
|
||||||
int padRight = di.readInt();
|
int padRight = in.readInt();
|
||||||
int padTop = di.readInt();
|
int padTop = in.readInt();
|
||||||
int padBottom = di.readInt();
|
int padBottom = in.readInt();
|
||||||
di.skipBytes(4); // colorsOffset
|
in.skipBytes(4); // colorsOffset
|
||||||
int[] xDivs = di.readIntArray(numXDivs);
|
int[] xDivs = in.readIntArray(numXDivs);
|
||||||
int[] yDivs = di.readIntArray(numYDivs);
|
int[] yDivs = in.readIntArray(numYDivs);
|
||||||
|
|
||||||
return new NinePatchData(padLeft, padRight, padTop, padBottom, xDivs, yDivs);
|
return new NinePatchData(padLeft, padRight, padTop, padBottom, xDivs, yDivs);
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package brut.androlib.res.data.ninepatch;
|
package brut.androlib.res.data.ninepatch;
|
||||||
|
|
||||||
import brut.util.ExtDataInput;
|
import brut.util.ExtDataInput;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
public class OpticalInset {
|
public class OpticalInset {
|
||||||
@ -29,11 +30,11 @@ public class OpticalInset {
|
|||||||
this.layoutBoundsBottom = layoutBoundsBottom;
|
this.layoutBoundsBottom = layoutBoundsBottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static OpticalInset decode(ExtDataInput di) throws IOException {
|
public static OpticalInset decode(ExtDataInput in) throws IOException {
|
||||||
int layoutBoundsLeft = Integer.reverseBytes(di.readInt());
|
int layoutBoundsLeft = Integer.reverseBytes(in.readInt());
|
||||||
int layoutBoundsTop = Integer.reverseBytes(di.readInt());
|
int layoutBoundsTop = Integer.reverseBytes(in.readInt());
|
||||||
int layoutBoundsRight = Integer.reverseBytes(di.readInt());
|
int layoutBoundsRight = Integer.reverseBytes(in.readInt());
|
||||||
int layoutBoundsBottom = Integer.reverseBytes(di.readInt());
|
int layoutBoundsBottom = Integer.reverseBytes(in.readInt());
|
||||||
return new OpticalInset(layoutBoundsLeft, layoutBoundsTop, layoutBoundsRight, layoutBoundsBottom);
|
return new OpticalInset(layoutBoundsLeft, layoutBoundsTop, layoutBoundsRight, layoutBoundsBottom);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,15 +20,19 @@ import brut.androlib.exceptions.AndrolibException;
|
|||||||
import brut.androlib.res.data.ResResource;
|
import brut.androlib.res.data.ResResource;
|
||||||
import brut.androlib.res.xml.ResValuesXmlSerializable;
|
import brut.androlib.res.xml.ResValuesXmlSerializable;
|
||||||
import brut.util.Duo;
|
import brut.util.Duo;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
import org.xmlpull.v1.XmlSerializer;
|
import org.xmlpull.v1.XmlSerializer;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Set;
|
||||||
|
|
||||||
public class ResArrayValue extends ResBagValue implements ResValuesXmlSerializable {
|
public class ResArrayValue extends ResBagValue implements ResValuesXmlSerializable {
|
||||||
|
private static final Set<String> ALLOWED_ARRAY_TYPES = Sets.newHashSet("string", "integer");
|
||||||
|
|
||||||
|
private final ResScalarValue[] mItems;
|
||||||
|
|
||||||
ResArrayValue(ResReferenceValue parent, Duo<Integer, ResScalarValue>[] items) {
|
ResArrayValue(ResReferenceValue parent, Duo<Integer, ResScalarValue>[] items) {
|
||||||
super(parent);
|
super(parent);
|
||||||
|
|
||||||
mItems = new ResScalarValue[items.length];
|
mItems = new ResScalarValue[items.length];
|
||||||
for (int i = 0; i < items.length; i++) {
|
for (int i = 0; i < items.length; i++) {
|
||||||
mItems[i] = items[i].m2;
|
mItems[i] = items[i].m2;
|
||||||
@ -83,12 +87,9 @@ public class ResArrayValue extends ResBagValue implements ResValuesXmlSerializab
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!Arrays.asList(AllowedArrayTypes).contains(type)) {
|
if (!ALLOWED_ARRAY_TYPES.contains(type)) {
|
||||||
return "string";
|
return "string";
|
||||||
}
|
}
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final ResScalarValue[] mItems;
|
|
||||||
private final String[] AllowedArrayTypes = {"string", "integer"};
|
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,28 @@ import org.xmlpull.v1.XmlSerializer;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
public class ResAttr extends ResBagValue implements ResValuesXmlSerializable {
|
public class ResAttr extends ResBagValue implements ResValuesXmlSerializable {
|
||||||
|
private static final int BAG_KEY_ATTR_MIN = 0x01000001;
|
||||||
|
private static final int BAG_KEY_ATTR_MAX = 0x01000002;
|
||||||
|
private static final int BAG_KEY_ATTR_L10N = 0x01000003;
|
||||||
|
|
||||||
|
private static final int TYPE_REFERENCE = 0x01;
|
||||||
|
private static final int TYPE_STRING = 0x02;
|
||||||
|
private static final int TYPE_INT = 0x04;
|
||||||
|
private static final int TYPE_BOOL = 0x08;
|
||||||
|
private static final int TYPE_COLOR = 0x10;
|
||||||
|
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_ENUM = 0x00010000;
|
||||||
|
private static final int TYPE_FLAGS = 0x00020000;
|
||||||
|
|
||||||
|
private final int mType;
|
||||||
|
private final Integer mMin;
|
||||||
|
private final Integer mMax;
|
||||||
|
private final Boolean mL10n;
|
||||||
|
|
||||||
ResAttr(ResReferenceValue parentVal, int type, Integer min, Integer max, Boolean l10n) {
|
ResAttr(ResReferenceValue parentVal, int type, Integer min, Integer max, Boolean l10n) {
|
||||||
super(parentVal);
|
super(parentVal);
|
||||||
mType = type;
|
mType = type;
|
||||||
@ -139,26 +161,4 @@ public class ResAttr extends ResBagValue implements ResValuesXmlSerializable {
|
|||||||
}
|
}
|
||||||
return s.substring(1);
|
return s.substring(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final int mType;
|
|
||||||
private final Integer mMin;
|
|
||||||
private final Integer mMax;
|
|
||||||
private final Boolean mL10n;
|
|
||||||
|
|
||||||
private static final int BAG_KEY_ATTR_MIN = 0x01000001;
|
|
||||||
private static final int BAG_KEY_ATTR_MAX = 0x01000002;
|
|
||||||
private static final int BAG_KEY_ATTR_L10N = 0x01000003;
|
|
||||||
|
|
||||||
private final static int TYPE_REFERENCE = 0x01;
|
|
||||||
private final static int TYPE_STRING = 0x02;
|
|
||||||
private final static int TYPE_INT = 0x04;
|
|
||||||
private final static int TYPE_BOOL = 0x08;
|
|
||||||
private final static int TYPE_COLOR = 0x10;
|
|
||||||
private final static int TYPE_FLOAT = 0x20;
|
|
||||||
private final static int TYPE_DIMEN = 0x40;
|
|
||||||
private final static int TYPE_FRACTION = 0x80;
|
|
||||||
private final static int TYPE_ANY_STRING = 0xee;
|
|
||||||
|
|
||||||
private static final int TYPE_ENUM = 0x00010000;
|
|
||||||
private static final int TYPE_FLAGS = 0x00020000;
|
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ public class ResBagValue extends ResValue implements ResValuesXmlSerializable {
|
|||||||
protected final ResReferenceValue mParent;
|
protected final ResReferenceValue mParent;
|
||||||
|
|
||||||
public ResBagValue(ResReferenceValue parent) {
|
public ResBagValue(ResReferenceValue parent) {
|
||||||
this.mParent = parent;
|
mParent = parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -21,7 +21,7 @@ public class ResBoolValue extends ResScalarValue {
|
|||||||
|
|
||||||
public ResBoolValue(boolean value, int rawIntValue, String rawValue) {
|
public ResBoolValue(boolean value, int rawIntValue, String rawValue) {
|
||||||
super("bool", rawIntValue, rawValue);
|
super("bool", rawIntValue, rawValue);
|
||||||
this.mValue = value;
|
mValue = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getValue() {
|
public boolean getValue() {
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package brut.androlib.res.data.value;
|
package brut.androlib.res.data.value;
|
||||||
|
|
||||||
public class ResColorValue extends ResIntValue {
|
public class ResColorValue extends ResIntValue {
|
||||||
|
|
||||||
public ResColorValue(int value, String rawValue) {
|
public ResColorValue(int value, String rawValue) {
|
||||||
super(value, rawValue, "color");
|
super(value, rawValue, "color");
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import android.util.TypedValue;
|
|||||||
import brut.androlib.exceptions.AndrolibException;
|
import brut.androlib.exceptions.AndrolibException;
|
||||||
|
|
||||||
public class ResDimenValue extends ResIntValue {
|
public class ResDimenValue extends ResIntValue {
|
||||||
|
|
||||||
public ResDimenValue(int value, String rawValue) {
|
public ResDimenValue(int value, String rawValue) {
|
||||||
super(value, rawValue, "dimen");
|
super(value, rawValue, "dimen");
|
||||||
}
|
}
|
||||||
|
@ -20,18 +20,18 @@ import brut.androlib.exceptions.AndrolibException;
|
|||||||
|
|
||||||
public class ResEmptyValue extends ResScalarValue {
|
public class ResEmptyValue extends ResScalarValue {
|
||||||
protected final int mValue;
|
protected final int mValue;
|
||||||
protected int type;
|
protected int mType;
|
||||||
|
|
||||||
public ResEmptyValue(int value, String rawValue, int type) {
|
public ResEmptyValue(int value, String rawValue, int type) {
|
||||||
this(value, rawValue, "integer");
|
this(value, rawValue, "integer");
|
||||||
this.type = type;
|
mType = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResEmptyValue(int value, String rawValue, String type) {
|
public ResEmptyValue(int value, String rawValue, String type) {
|
||||||
super(type, value, rawValue);
|
super(type, value, rawValue);
|
||||||
if (value != 1)
|
if (value != 1)
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
this.mValue = value;
|
mValue = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getValue() {
|
public int getValue() {
|
||||||
|
@ -28,10 +28,16 @@ import java.util.Map;
|
|||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
public class ResEnumAttr extends ResAttr {
|
public class ResEnumAttr extends ResAttr {
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(ResEnumAttr.class.getName());
|
||||||
|
|
||||||
|
private final Duo<ResReferenceValue, ResScalarValue>[] mItems;
|
||||||
|
private final Map<Integer, String> mItemsCache;
|
||||||
|
|
||||||
ResEnumAttr(ResReferenceValue parent, int type, Integer min, Integer max,
|
ResEnumAttr(ResReferenceValue parent, int type, Integer min, Integer max,
|
||||||
Boolean l10n, Duo<ResReferenceValue, ResScalarValue>[] items) {
|
Boolean l10n, Duo<ResReferenceValue, ResScalarValue>[] items) {
|
||||||
super(parent, type, min, max, l10n);
|
super(parent, type, min, max, l10n);
|
||||||
mItems = items;
|
mItems = items;
|
||||||
|
mItemsCache = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -85,9 +91,4 @@ public class ResEnumAttr extends ResAttr {
|
|||||||
}
|
}
|
||||||
return value2;
|
return value2;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Duo<ResReferenceValue, ResScalarValue>[] mItems;
|
|
||||||
private final Map<Integer, String> mItemsCache = new HashMap<>();
|
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(ResEnumAttr.class.getName());
|
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ public class ResFileValue extends ResIntBasedValue {
|
|||||||
|
|
||||||
public ResFileValue(String path, int rawIntValue) {
|
public ResFileValue(String path, int rawIntValue) {
|
||||||
super(rawIntValue);
|
super(rawIntValue);
|
||||||
this.mPath = path;
|
mPath = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getStrippedPath() throws AndrolibException {
|
public String getStrippedPath() throws AndrolibException {
|
||||||
|
@ -28,6 +28,12 @@ import java.util.Arrays;
|
|||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
public class ResFlagsAttr extends ResAttr {
|
public class ResFlagsAttr extends ResAttr {
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(ResFlagsAttr.class.getName());
|
||||||
|
|
||||||
|
private final FlagItem[] mItems;
|
||||||
|
private FlagItem[] mZeroFlags;
|
||||||
|
private FlagItem[] mFlags;
|
||||||
|
|
||||||
ResFlagsAttr(ResReferenceValue parent, int type, Integer min, Integer max,
|
ResFlagsAttr(ResReferenceValue parent, int type, Integer min, Integer max,
|
||||||
Boolean l10n, Duo<ResReferenceValue, ResScalarValue>[] items) {
|
Boolean l10n, Duo<ResReferenceValue, ResScalarValue>[] items) {
|
||||||
super(parent, type, min, max, l10n);
|
super(parent, type, min, max, l10n);
|
||||||
@ -133,11 +139,4 @@ public class ResFlagsAttr extends ResAttr {
|
|||||||
|
|
||||||
Arrays.sort(mFlags, (o1, o2) -> Integer.compare(Integer.bitCount(o2.flag), Integer.bitCount(o1.flag)));
|
Arrays.sort(mFlags, (o1, o2) -> Integer.compare(Integer.bitCount(o2.flag), Integer.bitCount(o1.flag)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private final FlagItem[] mItems;
|
|
||||||
|
|
||||||
private FlagItem[] mZeroFlags;
|
|
||||||
private FlagItem[] mFlags;
|
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(ResFlagsAttr.class.getName());
|
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ public class ResFloatValue extends ResScalarValue {
|
|||||||
|
|
||||||
public ResFloatValue(float value, int rawIntValue, String rawValue) {
|
public ResFloatValue(float value, int rawIntValue, String rawValue) {
|
||||||
super("float", rawIntValue, rawValue);
|
super("float", rawIntValue, rawValue);
|
||||||
this.mValue = value;
|
mValue = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float getValue() {
|
public float getValue() {
|
||||||
|
@ -20,6 +20,7 @@ import android.util.TypedValue;
|
|||||||
import brut.androlib.exceptions.AndrolibException;
|
import brut.androlib.exceptions.AndrolibException;
|
||||||
|
|
||||||
public class ResFractionValue extends ResIntValue {
|
public class ResFractionValue extends ResIntValue {
|
||||||
|
|
||||||
public ResFractionValue(int value, String rawValue) {
|
public ResFractionValue(int value, String rawValue) {
|
||||||
super(value, rawValue, "fraction");
|
super(value, rawValue, "fraction");
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ import org.xmlpull.v1.XmlSerializer;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
public class ResIdValue extends ResValue implements ResValuesXmlSerializable {
|
public class ResIdValue extends ResValue implements ResValuesXmlSerializable {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void serializeToResValuesXml(XmlSerializer serializer, ResResource res) throws IOException {
|
public void serializeToResValuesXml(XmlSerializer serializer, ResResource res) throws IOException {
|
||||||
serializer.startTag(null, "item");
|
serializer.startTag(null, "item");
|
||||||
|
@ -21,16 +21,16 @@ import brut.androlib.exceptions.AndrolibException;
|
|||||||
|
|
||||||
public class ResIntValue extends ResScalarValue {
|
public class ResIntValue extends ResScalarValue {
|
||||||
protected final int mValue;
|
protected final int mValue;
|
||||||
private int type;
|
private int mType;
|
||||||
|
|
||||||
public ResIntValue(int value, String rawValue, int type) {
|
public ResIntValue(int value, String rawValue, int type) {
|
||||||
this(value, rawValue, "integer");
|
this(value, rawValue, "integer");
|
||||||
this.type = type;
|
mType = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResIntValue(int value, String rawValue, String type) {
|
public ResIntValue(int value, String rawValue, String type) {
|
||||||
super(type, value, rawValue);
|
super(type, value, rawValue);
|
||||||
this.mValue = value;
|
mValue = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getValue() {
|
public int getValue() {
|
||||||
@ -39,6 +39,6 @@ public class ResIntValue extends ResScalarValue {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String encodeAsResXml() throws AndrolibException {
|
protected String encodeAsResXml() throws AndrolibException {
|
||||||
return TypedValue.coerceToString(type, mValue);
|
return TypedValue.coerceToString(mType, mValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,9 +26,14 @@ import org.xmlpull.v1.XmlSerializer;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
public class ResPluralsValue extends ResBagValue implements ResValuesXmlSerializable {
|
public class ResPluralsValue extends ResBagValue implements ResValuesXmlSerializable {
|
||||||
|
private static final String[] QUANTITY_MAP = { "other", "zero", "one", "two", "few", "many" };
|
||||||
|
|
||||||
|
private static final int BAG_KEY_PLURALS_START = 0x01000004;
|
||||||
|
|
||||||
|
private final ResScalarValue[] mItems;
|
||||||
|
|
||||||
ResPluralsValue(ResReferenceValue parent, Duo<Integer, ResScalarValue>[] items) {
|
ResPluralsValue(ResReferenceValue parent, Duo<Integer, ResScalarValue>[] items) {
|
||||||
super(parent);
|
super(parent);
|
||||||
|
|
||||||
mItems = new ResScalarValue[6];
|
mItems = new ResScalarValue[6];
|
||||||
for (Duo<Integer, ResScalarValue> item : items) {
|
for (Duo<Integer, ResScalarValue> item : items) {
|
||||||
mItems[item.m1 - BAG_KEY_PLURALS_START] = item.m2;
|
mItems[item.m1 - BAG_KEY_PLURALS_START] = item.m2;
|
||||||
@ -53,9 +58,4 @@ public class ResPluralsValue extends ResBagValue implements ResValuesXmlSerializ
|
|||||||
}
|
}
|
||||||
serializer.endTag(null, "plurals");
|
serializer.endTag(null, "plurals");
|
||||||
}
|
}
|
||||||
|
|
||||||
private final ResScalarValue[] mItems;
|
|
||||||
|
|
||||||
public static final int BAG_KEY_PLURALS_START = 0x01000004;
|
|
||||||
private static final String[] QUANTITY_MAP = new String[] { "other", "zero", "one", "two", "few", "many" };
|
|
||||||
}
|
}
|
||||||
|
@ -25,14 +25,13 @@ public class ResReferenceValue extends ResIntValue {
|
|||||||
private final ResPackage mPackage;
|
private final ResPackage mPackage;
|
||||||
private final boolean mTheme;
|
private final boolean mTheme;
|
||||||
|
|
||||||
public ResReferenceValue(ResPackage package_, int value, String rawValue) {
|
public ResReferenceValue(ResPackage pkg, int value, String rawValue) {
|
||||||
this(package_, value, rawValue, false);
|
this(pkg, value, rawValue, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResReferenceValue(ResPackage package_, int value, String rawValue,
|
public ResReferenceValue(ResPackage pkg, int value, String rawValue, boolean theme) {
|
||||||
boolean theme) {
|
|
||||||
super(value, rawValue, "reference");
|
super(value, rawValue, "reference");
|
||||||
mPackage = package_;
|
mPackage = pkg;
|
||||||
mTheme = theme;
|
mTheme = theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,8 +115,9 @@ public abstract class ResScalarValue extends ResIntBasedValue implements
|
|||||||
return mType;
|
return mType;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void serializeExtraXmlAttrs(XmlSerializer serializer,
|
protected void serializeExtraXmlAttrs(XmlSerializer serializer, ResResource res)
|
||||||
ResResource res) throws IOException {
|
throws IOException {
|
||||||
|
// stub
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract String encodeAsResXml() throws AndrolibException;
|
protected abstract String encodeAsResXml() throws AndrolibException;
|
||||||
|
@ -25,6 +25,8 @@ import java.io.IOException;
|
|||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class ResStringValue extends ResScalarValue {
|
public class ResStringValue extends ResScalarValue {
|
||||||
|
private static final Pattern ALL_DIGITS = Pattern.compile("\\d{9,}");
|
||||||
|
|
||||||
public ResStringValue(String value, int rawValue) {
|
public ResStringValue(String value, int rawValue) {
|
||||||
this(value, rawValue, "string");
|
this(value, rawValue, "string");
|
||||||
}
|
}
|
||||||
@ -64,8 +66,6 @@ public class ResStringValue extends ResScalarValue {
|
|||||||
if (val == null || val.isEmpty()) {
|
if (val == null || val.isEmpty()) {
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
return allDigits.matcher(val).matches() ? "\\ " + val : val;
|
return ALL_DIGITS.matcher(val).matches() ? "\\ " + val : val;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Pattern allDigits = Pattern.compile("\\d{9,}");
|
|
||||||
}
|
}
|
||||||
|
@ -29,9 +29,12 @@ import java.util.Set;
|
|||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
public class ResStyleValue extends ResBagValue implements ResValuesXmlSerializable {
|
public class ResStyleValue extends ResBagValue implements ResValuesXmlSerializable {
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(ResStyleValue.class.getName());
|
||||||
|
|
||||||
|
private final Duo<ResReferenceValue, ResScalarValue>[] mItems;
|
||||||
|
|
||||||
ResStyleValue(ResReferenceValue parent, Duo<Integer, ResScalarValue>[] items, ResValueFactory factory) {
|
ResStyleValue(ResReferenceValue parent, Duo<Integer, ResScalarValue>[] items, ResValueFactory factory) {
|
||||||
super(parent);
|
super(parent);
|
||||||
|
|
||||||
mItems = new Duo[items.length];
|
mItems = new Duo[items.length];
|
||||||
for (int i = 0; i < items.length; i++) {
|
for (int i = 0; i < items.length; i++) {
|
||||||
mItems[i] = new Duo<>(
|
mItems[i] = new Duo<>(
|
||||||
@ -97,8 +100,4 @@ public class ResStyleValue extends ResBagValue implements ResValuesXmlSerializab
|
|||||||
serializer.endTag(null, "style");
|
serializer.endTag(null, "style");
|
||||||
processedNames.clear();
|
processedNames.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Duo<ResReferenceValue, ResScalarValue>[] mItems;
|
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(ResStyleValue.class.getName());
|
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ package brut.androlib.res.data.value;
|
|||||||
import brut.androlib.Config;
|
import brut.androlib.Config;
|
||||||
|
|
||||||
public class ResValue {
|
public class ResValue {
|
||||||
|
|
||||||
public boolean shouldRemoveUnknownRes() {
|
public boolean shouldRemoveUnknownRes() {
|
||||||
return Config.getInstance().isDecodeResolveModeRemoving();
|
return Config.getInstance().isDecodeResolveModeRemoving();
|
||||||
}
|
}
|
||||||
|
@ -25,8 +25,8 @@ import brut.util.Duo;
|
|||||||
public class ResValueFactory {
|
public class ResValueFactory {
|
||||||
private final ResPackage mPackage;
|
private final ResPackage mPackage;
|
||||||
|
|
||||||
public ResValueFactory(ResPackage package_) {
|
public ResValueFactory(ResPackage pkg) {
|
||||||
this.mPackage = package_;
|
mPackage = pkg;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResScalarValue factory(int type, int value, String rawValue) throws AndrolibException {
|
public ResScalarValue factory(int type, int value, String rawValue) throws AndrolibException {
|
||||||
|
@ -22,8 +22,7 @@ import brut.androlib.res.data.*;
|
|||||||
import brut.androlib.res.data.arsc.*;
|
import brut.androlib.res.data.arsc.*;
|
||||||
import brut.androlib.res.data.value.*;
|
import brut.androlib.res.data.value.*;
|
||||||
import brut.util.Duo;
|
import brut.util.Duo;
|
||||||
import brut.util.ExtCountingDataInput;
|
import brut.util.ExtDataInputStream;
|
||||||
import com.google.common.io.LittleEndianDataInputStream;
|
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
@ -31,37 +30,57 @@ import java.util.*;
|
|||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
public class ARSCDecoder {
|
public class ARSCDecoder {
|
||||||
public static ARSCData decode(InputStream arscStream, boolean findFlagsOffsets, boolean keepBroken)
|
private static final Logger LOGGER = Logger.getLogger(ARSCDecoder.class.getName());
|
||||||
throws AndrolibException {
|
|
||||||
return decode(arscStream, findFlagsOffsets, keepBroken, new ResTable());
|
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 short TABLE_TYPE_FLAG_SPARSE = 0x01;
|
||||||
|
private static final short TABLE_TYPE_FLAG_OFFSET16 = 0x02;
|
||||||
|
|
||||||
|
private static final int KNOWN_CONFIG_BYTES = 64;
|
||||||
|
|
||||||
|
private static final int NO_ENTRY = 0xFFFFFFFF;
|
||||||
|
private static final int NO_ENTRY_OFFSET16 = 0xFFFF;
|
||||||
|
|
||||||
|
private final ExtDataInputStream mIn;
|
||||||
|
private final ResTable mResTable;
|
||||||
|
private final List<FlagsOffset> mFlagsOffsets;
|
||||||
|
private final boolean mKeepBroken;
|
||||||
|
private final HashMap<Integer, Integer> mMissingResSpecMap;
|
||||||
|
private final HashMap<Integer, ResTypeSpec> mResTypeSpecs;
|
||||||
|
|
||||||
|
private ARSCHeader mHeader;
|
||||||
|
private StringBlock mTableStrings;
|
||||||
|
private StringBlock mTypeNames;
|
||||||
|
private StringBlock mSpecNames;
|
||||||
|
private ResPackage mPkg;
|
||||||
|
private ResTypeSpec mTypeSpec;
|
||||||
|
private ResType mType;
|
||||||
|
private int mResId;
|
||||||
|
private int mTypeIdOffset;
|
||||||
|
|
||||||
|
public ARSCDecoder(InputStream in, ResTable resTable, boolean storeFlagsOffsets, boolean keepBroken) {
|
||||||
|
mIn = ExtDataInputStream.littleEndian(in);
|
||||||
|
mResTable = resTable != null ? resTable : new ResTable();
|
||||||
|
mFlagsOffsets = storeFlagsOffsets ? new ArrayList<>() : null;
|
||||||
|
mKeepBroken = keepBroken;
|
||||||
|
mMissingResSpecMap = new LinkedHashMap<>();
|
||||||
|
mResTypeSpecs = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ARSCData decode(InputStream arscStream, boolean findFlagsOffsets, boolean keepBroken,
|
public ARSCData decode() throws AndrolibException {
|
||||||
ResTable resTable)
|
|
||||||
throws AndrolibException {
|
|
||||||
try {
|
try {
|
||||||
ARSCDecoder decoder = new ARSCDecoder(arscStream, resTable, findFlagsOffsets, keepBroken);
|
ResPackage[] pkgs = readResourceTable();
|
||||||
ResPackage[] pkgs = decoder.readResourceTable();
|
FlagsOffset[] flagsOffsets = mFlagsOffsets != null ? mFlagsOffsets.toArray(new FlagsOffset[0]) : null;
|
||||||
return new ARSCData(pkgs, decoder.mFlagsOffsets == null
|
return new ARSCData(pkgs, flagsOffsets);
|
||||||
? null
|
|
||||||
: decoder.mFlagsOffsets.toArray(new FlagsOffset[0]));
|
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
throw new AndrolibException("Could not decode arsc file", ex);
|
throw new AndrolibException("Could not decode arsc file", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ARSCDecoder(InputStream arscStream, ResTable resTable, boolean storeFlagsOffsets, boolean keepBroken) {
|
|
||||||
if (storeFlagsOffsets) {
|
|
||||||
mFlagsOffsets = new ArrayList<>();
|
|
||||||
} else {
|
|
||||||
mFlagsOffsets = null;
|
|
||||||
}
|
|
||||||
mIn = new ExtCountingDataInput(new LittleEndianDataInputStream(arscStream));
|
|
||||||
mResTable = resTable;
|
|
||||||
mKeepBroken = keepBroken;
|
|
||||||
mMissingResSpecMap = new LinkedHashMap<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private ResPackage[] readResourceTable() throws IOException, AndrolibException {
|
private ResPackage[] readResourceTable() throws IOException, AndrolibException {
|
||||||
Set<ResPackage> pkgs = new LinkedHashSet<>();
|
Set<ResPackage> pkgs = new LinkedHashSet<>();
|
||||||
ResTypeSpec typeSpec;
|
ResTypeSpec typeSpec;
|
||||||
@ -170,7 +189,7 @@ public class ARSCDecoder {
|
|||||||
|
|
||||||
// TypeIdOffset was added platform_frameworks_base/@f90f2f8dc36e7243b85e0b6a7fd5a590893c827e
|
// TypeIdOffset was added platform_frameworks_base/@f90f2f8dc36e7243b85e0b6a7fd5a590893c827e
|
||||||
// which is only in split/new applications.
|
// which is only in split/new applications.
|
||||||
int splitHeaderSize = (2 + 2 + 4 + 4 + (2 * 128) + (4 * 5)); // short, short, int, int, char[128], int * 4
|
int splitHeaderSize = 2 + 2 + 4 + 4 + (2 * 128) + (4 * 5); // short, short, int, int, char[128], int * 4
|
||||||
if (mHeader.headerSize == splitHeaderSize) {
|
if (mHeader.headerSize == splitHeaderSize) {
|
||||||
mTypeIdOffset = mIn.readInt();
|
mTypeIdOffset = mIn.readInt();
|
||||||
}
|
}
|
||||||
@ -246,7 +265,7 @@ public class ARSCDecoder {
|
|||||||
int entryCount = mIn.readInt();
|
int entryCount = mIn.readInt();
|
||||||
|
|
||||||
if (mFlagsOffsets != null) {
|
if (mFlagsOffsets != null) {
|
||||||
mFlagsOffsets.add(new FlagsOffset(mIn.position(), entryCount));
|
mFlagsOffsets.add(new FlagsOffset((int) mIn.position(), entryCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
mHeader.checkForUnreadHeader(mIn);
|
mHeader.checkForUnreadHeader(mIn);
|
||||||
@ -310,11 +329,11 @@ public class ARSCDecoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mType = flags.isInvalid && !mKeepBroken ? null : mPkg.getOrCreateConfig(flags);
|
mType = !flags.isInvalid || mKeepBroken ? mPkg.getOrCreateConfig(flags) : null;
|
||||||
int noEntry = isOffset16 ? NO_ENTRY_OFFSET16 : NO_ENTRY;
|
int noEntry = isOffset16 ? NO_ENTRY_OFFSET16 : NO_ENTRY;
|
||||||
|
|
||||||
// #3428 - In some applications the res entries are padded for alignment.
|
// #3428 - In some applications the res entries are padded for alignment.
|
||||||
int entriesStartAligned = mHeader.startPosition + entriesStart;
|
long entriesStartAligned = mHeader.startPosition + entriesStart;
|
||||||
if (mIn.position() < entriesStartAligned) {
|
if (mIn.position() < entriesStartAligned) {
|
||||||
long bytesSkipped = mIn.skip(entriesStartAligned - mIn.position());
|
long bytesSkipped = mIn.skip(entriesStartAligned - mIn.position());
|
||||||
LOGGER.fine(String.format("Skipping: %d byte(s) to align with ResTable_entry start.", bytesSkipped));
|
LOGGER.fine(String.format("Skipping: %d byte(s) to align with ResTable_entry start.", bytesSkipped));
|
||||||
@ -394,15 +413,15 @@ public class ARSCDecoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
EntryData entryData = new EntryData();
|
EntryData entryData = new EntryData();
|
||||||
entryData.mFlags = flags;
|
entryData.flags = flags;
|
||||||
entryData.mSpecNamesId = specNamesId;
|
entryData.specNamesId = specNamesId;
|
||||||
entryData.mValue = value;
|
entryData.value = value;
|
||||||
return entryData;
|
return entryData;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readEntry(EntryData entryData) throws AndrolibException {
|
private void readEntry(EntryData entryData) throws AndrolibException {
|
||||||
int specNamesId = entryData.mSpecNamesId;
|
int specNamesId = entryData.specNamesId;
|
||||||
ResValue value = entryData.mValue;
|
ResValue value = entryData.value;
|
||||||
|
|
||||||
if (mTypeSpec.isString() && value instanceof ResFileValue) {
|
if (mTypeSpec.isString() && value instanceof ResFileValue) {
|
||||||
value = new ResStringValue(value.toString(), ((ResFileValue) value).getRawIntValue());
|
value = new ResStringValue(value.toString(), ((ResFileValue) value).getRawIntValue());
|
||||||
@ -481,8 +500,8 @@ public class ARSCDecoder {
|
|||||||
int data = mIn.readInt();
|
int data = mIn.readInt();
|
||||||
|
|
||||||
return type == TypedValue.TYPE_STRING
|
return type == TypedValue.TYPE_STRING
|
||||||
? mPkg.getValueFactory().factory(mTableStrings.getHTML(data), data)
|
? mPkg.getValueFactory().factory(mTableStrings.getHTML(data), data)
|
||||||
: mPkg.getValueFactory().factory(type, data, null);
|
: mPkg.getValueFactory().factory(type, data, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ResConfigFlags readConfigFlags() throws IOException, AndrolibException {
|
private ResConfigFlags readConfigFlags() throws IOException, AndrolibException {
|
||||||
@ -684,36 +703,4 @@ public class ARSCDecoder {
|
|||||||
expectedType, mHeader.type));
|
expectedType, mHeader.type));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final ExtCountingDataInput mIn;
|
|
||||||
private final ResTable mResTable;
|
|
||||||
private final List<FlagsOffset> mFlagsOffsets;
|
|
||||||
private final boolean mKeepBroken;
|
|
||||||
|
|
||||||
private ARSCHeader mHeader;
|
|
||||||
private StringBlock mTableStrings;
|
|
||||||
private StringBlock mTypeNames;
|
|
||||||
private StringBlock mSpecNames;
|
|
||||||
private ResPackage mPkg;
|
|
||||||
private ResTypeSpec mTypeSpec;
|
|
||||||
private ResType mType;
|
|
||||||
private int mResId;
|
|
||||||
private int mTypeIdOffset = 0;
|
|
||||||
private final HashMap<Integer, Integer> mMissingResSpecMap;
|
|
||||||
private final HashMap<Integer, ResTypeSpec> mResTypeSpecs = new HashMap<>();
|
|
||||||
|
|
||||||
private final static short ENTRY_FLAG_COMPLEX = 0x0001;
|
|
||||||
private final static short ENTRY_FLAG_PUBLIC = 0x0002;
|
|
||||||
private final static short ENTRY_FLAG_WEAK = 0x0004;
|
|
||||||
private final static short ENTRY_FLAG_COMPACT = 0x0008;
|
|
||||||
|
|
||||||
private final static short TABLE_TYPE_FLAG_SPARSE = 0x01;
|
|
||||||
private final static short TABLE_TYPE_FLAG_OFFSET16 = 0x02;
|
|
||||||
|
|
||||||
private static final int KNOWN_CONFIG_BYTES = 64;
|
|
||||||
|
|
||||||
private static final int NO_ENTRY = 0xFFFFFFFF;
|
|
||||||
private static final int NO_ENTRY_OFFSET16 = 0xFFFF;
|
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(ARSCDecoder.class.getName());
|
|
||||||
}
|
}
|
||||||
|
@ -29,8 +29,7 @@ import brut.androlib.res.data.axml.NamespaceStack;
|
|||||||
import brut.androlib.res.data.value.ResAttr;
|
import brut.androlib.res.data.value.ResAttr;
|
||||||
import brut.androlib.res.data.value.ResScalarValue;
|
import brut.androlib.res.data.value.ResScalarValue;
|
||||||
import brut.androlib.res.xml.ResXmlEncoders;
|
import brut.androlib.res.xml.ResXmlEncoders;
|
||||||
import brut.util.ExtCountingDataInput;
|
import brut.util.ExtDataInputStream;
|
||||||
import com.google.common.io.LittleEndianDataInputStream;
|
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
@ -47,9 +46,45 @@ import java.util.logging.Logger;
|
|||||||
* this state methods return invalid values or throw exceptions.
|
* this state methods return invalid values or throw exceptions.
|
||||||
*/
|
*/
|
||||||
public class AXmlResourceParser implements XmlResourceParser {
|
public class AXmlResourceParser implements XmlResourceParser {
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(AXmlResourceParser.class.getName());
|
||||||
|
|
||||||
|
private static final String E_NOT_SUPPORTED = "Method is not supported.";
|
||||||
|
private static final String ANDROID_RES_NS_AUTO = "http://schemas.android.com/apk/res-auto";
|
||||||
|
public static final String ANDROID_RES_NS = "http://schemas.android.com/apk/res/android";
|
||||||
|
|
||||||
|
// ResXMLTree_attribute
|
||||||
|
private static final int ATTRIBUTE_IX_NAMESPACE_URI = 0; // ns
|
||||||
|
private static final int ATTRIBUTE_IX_NAME = 1; // name
|
||||||
|
private static final int ATTRIBUTE_IX_VALUE_STRING = 2; // rawValue
|
||||||
|
private static final int ATTRIBUTE_IX_VALUE_TYPE = 3; // (size/res0/dataType)
|
||||||
|
private static final int ATTRIBUTE_IX_VALUE_DATA = 4; // data
|
||||||
|
private static final int ATTRIBUTE_LENGTH = 5;
|
||||||
|
|
||||||
|
private static final int PRIVATE_PKG_ID = 0x7F;
|
||||||
|
|
||||||
|
private final ResTable mResTable;
|
||||||
|
private final NamespaceStack mNamespaces;
|
||||||
|
|
||||||
|
private boolean mIsOperational;
|
||||||
|
private ExtDataInputStream mIn;
|
||||||
|
private StringBlock mStringBlock;
|
||||||
|
private int[] mResourceIds;
|
||||||
|
private boolean mDecreaseDepth;
|
||||||
|
private AndrolibException mFirstError;
|
||||||
|
|
||||||
|
// All values are essentially indices, e.g. mNameIndex is an index of name in mStringBlock.
|
||||||
|
private int mEvent;
|
||||||
|
private int mLineNumber;
|
||||||
|
private int mNameIndex;
|
||||||
|
private int mNamespaceIndex;
|
||||||
|
private int[] mAttributes;
|
||||||
|
private int mIdIndex;
|
||||||
|
private int mClassIndex;
|
||||||
|
private int mStyleIndex;
|
||||||
|
|
||||||
public AXmlResourceParser(ResTable resTable) {
|
public AXmlResourceParser(ResTable resTable) {
|
||||||
mResTable = resTable;
|
mResTable = resTable;
|
||||||
|
mNamespaces = new NamespaceStack();
|
||||||
resetEventInfo();
|
resetEventInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,16 +99,16 @@ public class AXmlResourceParser implements XmlResourceParser {
|
|||||||
public void open(InputStream stream) {
|
public void open(InputStream stream) {
|
||||||
close();
|
close();
|
||||||
if (stream != null) {
|
if (stream != null) {
|
||||||
mIn = new ExtCountingDataInput(new LittleEndianDataInputStream(stream));
|
mIn = ExtDataInputStream.littleEndian(stream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
if (!isOperational) {
|
if (!mIsOperational) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
isOperational = false;
|
mIsOperational = false;
|
||||||
mIn = null;
|
mIn = null;
|
||||||
mStringBlock = null;
|
mStringBlock = null;
|
||||||
mResourceIds = null;
|
mResourceIds = null;
|
||||||
@ -314,17 +349,16 @@ public class AXmlResourceParser implements XmlResourceParser {
|
|||||||
|
|
||||||
private String getNonDefaultNamespaceUri(int offset) {
|
private String getNonDefaultNamespaceUri(int offset) {
|
||||||
String prefix = mStringBlock.getString(mNamespaces.getPrefix(offset));
|
String prefix = mStringBlock.getString(mNamespaces.getPrefix(offset));
|
||||||
if (prefix != null) {
|
if (prefix == null) {
|
||||||
return mStringBlock.getString(mNamespaces.getUri(offset));
|
// If we are here. There is some clever obfuscation going on. Our reference points to the namespace are gone.
|
||||||
|
// Normally we could take the index * attributeCount to get an offset.
|
||||||
|
// That would point to the URI in the StringBlock table, but that is empty.
|
||||||
|
// We have the namespaces that can't be touched in the opening tag.
|
||||||
|
// Though no known way to correlate them at this time.
|
||||||
|
// So return the res-auto namespace.
|
||||||
|
return ANDROID_RES_NS_AUTO;
|
||||||
}
|
}
|
||||||
|
return mStringBlock.getString(mNamespaces.getUri(offset));
|
||||||
// If we are here. There is some clever obfuscation going on. Our reference points to the namespace are gone.
|
|
||||||
// Normally we could take the index * attributeCount to get an offset.
|
|
||||||
// That would point to the URI in the StringBlock table, but that is empty.
|
|
||||||
// We have the namespaces that can't be touched in the opening tag.
|
|
||||||
// Though no known way to correlate them at this time.
|
|
||||||
// So return the res-auto namespace.
|
|
||||||
return ANDROID_RES_NS_AUTO;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -404,7 +438,9 @@ public class AXmlResourceParser implements XmlResourceParser {
|
|||||||
int valueRaw = mAttributes[offset + ATTRIBUTE_IX_VALUE_STRING];
|
int valueRaw = mAttributes[offset + ATTRIBUTE_IX_VALUE_STRING];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String stringBlockValue = valueRaw == -1 ? null : ResXmlEncoders.escapeXmlChars(mStringBlock.getString(valueRaw));
|
String stringBlockValue = valueRaw != -1
|
||||||
|
? ResXmlEncoders.escapeXmlChars(mStringBlock.getString(valueRaw))
|
||||||
|
: null;
|
||||||
String resourceMapValue = null;
|
String resourceMapValue = null;
|
||||||
|
|
||||||
// Ensure we only track down obfuscated values for reference/attribute type values. Otherwise, we might
|
// Ensure we only track down obfuscated values for reference/attribute type values. Otherwise, we might
|
||||||
@ -453,21 +489,20 @@ public class AXmlResourceParser implements XmlResourceParser {
|
|||||||
public float getAttributeFloatValue(int index, float defaultValue) {
|
public float getAttributeFloatValue(int index, float defaultValue) {
|
||||||
int offset = getAttributeOffset(index);
|
int offset = getAttributeOffset(index);
|
||||||
int valueType = mAttributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
|
int valueType = mAttributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
|
||||||
if (valueType == TypedValue.TYPE_FLOAT) {
|
if (valueType != TypedValue.TYPE_FLOAT) {
|
||||||
int valueData = mAttributes[offset + ATTRIBUTE_IX_VALUE_DATA];
|
return defaultValue;
|
||||||
return Float.intBitsToFloat(valueData);
|
|
||||||
}
|
}
|
||||||
return defaultValue;
|
return Float.intBitsToFloat(mAttributes[offset + ATTRIBUTE_IX_VALUE_DATA]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getAttributeIntValue(int index, int defaultValue) {
|
public int getAttributeIntValue(int index, int defaultValue) {
|
||||||
int offset = getAttributeOffset(index);
|
int offset = getAttributeOffset(index);
|
||||||
int valueType = mAttributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
|
int valueType = mAttributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
|
||||||
if (valueType >= TypedValue.TYPE_FIRST_INT && valueType <= TypedValue.TYPE_LAST_INT) {
|
if (valueType < TypedValue.TYPE_FIRST_INT || valueType > TypedValue.TYPE_LAST_INT) {
|
||||||
return mAttributes[offset + ATTRIBUTE_IX_VALUE_DATA];
|
return defaultValue;
|
||||||
}
|
}
|
||||||
return defaultValue;
|
return mAttributes[offset + ATTRIBUTE_IX_VALUE_DATA];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -479,10 +514,10 @@ public class AXmlResourceParser implements XmlResourceParser {
|
|||||||
public int getAttributeResourceValue(int index, int defaultValue) {
|
public int getAttributeResourceValue(int index, int defaultValue) {
|
||||||
int offset = getAttributeOffset(index);
|
int offset = getAttributeOffset(index);
|
||||||
int valueType = mAttributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
|
int valueType = mAttributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
|
||||||
if (valueType == TypedValue.TYPE_REFERENCE) {
|
if (valueType != TypedValue.TYPE_REFERENCE) {
|
||||||
return mAttributes[offset + ATTRIBUTE_IX_VALUE_DATA];
|
return defaultValue;
|
||||||
}
|
}
|
||||||
return defaultValue;
|
return mAttributes[offset + ATTRIBUTE_IX_VALUE_DATA];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -687,7 +722,7 @@ public class AXmlResourceParser implements XmlResourceParser {
|
|||||||
|
|
||||||
mStringBlock = StringBlock.readWithChunk(mIn);
|
mStringBlock = StringBlock.readWithChunk(mIn);
|
||||||
mNamespaces.increaseDepth();
|
mNamespaces.increaseDepth();
|
||||||
isOperational = true;
|
mIsOperational = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mEvent == END_DOCUMENT) {
|
if (mEvent == END_DOCUMENT) {
|
||||||
@ -698,8 +733,8 @@ public class AXmlResourceParser implements XmlResourceParser {
|
|||||||
resetEventInfo();
|
resetEventInfo();
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (m_decreaseDepth) {
|
if (mDecreaseDepth) {
|
||||||
m_decreaseDepth = false;
|
mDecreaseDepth = false;
|
||||||
mNamespaces.decreaseDepth();
|
mNamespaces.decreaseDepth();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -710,7 +745,7 @@ public class AXmlResourceParser implements XmlResourceParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// #2070 - Some applications have 2 start namespaces, but only 1 end namespace.
|
// #2070 - Some applications have 2 start namespaces, but only 1 end namespace.
|
||||||
if (mIn.remaining() == 0) {
|
if (mIn.available() == 0) {
|
||||||
LOGGER.warning(String.format("AXML hit unexpected end of file at byte: 0x%X", mIn.position()));
|
LOGGER.warning(String.format("AXML hit unexpected end of file at byte: 0x%X", mIn.position()));
|
||||||
mEvent = END_DOCUMENT;
|
mEvent = END_DOCUMENT;
|
||||||
break;
|
break;
|
||||||
@ -809,7 +844,7 @@ public class AXmlResourceParser implements XmlResourceParser {
|
|||||||
mNamespaceIndex = mIn.readInt();
|
mNamespaceIndex = mIn.readInt();
|
||||||
mNameIndex = mIn.readInt();
|
mNameIndex = mIn.readInt();
|
||||||
mEvent = END_TAG;
|
mEvent = END_TAG;
|
||||||
m_decreaseDepth = true;
|
mDecreaseDepth = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -828,40 +863,4 @@ public class AXmlResourceParser implements XmlResourceParser {
|
|||||||
mFirstError = error;
|
mFirstError = error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ExtCountingDataInput mIn;
|
|
||||||
private final ResTable mResTable;
|
|
||||||
private AndrolibException mFirstError;
|
|
||||||
|
|
||||||
private boolean isOperational = false;
|
|
||||||
private StringBlock mStringBlock;
|
|
||||||
private int[] mResourceIds;
|
|
||||||
private final NamespaceStack mNamespaces = new NamespaceStack();
|
|
||||||
private boolean m_decreaseDepth;
|
|
||||||
|
|
||||||
// All values are essentially indices, e.g. mNameIndex is an index of name in mStringBlock.
|
|
||||||
private int mEvent;
|
|
||||||
private int mLineNumber;
|
|
||||||
private int mNameIndex;
|
|
||||||
private int mNamespaceIndex;
|
|
||||||
private int[] mAttributes;
|
|
||||||
private int mIdIndex;
|
|
||||||
private int mClassIndex;
|
|
||||||
private int mStyleIndex;
|
|
||||||
|
|
||||||
private final static Logger LOGGER = Logger.getLogger(AXmlResourceParser.class.getName());
|
|
||||||
private static final String E_NOT_SUPPORTED = "Method is not supported.";
|
|
||||||
|
|
||||||
// ResXMLTree_attribute
|
|
||||||
private static final int ATTRIBUTE_IX_NAMESPACE_URI = 0; // ns
|
|
||||||
private static final int ATTRIBUTE_IX_NAME = 1; // name
|
|
||||||
private static final int ATTRIBUTE_IX_VALUE_STRING = 2; // rawValue
|
|
||||||
private static final int ATTRIBUTE_IX_VALUE_TYPE = 3; // (size/res0/dataType)
|
|
||||||
private static final int ATTRIBUTE_IX_VALUE_DATA = 4; // data
|
|
||||||
private static final int ATTRIBUTE_LENGTH = 5;
|
|
||||||
|
|
||||||
private static final int PRIVATE_PKG_ID = 0x7F;
|
|
||||||
|
|
||||||
private static final String ANDROID_RES_NS_AUTO = "http://schemas.android.com/apk/res-auto";
|
|
||||||
public static final String ANDROID_RES_NS = "http://schemas.android.com/apk/res/android";
|
|
||||||
}
|
}
|
||||||
|
@ -25,11 +25,6 @@ import java.util.regex.Pattern;
|
|||||||
* AXmlResourceParser specifically for parsing encoded AndroidManifest.xml.
|
* AXmlResourceParser specifically for parsing encoded AndroidManifest.xml.
|
||||||
*/
|
*/
|
||||||
public class AndroidManifestResourceParser extends AXmlResourceParser {
|
public class AndroidManifestResourceParser extends AXmlResourceParser {
|
||||||
|
|
||||||
public AndroidManifestResourceParser(ResTable resTable) {
|
|
||||||
super(resTable);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pattern for matching numeric string meta-data values. aapt automatically infers the
|
* Pattern for matching numeric string meta-data values. aapt automatically infers the
|
||||||
* type for a manifest meta-data value based on the string in the unencoded XML. However,
|
* type for a manifest meta-data value based on the string in the unencoded XML. However,
|
||||||
@ -39,6 +34,10 @@ public class AndroidManifestResourceParser extends AXmlResourceParser {
|
|||||||
*/
|
*/
|
||||||
private static final Pattern PATTERN_NUMERIC_STRING = Pattern.compile("\\s?\\d+");
|
private static final Pattern PATTERN_NUMERIC_STRING = Pattern.compile("\\s?\\d+");
|
||||||
|
|
||||||
|
public AndroidManifestResourceParser(ResTable resTable) {
|
||||||
|
super(resTable);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getAttributeValue(int index) {
|
public String getAttributeValue(int index) {
|
||||||
String value = super.getAttributeValue(index);
|
String value = super.getAttributeValue(index);
|
||||||
|
@ -21,6 +21,7 @@ import brut.androlib.exceptions.CantFind9PatchChunkException;
|
|||||||
import brut.androlib.res.data.ninepatch.NinePatchData;
|
import brut.androlib.res.data.ninepatch.NinePatchData;
|
||||||
import brut.androlib.res.data.ninepatch.OpticalInset;
|
import brut.androlib.res.data.ninepatch.OpticalInset;
|
||||||
import brut.util.ExtDataInput;
|
import brut.util.ExtDataInput;
|
||||||
|
import brut.util.ExtDataInputStream;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
@ -30,6 +31,11 @@ import java.awt.image.WritableRaster;
|
|||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
|
||||||
public class Res9patchStreamDecoder implements ResStreamDecoder {
|
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;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void decode(InputStream in, OutputStream out) throws AndrolibException {
|
public void decode(InputStream in, OutputStream out) throws AndrolibException {
|
||||||
try {
|
try {
|
||||||
@ -122,32 +128,32 @@ public class Res9patchStreamDecoder implements ResStreamDecoder {
|
|||||||
|
|
||||||
private NinePatchData getNinePatch(byte[] data) throws AndrolibException,
|
private NinePatchData getNinePatch(byte[] data) throws AndrolibException,
|
||||||
IOException {
|
IOException {
|
||||||
ExtDataInput di = new ExtDataInput(new ByteArrayInputStream(data));
|
ExtDataInput in = ExtDataInputStream.bigEndian(new ByteArrayInputStream(data));
|
||||||
find9patchChunk(di, NP_CHUNK_TYPE);
|
find9patchChunk(in, NP_CHUNK_TYPE);
|
||||||
return NinePatchData.decode(di);
|
return NinePatchData.decode(in);
|
||||||
}
|
}
|
||||||
|
|
||||||
private OpticalInset getOpticalInset(byte[] data) throws AndrolibException,
|
private OpticalInset getOpticalInset(byte[] data) throws AndrolibException,
|
||||||
IOException {
|
IOException {
|
||||||
ExtDataInput di = new ExtDataInput(new ByteArrayInputStream(data));
|
ExtDataInput in = ExtDataInputStream.bigEndian(new ByteArrayInputStream(data));
|
||||||
find9patchChunk(di, OI_CHUNK_TYPE);
|
find9patchChunk(in, OI_CHUNK_TYPE);
|
||||||
return OpticalInset.decode(di);
|
return OpticalInset.decode(in);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void find9patchChunk(DataInput di, int magic) throws AndrolibException,
|
private void find9patchChunk(DataInput in, int magic) throws AndrolibException,
|
||||||
IOException {
|
IOException {
|
||||||
di.skipBytes(8);
|
in.skipBytes(8);
|
||||||
while (true) {
|
while (true) {
|
||||||
int size;
|
int size;
|
||||||
try {
|
try {
|
||||||
size = di.readInt();
|
size = in.readInt();
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
throw new CantFind9PatchChunkException("Cant find nine patch chunk", ex);
|
throw new CantFind9PatchChunkException("Could not find nine patch chunk", ex);
|
||||||
}
|
}
|
||||||
if (di.readInt() == magic) {
|
if (in.readInt() == magic) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
di.skipBytes(size + 4);
|
in.skipBytes(size + 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,9 +168,4 @@ public class Res9patchStreamDecoder implements ResStreamDecoder {
|
|||||||
im.setRGB(x, y, NP_COLOR);
|
im.setRGB(x, y, NP_COLOR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ import brut.androlib.res.data.value.ResBoolValue;
|
|||||||
import brut.androlib.res.data.value.ResFileValue;
|
import brut.androlib.res.data.value.ResFileValue;
|
||||||
import brut.directory.Directory;
|
import brut.directory.Directory;
|
||||||
import brut.directory.DirectoryException;
|
import brut.directory.DirectoryException;
|
||||||
import brut.directory.DirUtil;
|
import brut.directory.DirUtils;
|
||||||
import brut.util.BrutIO;
|
import brut.util.BrutIO;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
@ -33,6 +33,17 @@ import java.util.logging.Level;
|
|||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
public class ResFileDecoder {
|
public class ResFileDecoder {
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(ResFileDecoder.class.getName());
|
||||||
|
|
||||||
|
private static final String[] RAW_IMAGE_EXTENSIONS = {
|
||||||
|
"m4a", // apple
|
||||||
|
"qmg", // samsung
|
||||||
|
};
|
||||||
|
private static final String[] RAW_9PATCH_IMAGE_EXTENSIONS = {
|
||||||
|
"qmg", // samsung
|
||||||
|
"spi", // samsung
|
||||||
|
};
|
||||||
|
|
||||||
private final ResStreamDecoderContainer mDecoders;
|
private final ResStreamDecoderContainer mDecoders;
|
||||||
|
|
||||||
public ResFileDecoder(ResStreamDecoderContainer decoders) {
|
public ResFileDecoder(ResStreamDecoderContainer decoders) {
|
||||||
@ -109,7 +120,7 @@ public class ResFileDecoder {
|
|||||||
return;
|
return;
|
||||||
} catch (CantFind9PatchChunkException ex) {
|
} catch (CantFind9PatchChunkException ex) {
|
||||||
LOGGER.log(Level.WARNING, String.format(
|
LOGGER.log(Level.WARNING, String.format(
|
||||||
"Cant find 9patch chunk in file: \"%s\". Renaming it to *.png.", inFileName
|
"Could not find 9patch chunk in file: \"%s\". Renaming it to *.png.", inFileName
|
||||||
), ex);
|
), ex);
|
||||||
outDir.removeFile(outFileName);
|
outDir.removeFile(outFileName);
|
||||||
outFileName = outResName + ext;
|
outFileName = outResName + ext;
|
||||||
@ -156,24 +167,12 @@ public class ResFileDecoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void copyRaw(Directory inDir, Directory outDir, String inFilename,
|
public void copyRaw(Directory inDir, Directory outDir, String inFileName,
|
||||||
String outFilename) throws AndrolibException {
|
String outFileName) throws AndrolibException {
|
||||||
try {
|
try {
|
||||||
DirUtil.copyToDir(inDir, outDir, inFilename, outFilename);
|
DirUtils.copyToDir(inDir, outDir, inFileName, outFileName);
|
||||||
} catch (DirectoryException ex) {
|
} catch (DirectoryException ex) {
|
||||||
throw new AndrolibException(ex);
|
throw new AndrolibException(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final static Logger LOGGER = Logger.getLogger(ResFileDecoder.class.getName());
|
|
||||||
|
|
||||||
private final static String[] RAW_IMAGE_EXTENSIONS = new String[] {
|
|
||||||
"m4a", // apple
|
|
||||||
"qmg", // samsung
|
|
||||||
};
|
|
||||||
|
|
||||||
private final static String[] RAW_9PATCH_IMAGE_EXTENSIONS = new String[] {
|
|
||||||
"qmg", // samsung
|
|
||||||
"spi", // samsung
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import org.apache.commons.io.IOUtils;
|
|||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
|
||||||
public class ResRawStreamDecoder implements ResStreamDecoder {
|
public class ResRawStreamDecoder implements ResStreamDecoder {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void decode(InputStream in, OutputStream out) throws AndrolibException {
|
public void decode(InputStream in, OutputStream out) throws AndrolibException {
|
||||||
try {
|
try {
|
||||||
|
@ -21,7 +21,11 @@ import java.io.*;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public class ResStreamDecoderContainer {
|
public class ResStreamDecoderContainer {
|
||||||
private final Map<String, ResStreamDecoder> mDecoders = new HashMap<>();
|
private final Map<String, ResStreamDecoder> mDecoders;
|
||||||
|
|
||||||
|
public ResStreamDecoderContainer() {
|
||||||
|
mDecoders = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
public void decode(InputStream in, OutputStream out, String decoderName)
|
public void decode(InputStream in, OutputStream out, String decoderName)
|
||||||
throws AndrolibException {
|
throws AndrolibException {
|
||||||
|
@ -18,7 +18,7 @@ package brut.androlib.res.decoder;
|
|||||||
|
|
||||||
import brut.androlib.res.data.arsc.ARSCHeader;
|
import brut.androlib.res.data.arsc.ARSCHeader;
|
||||||
import brut.androlib.res.xml.ResXmlEncoders;
|
import brut.androlib.res.xml.ResXmlEncoders;
|
||||||
import brut.util.ExtCountingDataInput;
|
import brut.util.ExtDataInput;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -29,8 +29,23 @@ import java.util.List;
|
|||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
public class StringBlock {
|
public class StringBlock {
|
||||||
public static StringBlock readWithChunk(ExtCountingDataInput reader) throws IOException {
|
private static final Logger LOGGER = Logger.getLogger(StringBlock.class.getName());
|
||||||
int startPosition = reader.position();
|
|
||||||
|
private static final CharsetDecoder UTF16LE_DECODER = StandardCharsets.UTF_16LE.newDecoder();
|
||||||
|
private static final CharsetDecoder UTF8_DECODER = StandardCharsets.UTF_8.newDecoder();
|
||||||
|
private static final CharsetDecoder CESU8_DECODER = Charset.forName("CESU8").newDecoder();
|
||||||
|
|
||||||
|
private static final int UTF8_FLAG = 0x00000100;
|
||||||
|
private static final int STRING_BLOCK_HEADER_SIZE = 28;
|
||||||
|
|
||||||
|
private int[] mStringOffsets;
|
||||||
|
private byte[] mStrings;
|
||||||
|
private int[] mStyleOffsets;
|
||||||
|
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);
|
reader.skipCheckShort(ARSCHeader.RES_STRING_POOL_TYPE);
|
||||||
int headerSize = reader.readShort();
|
int headerSize = reader.readShort();
|
||||||
int chunkSize = reader.readInt();
|
int chunkSize = reader.readInt();
|
||||||
@ -38,9 +53,8 @@ public class StringBlock {
|
|||||||
return readWithoutChunk(reader, startPosition, headerSize, chunkSize);
|
return readWithoutChunk(reader, startPosition, headerSize, chunkSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static StringBlock readWithoutChunk(ExtCountingDataInput reader, int startPosition, int headerSize,
|
public static StringBlock readWithoutChunk(ExtDataInput reader, long startPosition,
|
||||||
int chunkSize) throws IOException
|
int headerSize, int chunkSize) throws IOException {
|
||||||
{
|
|
||||||
// ResStringPool_header
|
// ResStringPool_header
|
||||||
int stringCount = reader.readInt();
|
int stringCount = reader.readInt();
|
||||||
int styleCount = reader.readInt();
|
int styleCount = reader.readInt();
|
||||||
@ -90,6 +104,15 @@ public class StringBlock {
|
|||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private StringBlock() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
StringBlock(byte[] strings, boolean isUTF8) {
|
||||||
|
mStrings = strings;
|
||||||
|
mIsUtf8 = isUTF8;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns raw string (without any styling information) at specified index.
|
* Returns raw string (without any styling information) at specified index.
|
||||||
* @param index int
|
* @param index int
|
||||||
@ -111,6 +134,7 @@ public class StringBlock {
|
|||||||
offset += val[0];
|
offset += val[0];
|
||||||
}
|
}
|
||||||
length = val[1];
|
length = val[1];
|
||||||
|
|
||||||
return decodeString(offset, length);
|
return decodeString(offset, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,14 +178,14 @@ public class StringBlock {
|
|||||||
if (string == null) {
|
if (string == null) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
for (int i = 0; i != mStringOffsets.length; ++i) {
|
for (int i = 0; i != mStringOffsets.length; i++) {
|
||||||
int offset = mStringOffsets[i];
|
int offset = mStringOffsets[i];
|
||||||
int length = getShort(mStrings, offset);
|
int length = getShort(mStrings, offset);
|
||||||
if (length != string.length()) {
|
if (length != string.length()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
int j = 0;
|
int j = 0;
|
||||||
for (; j != length; ++j) {
|
for (; j != length; j++) {
|
||||||
offset += 2;
|
offset += 2;
|
||||||
if (string.charAt(j) != getShort(mStrings, offset)) {
|
if (string.charAt(j) != getShort(mStrings, offset)) {
|
||||||
break;
|
break;
|
||||||
@ -174,15 +198,6 @@ public class StringBlock {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private StringBlock() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
StringBlock(byte[] strings, boolean isUTF8) {
|
|
||||||
mStrings = strings;
|
|
||||||
mIsUtf8 = isUTF8;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns style information - array of int triplets, where in each triplet:
|
* Returns style information - array of int triplets, where in each triplet:
|
||||||
* * first int is index of tag name ('b','i', etc.) * second int is tag
|
* * first int is index of tag name ('b','i', etc.) * second int is tag
|
||||||
@ -196,7 +211,7 @@ public class StringBlock {
|
|||||||
int count = 0;
|
int count = 0;
|
||||||
int[] style;
|
int[] style;
|
||||||
|
|
||||||
for (int i = offset; i < mStyles.length; ++i) {
|
for (int i = offset; i < mStyles.length; i++) {
|
||||||
if (mStyles[i] == -1) {
|
if (mStyles[i] == -1) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -262,39 +277,25 @@ public class StringBlock {
|
|||||||
val = array[offset];
|
val = array[offset];
|
||||||
offset += 1;
|
offset += 1;
|
||||||
if ((val & 0x80) != 0) {
|
if ((val & 0x80) != 0) {
|
||||||
int low = (array[offset] & 0xFF);
|
int low = array[offset] & 0xFF;
|
||||||
length = ((val & 0x7F) << 8) + low;
|
length = ((val & 0x7F) << 8) + low;
|
||||||
offset += 1;
|
offset += 1;
|
||||||
} else {
|
} else {
|
||||||
length = val;
|
length = val;
|
||||||
}
|
}
|
||||||
return new int[] { offset, length};
|
return new int[] { offset, length };
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int[] getUtf16(byte[] array, int offset) {
|
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) {
|
if ((val & 0x8000) != 0) {
|
||||||
int high = (array[offset + 3] & 0xFF) << 8;
|
int high = (array[offset + 3] & 0xFF) << 8;
|
||||||
int low = (array[offset + 2] & 0xFF);
|
int low = (array[offset + 2] & 0xFF);
|
||||||
int len_value = ((val & 0x7FFF) << 16) + (high + low);
|
int len_value = ((val & 0x7FFF) << 16) + (high + low);
|
||||||
return new int[] {4, len_value * 2};
|
return new int[] { 4, len_value * 2 };
|
||||||
|
|
||||||
}
|
}
|
||||||
return new int[] {2, val * 2};
|
return new int[] { 2, val * 2 };
|
||||||
}
|
}
|
||||||
|
|
||||||
private int[] mStringOffsets;
|
|
||||||
private byte[] mStrings;
|
|
||||||
private int[] mStyleOffsets;
|
|
||||||
private int[] mStyles;
|
|
||||||
private boolean mIsUtf8;
|
|
||||||
|
|
||||||
private final CharsetDecoder UTF16LE_DECODER = StandardCharsets.UTF_16LE.newDecoder();
|
|
||||||
private final CharsetDecoder UTF8_DECODER = StandardCharsets.UTF_8.newDecoder();
|
|
||||||
private final CharsetDecoder CESU8_DECODER = Charset.forName("CESU8").newDecoder();
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(StringBlock.class.getName());
|
|
||||||
|
|
||||||
private static final int UTF8_FLAG = 0x00000100;
|
|
||||||
private static final int STRING_BLOCK_HEADER_SIZE = 28;
|
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,8 @@ import java.util.Map;
|
|||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
public class StyledString {
|
public class StyledString {
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(StyledString.class.getName());
|
||||||
|
|
||||||
private final String mText;
|
private final String mText;
|
||||||
private final List<Span> mSpans;
|
private final List<Span> mSpans;
|
||||||
|
|
||||||
@ -52,63 +54,63 @@ public class StyledString {
|
|||||||
private static final MapSplitter ATTRIBUTES_SPLITTER =
|
private static final MapSplitter ATTRIBUTES_SPLITTER =
|
||||||
Splitter.on(';').omitEmptyStrings().withKeyValueSeparator(Splitter.on('=').limit(2));
|
Splitter.on(';').omitEmptyStrings().withKeyValueSeparator(Splitter.on('=').limit(2));
|
||||||
|
|
||||||
private final String tag;
|
private final String mTag;
|
||||||
private final int firstChar;
|
private final int mFirstChar;
|
||||||
private final int lastChar;
|
private final int mLastChar;
|
||||||
|
|
||||||
public Span(String tag, int firstChar, int lastChar) {
|
public Span(String tag, int firstChar, int lastChar) {
|
||||||
this.tag = tag;
|
mTag = tag;
|
||||||
this.firstChar = firstChar;
|
mFirstChar = firstChar;
|
||||||
this.lastChar = lastChar;
|
mLastChar = lastChar;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTag() {
|
public String getTag() {
|
||||||
return tag;
|
return mTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getFirstChar() {
|
public int getFirstChar() {
|
||||||
return firstChar;
|
return mFirstChar;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getLastChar() {
|
public int getLastChar() {
|
||||||
return lastChar;
|
return mLastChar;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
int separatorIdx = tag.indexOf(';');
|
int separatorIdx = mTag.indexOf(';');
|
||||||
return separatorIdx == -1 ? tag : tag.substring(0, separatorIdx);
|
return separatorIdx == -1 ? mTag : mTag.substring(0, separatorIdx);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, String> getAttributes() {
|
public Map<String, String> getAttributes() {
|
||||||
int separatorIdx = tag.indexOf(';');
|
int separatorIdx = mTag.indexOf(';');
|
||||||
return separatorIdx == -1 ? null : ATTRIBUTES_SPLITTER.split(
|
return separatorIdx != -1 ? ATTRIBUTES_SPLITTER.split(
|
||||||
tag.substring(separatorIdx + 1, tag.endsWith(";") ? tag.length() - 1 : tag.length())
|
mTag.substring(separatorIdx + 1, mTag.endsWith(";") ? mTag.length() - 1 : mTag.length())
|
||||||
);
|
) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(Span o) {
|
public int compareTo(Span o) {
|
||||||
int res = Integer.compare(firstChar, o.firstChar);
|
int res = Integer.compare(mFirstChar, o.mFirstChar);
|
||||||
if (res != 0) {
|
if (res != 0) {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
res = Integer.compare(lastChar, o.lastChar);
|
res = Integer.compare(mLastChar, o.mLastChar);
|
||||||
if (res != 0) {
|
if (res != 0) {
|
||||||
return -res;
|
return -res;
|
||||||
}
|
}
|
||||||
return -tag.compareTo(o.tag);
|
return -mTag.compareTo(o.mTag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class Decoder {
|
private static class Decoder {
|
||||||
private String text;
|
private String mText;
|
||||||
private StringBuilder xmlValue;
|
private StringBuilder mXmlValue;
|
||||||
private int lastOffset;
|
private int mLastOffset;
|
||||||
|
|
||||||
String decode(StyledString styledString) {
|
public String decode(StyledString styledString) {
|
||||||
text = styledString.getText();
|
mText = styledString.getText();
|
||||||
xmlValue = new StringBuilder(text.length() * 2);
|
mXmlValue = new StringBuilder(mText.length() * 2);
|
||||||
lastOffset = 0;
|
mLastOffset = 0;
|
||||||
|
|
||||||
// recurse top-level tags
|
// recurse top-level tags
|
||||||
PeekingIterator<Span> it = Iterators.peekingIterator(styledString.getSpans().iterator());
|
PeekingIterator<Span> it = Iterators.peekingIterator(styledString.getSpans().iterator());
|
||||||
@ -116,11 +118,11 @@ public class StyledString {
|
|||||||
decodeIterate(it);
|
decodeIterate(it);
|
||||||
}
|
}
|
||||||
|
|
||||||
// write the remaining encoded raw text
|
// write the remaining encoded raw mText
|
||||||
if (lastOffset < text.length()) {
|
if (mLastOffset < mText.length()) {
|
||||||
xmlValue.append(ResXmlEncoders.escapeXmlChars(text.substring(lastOffset)));
|
mXmlValue.append(ResXmlEncoders.escapeXmlChars(mText.substring(mLastOffset)));
|
||||||
}
|
}
|
||||||
return xmlValue.toString();
|
return mXmlValue.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void decodeIterate(PeekingIterator<Span> it) {
|
private void decodeIterate(PeekingIterator<Span> it) {
|
||||||
@ -130,45 +132,43 @@ public class StyledString {
|
|||||||
int spanStart = span.getFirstChar();
|
int spanStart = span.getFirstChar();
|
||||||
int spanEnd = span.getLastChar() + 1;
|
int spanEnd = span.getLastChar() + 1;
|
||||||
|
|
||||||
// write encoded raw text preceding the opening tag
|
// write encoded raw mText preceding the opening tag
|
||||||
if (spanStart > lastOffset) {
|
if (spanStart > mLastOffset) {
|
||||||
xmlValue.append(ResXmlEncoders.escapeXmlChars(text.substring(lastOffset, spanStart)));
|
mXmlValue.append(ResXmlEncoders.escapeXmlChars(mText.substring(mLastOffset, spanStart)));
|
||||||
}
|
}
|
||||||
lastOffset = spanStart;
|
mLastOffset = spanStart;
|
||||||
|
|
||||||
// write opening tag
|
// write opening tag
|
||||||
xmlValue.append('<').append(name);
|
mXmlValue.append('<').append(name);
|
||||||
if (attributes != null) {
|
if (attributes != null) {
|
||||||
for (Map.Entry<String, String> attrEntry : attributes.entrySet()) {
|
for (Map.Entry<String, String> attrEntry : attributes.entrySet()) {
|
||||||
xmlValue.append(' ').append(attrEntry.getKey()).append("=\"")
|
mXmlValue.append(' ').append(attrEntry.getKey()).append("=\"")
|
||||||
.append(ResXmlEncoders.escapeXmlChars(attrEntry.getValue())).append('"');
|
.append(ResXmlEncoders.escapeXmlChars(attrEntry.getValue())).append('"');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if an opening tag is followed by a matching closing tag, write as an empty-element tag
|
// if an opening tag is followed by a matching closing tag, write as an empty-element tag
|
||||||
if (spanStart == spanEnd) {
|
if (spanStart == spanEnd) {
|
||||||
xmlValue.append("/>");
|
mXmlValue.append("/>");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
xmlValue.append('>');
|
mXmlValue.append('>');
|
||||||
|
|
||||||
// recurse nested tags
|
// recurse nested tags
|
||||||
while (it.hasNext() && it.peek().getFirstChar() < spanEnd) {
|
while (it.hasNext() && it.peek().getFirstChar() < spanEnd) {
|
||||||
decodeIterate(it);
|
decodeIterate(it);
|
||||||
}
|
}
|
||||||
|
|
||||||
// write encoded raw text preceding the closing tag
|
// write encoded raw mText preceding the closing tag
|
||||||
if (spanEnd > lastOffset && text.length() >= spanEnd) {
|
if (spanEnd > mLastOffset && mText.length() >= spanEnd) {
|
||||||
xmlValue.append(ResXmlEncoders.escapeXmlChars(text.substring(lastOffset, spanEnd)));
|
mXmlValue.append(ResXmlEncoders.escapeXmlChars(mText.substring(mLastOffset, spanEnd)));
|
||||||
} else if (text.length() >= lastOffset && text.length() < spanEnd) {
|
} else if (mText.length() >= mLastOffset && mText.length() < spanEnd) {
|
||||||
LOGGER.warning("Span (" + name + ") exceeds text length " + text.length());
|
LOGGER.warning("Span (" + name + ") exceeds mText length " + mText.length());
|
||||||
xmlValue.append(ResXmlEncoders.escapeXmlChars(text.substring(lastOffset)));
|
mXmlValue.append(ResXmlEncoders.escapeXmlChars(mText.substring(mLastOffset)));
|
||||||
}
|
}
|
||||||
lastOffset = spanEnd;
|
mLastOffset = spanEnd;
|
||||||
|
|
||||||
// write closing tag
|
// write closing tag
|
||||||
xmlValue.append("</").append(name).append('>');
|
mXmlValue.append("</").append(name).append('>');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(StyledString.class.getName());
|
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,6 @@ import org.xmlpull.v1.XmlSerializer;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
public interface ResValuesXmlSerializable {
|
public interface ResValuesXmlSerializable {
|
||||||
void serializeToResValuesXml(XmlSerializer serializer,
|
void serializeToResValuesXml(XmlSerializer serializer, ResResource res)
|
||||||
ResResource res) throws IOException, AndrolibException;
|
throws IOException, AndrolibException;
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,10 @@ import java.util.List;
|
|||||||
|
|
||||||
public final class ResXmlEncoders {
|
public final class ResXmlEncoders {
|
||||||
|
|
||||||
|
private ResXmlEncoders() {
|
||||||
|
// Private constructor for utility class
|
||||||
|
}
|
||||||
|
|
||||||
public static String escapeXmlChars(String str) {
|
public static String escapeXmlChars(String str) {
|
||||||
return StringUtils.replace(StringUtils.replace(str, "&", "&"), "<", "<");
|
return StringUtils.replace(StringUtils.replace(str, "&", "&"), "<", "<");
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,15 @@ import java.nio.file.Files;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
public final class ResXmlPatcher {
|
public final class ResXmlUtils {
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(ResXmlUtils.class.getName());
|
||||||
|
|
||||||
|
private static final String FEATURE_LOAD_DTD = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
|
||||||
|
private static final String FEATURE_DISABLE_DOCTYPE_DECL = "http://apache.org/xml/features/disallow-doctype-decl";
|
||||||
|
|
||||||
|
private ResXmlUtils() {
|
||||||
|
// Private constructor for utility class
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes "debug" tag from file
|
* Removes "debug" tag from file
|
||||||
@ -50,9 +58,6 @@ public final class ResXmlPatcher {
|
|||||||
* @throws AndrolibException Error reading Manifest file
|
* @throws AndrolibException Error reading Manifest file
|
||||||
*/
|
*/
|
||||||
public static void removeApplicationDebugTag(File file) throws AndrolibException {
|
public static void removeApplicationDebugTag(File file) throws AndrolibException {
|
||||||
if (!file.exists()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
Document doc = loadDocument(file);
|
Document doc = loadDocument(file);
|
||||||
Node application = doc.getElementsByTagName("application").item(0);
|
Node application = doc.getElementsByTagName("application").item(0);
|
||||||
@ -67,7 +72,6 @@ public final class ResXmlPatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
saveDocument(file, doc);
|
saveDocument(file, doc);
|
||||||
|
|
||||||
} catch (SAXException | ParserConfigurationException | IOException | TransformerException ignored) {
|
} catch (SAXException | ParserConfigurationException | IOException | TransformerException ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -78,9 +82,6 @@ public final class ResXmlPatcher {
|
|||||||
* @param file AndroidManifest file
|
* @param file AndroidManifest file
|
||||||
*/
|
*/
|
||||||
public static void setApplicationDebugTagTrue(File file) {
|
public static void setApplicationDebugTagTrue(File file) {
|
||||||
if (!file.exists()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
Document doc = loadDocument(file);
|
Document doc = loadDocument(file);
|
||||||
Node application = doc.getElementsByTagName("application").item(0);
|
Node application = doc.getElementsByTagName("application").item(0);
|
||||||
@ -98,7 +99,6 @@ public final class ResXmlPatcher {
|
|||||||
debugAttr.setNodeValue("true");
|
debugAttr.setNodeValue("true");
|
||||||
|
|
||||||
saveDocument(file, doc);
|
saveDocument(file, doc);
|
||||||
|
|
||||||
} catch (SAXException | ParserConfigurationException | IOException | TransformerException ignored) {
|
} catch (SAXException | ParserConfigurationException | IOException | TransformerException ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,9 +109,6 @@ public final class ResXmlPatcher {
|
|||||||
* @param file AndroidManifest file
|
* @param file AndroidManifest file
|
||||||
*/
|
*/
|
||||||
public static void setNetworkSecurityConfig(File file) {
|
public static void setNetworkSecurityConfig(File file) {
|
||||||
if (!file.exists()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
Document doc = loadDocument(file);
|
Document doc = loadDocument(file);
|
||||||
Node application = doc.getElementsByTagName("application").item(0);
|
Node application = doc.getElementsByTagName("application").item(0);
|
||||||
@ -130,7 +127,6 @@ public final class ResXmlPatcher {
|
|||||||
netSecConfAttr.setNodeValue("@xml/network_security_config");
|
netSecConfAttr.setNodeValue("@xml/network_security_config");
|
||||||
|
|
||||||
saveDocument(file, doc);
|
saveDocument(file, doc);
|
||||||
|
|
||||||
} catch (SAXException | ParserConfigurationException | IOException | TransformerException ignored) {
|
} catch (SAXException | ParserConfigurationException | IOException | TransformerException ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -155,13 +151,13 @@ public final class ResXmlPatcher {
|
|||||||
} else {
|
} else {
|
||||||
document = documentBuilder.newDocument();
|
document = documentBuilder.newDocument();
|
||||||
}
|
}
|
||||||
|
|
||||||
Element root = (Element) document.getElementsByTagName("network-security-config").item(0);
|
Element root = (Element) document.getElementsByTagName("network-security-config").item(0);
|
||||||
if (root == null) {
|
if (root == null) {
|
||||||
root = document.createElement("network-security-config");
|
root = document.createElement("network-security-config");
|
||||||
document.appendChild(root);
|
document.appendChild(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
Element baseConfig = (Element) document.getElementsByTagName("base-config").item(0);
|
Element baseConfig = (Element) document.getElementsByTagName("base-config").item(0);
|
||||||
if (baseConfig == null) {
|
if (baseConfig == null) {
|
||||||
baseConfig = document.createElement("base-config");
|
baseConfig = document.createElement("base-config");
|
||||||
@ -192,13 +188,13 @@ public final class ResXmlPatcher {
|
|||||||
certSystem.setAttribute("src", "system");
|
certSystem.setAttribute("src", "system");
|
||||||
trustAnchors.appendChild(certSystem);
|
trustAnchors.appendChild(certSystem);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasUserCert) {
|
if (!hasUserCert) {
|
||||||
Element certUser = document.createElement("certificates");
|
Element certUser = document.createElement("certificates");
|
||||||
certUser.setAttribute("src", "user");
|
certUser.setAttribute("src", "user");
|
||||||
trustAnchors.appendChild(certUser);
|
trustAnchors.appendChild(certUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
saveDocument(file, document);
|
saveDocument(file, document);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,9 +210,6 @@ public final class ResXmlPatcher {
|
|||||||
* @param file File for AndroidManifest.xml
|
* @param file File for AndroidManifest.xml
|
||||||
*/
|
*/
|
||||||
public static void fixingPublicAttrsInProviderAttributes(File file) {
|
public static void fixingPublicAttrsInProviderAttributes(File file) {
|
||||||
if (!file.exists()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
Document doc = loadDocument(file);
|
Document doc = loadDocument(file);
|
||||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||||
@ -257,7 +250,6 @@ public final class ResXmlPatcher {
|
|||||||
if (saved) {
|
if (saved) {
|
||||||
saveDocument(file, doc);
|
saveDocument(file, doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (SAXException | ParserConfigurationException | IOException
|
} catch (SAXException | ParserConfigurationException | IOException
|
||||||
| XPathExpressionException | TransformerException ignored) {
|
| XPathExpressionException | TransformerException ignored) {
|
||||||
}
|
}
|
||||||
@ -285,16 +277,16 @@ public final class ResXmlPatcher {
|
|||||||
/**
|
/**
|
||||||
* Finds key in strings.xml file and returns text value
|
* Finds key in strings.xml file and returns text value
|
||||||
*
|
*
|
||||||
* @param directory Root directory of apk
|
* @param apkDir Root directory of apk
|
||||||
* @param key String reference (ie @string/foo)
|
* @param key String reference (ie @string/foo)
|
||||||
* @return String|null
|
* @return String|null
|
||||||
*/
|
*/
|
||||||
public static String pullValueFromStrings(File directory, String key) {
|
public static String pullValueFromStrings(File apkDir, String key) {
|
||||||
if (key == null || ! key.contains("@")) {
|
if (key == null || ! key.contains("@")) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
File file = new File(directory, "/res/values/strings.xml");
|
File file = new File(apkDir, "/res/values/strings.xml");
|
||||||
key = key.replace("@string/", "");
|
key = key.replace("@string/", "");
|
||||||
|
|
||||||
if (!file.exists()) {
|
if (!file.exists()) {
|
||||||
@ -307,7 +299,6 @@ public final class ResXmlPatcher {
|
|||||||
|
|
||||||
Object result = expression.evaluate(doc, XPathConstants.STRING);
|
Object result = expression.evaluate(doc, XPathConstants.STRING);
|
||||||
return result != null ? (String) result : null;
|
return result != null ? (String) result : null;
|
||||||
|
|
||||||
} catch (SAXException | ParserConfigurationException | IOException | XPathExpressionException ignored) {
|
} catch (SAXException | ParserConfigurationException | IOException | XPathExpressionException ignored) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -316,16 +307,16 @@ public final class ResXmlPatcher {
|
|||||||
/**
|
/**
|
||||||
* Finds key in integers.xml file and returns text value
|
* Finds key in integers.xml file and returns text value
|
||||||
*
|
*
|
||||||
* @param directory Root directory of apk
|
* @param apkDir Root directory of apk
|
||||||
* @param key Integer reference (ie @integer/foo)
|
* @param key Integer reference (ie @integer/foo)
|
||||||
* @return String|null
|
* @return String|null
|
||||||
*/
|
*/
|
||||||
public static String pullValueFromIntegers(File directory, String key) {
|
public static String pullValueFromIntegers(File apkDir, String key) {
|
||||||
if (key == null || ! key.contains("@")) {
|
if (key == null || ! key.contains("@")) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
File file = new File(directory, "/res/values/integers.xml");
|
File file = new File(apkDir, "/res/values/integers.xml");
|
||||||
key = key.replace("@integer/", "");
|
key = key.replace("@integer/", "");
|
||||||
|
|
||||||
if (!file.exists()) {
|
if (!file.exists()) {
|
||||||
@ -350,9 +341,6 @@ public final class ResXmlPatcher {
|
|||||||
* @param file File representing AndroidManifest.xml
|
* @param file File representing AndroidManifest.xml
|
||||||
*/
|
*/
|
||||||
public static void removeManifestVersions(File file) {
|
public static void removeManifestVersions(File file) {
|
||||||
if (!file.exists()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
Document doc = loadDocument(file);
|
Document doc = loadDocument(file);
|
||||||
Node manifest = doc.getFirstChild();
|
Node manifest = doc.getFirstChild();
|
||||||
@ -390,7 +378,6 @@ public final class ResXmlPatcher {
|
|||||||
Node nodeAttr = attr.getNamedItem("package");
|
Node nodeAttr = attr.getNamedItem("package");
|
||||||
nodeAttr.setNodeValue(packageOriginal);
|
nodeAttr.setNodeValue(packageOriginal);
|
||||||
saveDocument(file, doc);
|
saveDocument(file, doc);
|
||||||
|
|
||||||
} catch (SAXException | ParserConfigurationException | IOException | TransformerException ignored) {
|
} catch (SAXException | ParserConfigurationException | IOException | TransformerException ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -401,9 +388,6 @@ public final class ResXmlPatcher {
|
|||||||
* @param file File for AndroidManifest.xml
|
* @param file File for AndroidManifest.xml
|
||||||
*/
|
*/
|
||||||
public static List<String> pullManifestFeatureFlags(File file) {
|
public static List<String> pullManifestFeatureFlags(File file) {
|
||||||
if (!file.exists()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
Document doc = loadDocument(file);
|
Document doc = loadDocument(file);
|
||||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||||
@ -425,7 +409,6 @@ public final class ResXmlPatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return featureFlags;
|
return featureFlags;
|
||||||
|
|
||||||
} catch (SAXException | ParserConfigurationException | IOException | XPathExpressionException ignored) {
|
} catch (SAXException | ParserConfigurationException | IOException | XPathExpressionException ignored) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -441,23 +424,22 @@ public final class ResXmlPatcher {
|
|||||||
*/
|
*/
|
||||||
public static Document loadDocument(File file)
|
public static Document loadDocument(File file)
|
||||||
throws IOException, SAXException, ParserConfigurationException {
|
throws IOException, SAXException, ParserConfigurationException {
|
||||||
|
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||||
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
|
factory.setFeature(FEATURE_DISABLE_DOCTYPE_DECL, true);
|
||||||
docFactory.setFeature(FEATURE_DISABLE_DOCTYPE_DECL, true);
|
factory.setFeature(FEATURE_LOAD_DTD, false);
|
||||||
docFactory.setFeature(FEATURE_LOAD_DTD, false);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
docFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, " ");
|
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, " ");
|
||||||
docFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, " ");
|
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, " ");
|
||||||
} catch (IllegalArgumentException ex) {
|
} catch (IllegalArgumentException ex) {
|
||||||
LOGGER.warning("JAXP 1.5 Support is required to validate XML");
|
LOGGER.warning("JAXP 1.5 Support is required to validate XML");
|
||||||
}
|
}
|
||||||
|
|
||||||
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
|
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||||
// Not using the parse(File) method on purpose, so that we can control when
|
// Not using the parse(File) method on purpose, so that we can control when
|
||||||
// to close it. Somehow parse(File) does not seem to close the file in all cases.
|
// to close it. Somehow parse(File) does not seem to close the file in all cases.
|
||||||
try (FileInputStream inputStream = new FileInputStream(file)) {
|
try (InputStream in = Files.newInputStream(file.toPath())) {
|
||||||
return docBuilder.parse(inputStream);
|
return builder.parse(in);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -472,7 +454,6 @@ public final class ResXmlPatcher {
|
|||||||
*/
|
*/
|
||||||
private static void saveDocument(File file, Document doc)
|
private static void saveDocument(File file, Document doc)
|
||||||
throws IOException, SAXException, ParserConfigurationException, TransformerException {
|
throws IOException, SAXException, ParserConfigurationException, TransformerException {
|
||||||
|
|
||||||
TransformerFactory factory = TransformerFactory.newInstance();
|
TransformerFactory factory = TransformerFactory.newInstance();
|
||||||
Transformer transformer = factory.newTransformer();
|
Transformer transformer = factory.newTransformer();
|
||||||
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
|
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
|
||||||
@ -480,16 +461,11 @@ public final class ResXmlPatcher {
|
|||||||
byte[] xmlDecl = "<?xml version=\"1.0\" encoding=\"utf-8\"?>".getBytes(StandardCharsets.US_ASCII);
|
byte[] xmlDecl = "<?xml version=\"1.0\" encoding=\"utf-8\"?>".getBytes(StandardCharsets.US_ASCII);
|
||||||
byte[] newLine = System.getProperty("line.separator").getBytes(StandardCharsets.US_ASCII);
|
byte[] newLine = System.getProperty("line.separator").getBytes(StandardCharsets.US_ASCII);
|
||||||
|
|
||||||
try (OutputStream output = Files.newOutputStream(file.toPath())) {
|
try (OutputStream out = Files.newOutputStream(file.toPath())) {
|
||||||
output.write(xmlDecl);
|
out.write(xmlDecl);
|
||||||
output.write(newLine);
|
out.write(newLine);
|
||||||
transformer.transform(new DOMSource(doc), new StreamResult(output));
|
transformer.transform(new DOMSource(doc), new StreamResult(out));
|
||||||
output.write(newLine);
|
out.write(newLine);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String FEATURE_LOAD_DTD = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
|
|
||||||
private static final String FEATURE_DISABLE_DOCTYPE_DECL = "http://apache.org/xml/features/disallow-doctype-decl";
|
|
||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(ResXmlPatcher.class.getName());
|
|
||||||
}
|
}
|
@ -29,30 +29,27 @@ import java.nio.file.Files;
|
|||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
public class SmaliBuilder {
|
public class SmaliBuilder {
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(SmaliBuilder.class.getName());
|
||||||
|
|
||||||
public static void build(File smaliDir, File dexFile, int apiLevel) throws AndrolibException {
|
private final ExtFile mSmaliDir;
|
||||||
new SmaliBuilder(smaliDir, dexFile, apiLevel).build();
|
private final int mApiLevel;
|
||||||
}
|
|
||||||
|
|
||||||
private SmaliBuilder(File smaliDir, File dexFile, int apiLevel) {
|
public SmaliBuilder(File smaliDir, int apiLevel) {
|
||||||
mSmaliDir = new ExtFile(smaliDir);
|
mSmaliDir = new ExtFile(smaliDir);
|
||||||
mDexFile = dexFile;
|
|
||||||
mApiLevel = apiLevel;
|
mApiLevel = apiLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void build() throws AndrolibException {
|
public void build(File dexFile) throws AndrolibException {
|
||||||
try {
|
try {
|
||||||
DexBuilder dexBuilder;
|
DexBuilder dexBuilder = mApiLevel > 0
|
||||||
if (mApiLevel > 0) {
|
? new DexBuilder(Opcodes.forApi(mApiLevel))
|
||||||
dexBuilder = new DexBuilder(Opcodes.forApi(mApiLevel));
|
: new DexBuilder(Opcodes.getDefault());
|
||||||
} else {
|
|
||||||
dexBuilder = new DexBuilder(Opcodes.getDefault());
|
|
||||||
}
|
|
||||||
|
|
||||||
for (String fileName : mSmaliDir.getDirectory().getFiles(true)) {
|
for (String fileName : mSmaliDir.getDirectory().getFiles(true)) {
|
||||||
buildFile(fileName, dexBuilder);
|
buildFile(fileName, dexBuilder);
|
||||||
}
|
}
|
||||||
dexBuilder.writeTo(new FileDataStore(new File(mDexFile.getAbsolutePath())));
|
|
||||||
|
dexBuilder.writeTo(new FileDataStore(dexFile));
|
||||||
} catch (DirectoryException | IOException | RuntimeException ex) {
|
} catch (DirectoryException | IOException | RuntimeException ex) {
|
||||||
throw new AndrolibException("Could not smali folder: " + mSmaliDir.getName(), ex);
|
throw new AndrolibException("Could not smali folder: " + mSmaliDir.getName(), ex);
|
||||||
}
|
}
|
||||||
@ -82,10 +79,4 @@ public class SmaliBuilder {
|
|||||||
throw ex;
|
throw ex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final ExtFile mSmaliDir;
|
|
||||||
private final File mDexFile;
|
|
||||||
private final int mApiLevel;
|
|
||||||
|
|
||||||
private final static Logger LOGGER = Logger.getLogger(SmaliBuilder.class.getName());
|
|
||||||
}
|
}
|
||||||
|
@ -30,21 +30,19 @@ import com.android.tools.smali.dexlib2.iface.MultiDexContainer;
|
|||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
|
||||||
public class SmaliDecoder {
|
public class SmaliDecoder {
|
||||||
|
private final File mApkFile;
|
||||||
|
private final String mDexName;
|
||||||
|
private final boolean mBakDeb;
|
||||||
|
private final int mApiLevel;
|
||||||
|
|
||||||
public static DexFile decode(File apkFile, File outDir, String dexName, boolean bakDeb, int apiLevel)
|
public SmaliDecoder(File apkFile, String dexName, boolean bakDeb, int apiLevel) {
|
||||||
throws AndrolibException {
|
|
||||||
return new SmaliDecoder(apkFile, outDir, dexName, bakDeb, apiLevel).decode();
|
|
||||||
}
|
|
||||||
|
|
||||||
private SmaliDecoder(File apkFile, File outDir, String dexName, boolean bakDeb, int apiLevel) {
|
|
||||||
mApkFile = apkFile;
|
mApkFile = apkFile;
|
||||||
mOutDir = outDir;
|
|
||||||
mDexName = dexName;
|
mDexName = dexName;
|
||||||
mBakDeb = bakDeb;
|
mBakDeb = bakDeb;
|
||||||
mApiLevel = apiLevel;
|
mApiLevel = apiLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
private DexFile decode() throws AndrolibException {
|
public DexFile decode(File outDir) throws AndrolibException {
|
||||||
try {
|
try {
|
||||||
final BaksmaliOptions options = new BaksmaliOptions();
|
final BaksmaliOptions options = new BaksmaliOptions();
|
||||||
|
|
||||||
@ -93,20 +91,14 @@ public class SmaliDecoder {
|
|||||||
|
|
||||||
if (dexFile instanceof DexBackedOdexFile) {
|
if (dexFile instanceof DexBackedOdexFile) {
|
||||||
options.inlineResolver =
|
options.inlineResolver =
|
||||||
InlineMethodResolver.createInlineMethodResolver(((DexBackedOdexFile)dexFile).getOdexVersion());
|
InlineMethodResolver.createInlineMethodResolver(((DexBackedOdexFile) dexFile).getOdexVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
Baksmali.disassembleDexFile(dexFile, mOutDir, jobs, options);
|
Baksmali.disassembleDexFile(dexFile, outDir, jobs, options);
|
||||||
|
|
||||||
return dexFile;
|
return dexFile;
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
throw new AndrolibException("Could not baksmali file: " + mDexName, ex);
|
throw new AndrolibException("Could not baksmali file: " + mDexName, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final File mApkFile;
|
|
||||||
private final File mOutDir;
|
|
||||||
private final String mDexName;
|
|
||||||
private final boolean mBakDeb;
|
|
||||||
private final int mApiLevel;
|
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ import javax.xml.parsers.DocumentBuilder;
|
|||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
import java.nio.file.Files;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
@ -39,6 +40,16 @@ import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual;
|
|||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
public class BaseTest {
|
public class BaseTest {
|
||||||
|
protected static final Logger LOGGER = Logger.getLogger(BaseTest.class.getName());
|
||||||
|
|
||||||
|
private static final String ACCESS_EXTERNAL_DTD = "http://javax.xml.XMLConstants/property/accessExternalDTD";
|
||||||
|
private static final String ACCESS_EXTERNAL_SCHEMA = "http://javax.xml.XMLConstants/property/accessExternalSchema";
|
||||||
|
private static final String FEATURE_LOAD_DTD = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
|
||||||
|
private static final String FEATURE_DISABLE_DOCTYPE_DECL = "http://apache.org/xml/features/disallow-doctype-decl";
|
||||||
|
|
||||||
|
protected static ExtFile sTmpDir;
|
||||||
|
protected static ExtFile sTestOrigDir;
|
||||||
|
protected static ExtFile sTestNewDir;
|
||||||
|
|
||||||
protected void compareBinaryFolder(String path, boolean res) throws BrutException, IOException {
|
protected void compareBinaryFolder(String path, boolean res) throws BrutException, IOException {
|
||||||
boolean exists = true;
|
boolean exists = true;
|
||||||
@ -125,24 +136,24 @@ public class BaseTest {
|
|||||||
assertTrue(path + ": " + diff.getAllDifferences().toString(), diff.similar());
|
assertTrue(path + ": " + diff.getAllDifferences().toString(), diff.similar());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static Document loadDocument(File file) throws IOException, SAXException, ParserConfigurationException {
|
protected static Document loadDocument(File file)
|
||||||
|
throws IOException, SAXException, ParserConfigurationException {
|
||||||
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
|
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||||
docFactory.setFeature(FEATURE_DISABLE_DOCTYPE_DECL, true);
|
factory.setFeature(FEATURE_DISABLE_DOCTYPE_DECL, true);
|
||||||
docFactory.setFeature(FEATURE_LOAD_DTD, false);
|
factory.setFeature(FEATURE_LOAD_DTD, false);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
docFactory.setAttribute(ACCESS_EXTERNAL_DTD, " ");
|
factory.setAttribute(ACCESS_EXTERNAL_DTD, " ");
|
||||||
docFactory.setAttribute(ACCESS_EXTERNAL_SCHEMA, " ");
|
factory.setAttribute(ACCESS_EXTERNAL_SCHEMA, " ");
|
||||||
} catch (IllegalArgumentException ex) {
|
} catch (IllegalArgumentException ex) {
|
||||||
LOGGER.warning("JAXP 1.5 Support is required to validate XML");
|
LOGGER.warning("JAXP 1.5 Support is required to validate XML");
|
||||||
}
|
}
|
||||||
|
|
||||||
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
|
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||||
// Not using the parse(File) method on purpose, so that we can control when
|
// Not using the parse(File) method on purpose, so that we can control when
|
||||||
// to close it. Somehow parse(File) does not seem to close the file in all cases.
|
// to close it. Somehow parse(File) does not seem to close the file in all cases.
|
||||||
try (FileInputStream inputStream = new FileInputStream(file)) {
|
try (InputStream in = Files.newInputStream(file.toPath())) {
|
||||||
return docBuilder.parse(inputStream);
|
return builder.parse(in);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,21 +175,11 @@ public class BaseTest {
|
|||||||
NodeList children = element.getChildNodes();
|
NodeList children = element.getChildNodes();
|
||||||
for (int i = 0; i < children.getLength(); i++) {
|
for (int i = 0; i < children.getLength(); i++) {
|
||||||
Node child = children.item(i);
|
Node child = children.item(i);
|
||||||
if (child.getNodeType() == Node.ELEMENT_NODE && resourceNameContains((Element) child, name)) {
|
if (child.getNodeType() == Node.ELEMENT_NODE
|
||||||
|
&& resourceNameContains((Element) child, name)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static ExtFile sTmpDir;
|
|
||||||
protected static ExtFile sTestOrigDir;
|
|
||||||
protected static ExtFile sTestNewDir;
|
|
||||||
|
|
||||||
protected final static Logger LOGGER = Logger.getLogger(BaseTest.class.getName());
|
|
||||||
|
|
||||||
private static final String ACCESS_EXTERNAL_DTD = "http://javax.xml.XMLConstants/property/accessExternalDTD";
|
|
||||||
private static final String ACCESS_EXTERNAL_SCHEMA = "http://javax.xml.XMLConstants/property/accessExternalSchema";
|
|
||||||
private static final String FEATURE_LOAD_DTD = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
|
|
||||||
private static final String FEATURE_DISABLE_DOCTYPE_DECL = "http://apache.org/xml/features/disallow-doctype-decl";
|
|
||||||
}
|
}
|
||||||
|
@ -18,9 +18,9 @@ package brut.androlib;
|
|||||||
|
|
||||||
import brut.androlib.exceptions.AndrolibException;
|
import brut.androlib.exceptions.AndrolibException;
|
||||||
import brut.androlib.res.Framework;
|
import brut.androlib.res.Framework;
|
||||||
import brut.androlib.res.xml.ResXmlPatcher;
|
import brut.androlib.res.xml.ResXmlUtils;
|
||||||
import brut.common.BrutException;
|
import brut.common.BrutException;
|
||||||
import brut.directory.DirUtil;
|
import brut.directory.DirUtils;
|
||||||
import brut.directory.Directory;
|
import brut.directory.Directory;
|
||||||
import brut.directory.FileDirectory;
|
import brut.directory.FileDirectory;
|
||||||
import brut.util.OS;
|
import brut.util.OS;
|
||||||
@ -42,7 +42,11 @@ import java.nio.file.Files;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public abstract class TestUtils {
|
public final class TestUtils {
|
||||||
|
|
||||||
|
private TestUtils() {
|
||||||
|
// Private constructor for utility class
|
||||||
|
}
|
||||||
|
|
||||||
public static Map<String, String> parseStringsXml(File file) throws BrutException {
|
public static Map<String, String> parseStringsXml(File file) throws BrutException {
|
||||||
try {
|
try {
|
||||||
@ -68,28 +72,28 @@ public abstract class TestUtils {
|
|||||||
|
|
||||||
public static Document getDocumentFromFile(File file) throws BrutException {
|
public static Document getDocumentFromFile(File file) throws BrutException {
|
||||||
try {
|
try {
|
||||||
return ResXmlPatcher.loadDocument(file);
|
return ResXmlUtils.loadDocument(file);
|
||||||
} catch (IOException | SAXException | ParserConfigurationException ex) {
|
} catch (IOException | SAXException | ParserConfigurationException ex) {
|
||||||
throw new BrutException(ex);
|
throw new BrutException(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void copyResourceDir(Class<?> class_, String dirPath, File out) throws BrutException {
|
public static void copyResourceDir(Class<?> clz, String dirPath, File out) throws BrutException {
|
||||||
if (!out.exists()) {
|
if (!out.exists()) {
|
||||||
out.mkdirs();
|
out.mkdirs();
|
||||||
}
|
}
|
||||||
copyResourceDir(class_, dirPath, new FileDirectory(out));
|
copyResourceDir(clz, dirPath, new FileDirectory(out));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void copyResourceDir(Class<?> class_, String dirPath, Directory out) throws BrutException {
|
public static void copyResourceDir(Class<?> clz, String dirPath, Directory out) throws BrutException {
|
||||||
if (class_ == null) {
|
if (clz == null) {
|
||||||
class_ = Class.class;
|
clz = Class.class;
|
||||||
}
|
}
|
||||||
|
|
||||||
URL dirURL = class_.getClassLoader().getResource(dirPath);
|
URL dirURL = clz.getClassLoader().getResource(dirPath);
|
||||||
if (dirURL != null && dirURL.getProtocol().equals("file")) {
|
if (dirURL != null && dirURL.getProtocol().equals("file")) {
|
||||||
try {
|
try {
|
||||||
DirUtil.copyToDir(new FileDirectory(dirURL.getFile()), out);
|
DirUtils.copyToDir(new FileDirectory(dirURL.getFile()), out);
|
||||||
} catch (UnsupportedEncodingException ex) {
|
} catch (UnsupportedEncodingException ex) {
|
||||||
throw new BrutException(ex);
|
throw new BrutException(ex);
|
||||||
}
|
}
|
||||||
@ -97,15 +101,15 @@ public abstract class TestUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (dirURL == null) {
|
if (dirURL == null) {
|
||||||
String className = class_.getName().replace(".", "/") + ".class";
|
String className = clz.getName().replace(".", "/") + ".class";
|
||||||
dirURL = class_.getClassLoader().getResource(className);
|
dirURL = clz.getClassLoader().getResource(className);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dirURL.getProtocol().equals("jar")) {
|
if (dirURL.getProtocol().equals("jar")) {
|
||||||
String jarPath;
|
String jarPath;
|
||||||
try {
|
try {
|
||||||
jarPath = URLDecoder.decode(dirURL.getPath().substring(5, dirURL.getPath().indexOf("!")), "UTF-8");
|
jarPath = URLDecoder.decode(dirURL.getPath().substring(5, dirURL.getPath().indexOf("!")), "UTF-8");
|
||||||
DirUtil.copyToDir(new FileDirectory(jarPath), out);
|
DirUtils.copyToDir(new FileDirectory(jarPath), out);
|
||||||
} catch (UnsupportedEncodingException ex) {
|
} catch (UnsupportedEncodingException ex) {
|
||||||
throw new BrutException(ex);
|
throw new BrutException(ex);
|
||||||
}
|
}
|
||||||
@ -122,11 +126,12 @@ public abstract class TestUtils {
|
|||||||
|
|
||||||
public static byte[] readHeaderOfFile(File file, int size) throws IOException {
|
public static byte[] readHeaderOfFile(File file, int size) throws IOException {
|
||||||
byte[] buffer = new byte[size];
|
byte[] buffer = new byte[size];
|
||||||
InputStream inputStream = Files.newInputStream(file.toPath());
|
|
||||||
if (inputStream.read(buffer) != buffer.length) {
|
try (InputStream in = Files.newInputStream(file.toPath())) {
|
||||||
throw new IOException("File size too small for buffer length: " + size);
|
if (in.read(buffer) != buffer.length) {
|
||||||
|
throw new IOException("File size too small for buffer length: " + size);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
inputStream.close();
|
|
||||||
|
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ import java.io.File;
|
|||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
public class AndroidOreoNotSparseTest extends BaseTest {
|
public class AndroidOreoNotSparseTest extends BaseTest {
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void beforeClass() throws Exception {
|
public static void beforeClass() throws Exception {
|
||||||
TestUtils.cleanFrameworkFile();
|
TestUtils.cleanFrameworkFile();
|
||||||
|
@ -30,6 +30,7 @@ import java.io.File;
|
|||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
public class AndroidOreoSparseTest extends BaseTest {
|
public class AndroidOreoSparseTest extends BaseTest {
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void beforeClass() throws Exception {
|
public static void beforeClass() throws Exception {
|
||||||
TestUtils.cleanFrameworkFile();
|
TestUtils.cleanFrameworkFile();
|
||||||
|
@ -33,6 +33,12 @@ import java.util.logging.Logger;
|
|||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
public class EmptyResourcesArscTest {
|
public class EmptyResourcesArscTest {
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(EmptyResourcesArscTest.class.getName());
|
||||||
|
|
||||||
|
private static ExtFile sTmpDir;
|
||||||
|
private static ExtFile sTestOrigDir;
|
||||||
|
private static ExtFile sTestNewDir;
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void beforeClass() throws Exception {
|
public static void beforeClass() throws Exception {
|
||||||
TestUtils.cleanFrameworkFile();
|
TestUtils.cleanFrameworkFile();
|
||||||
@ -64,10 +70,4 @@ public class EmptyResourcesArscTest {
|
|||||||
assertTrue(sTestNewDir.isDirectory());
|
assertTrue(sTestNewDir.isDirectory());
|
||||||
assertTrue(sTestOrigDir.isDirectory());
|
assertTrue(sTestOrigDir.isDirectory());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ExtFile sTmpDir;
|
|
||||||
private static ExtFile sTestOrigDir;
|
|
||||||
private static ExtFile sTestNewDir;
|
|
||||||
|
|
||||||
private final static Logger LOGGER = Logger.getLogger(EmptyResourcesArscTest.class.getName());
|
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,6 @@ import javax.xml.xpath.XPathConstants;
|
|||||||
import javax.xml.xpath.XPathFactory;
|
import javax.xml.xpath.XPathFactory;
|
||||||
import javax.xml.xpath.XPathExpression;
|
import javax.xml.xpath.XPathExpression;
|
||||||
import javax.xml.xpath.XPathExpressionException;
|
import javax.xml.xpath.XPathExpressionException;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
@ -88,20 +87,19 @@ public class NetworkConfigTest extends BaseTest {
|
|||||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||||
factory.setNamespaceAware(true);
|
factory.setNamespaceAware(true);
|
||||||
DocumentBuilder builder = factory.newDocumentBuilder();
|
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||||
Document document = builder.parse(new ByteArrayInputStream(obtained.getBytes()));
|
Document doc = builder.parse(new ByteArrayInputStream(obtained.getBytes()));
|
||||||
|
|
||||||
// XPath expression to check for user and system certificates
|
// XPath expression to check for user and system certificates
|
||||||
XPathFactory xPathFactory = XPathFactory.newInstance();
|
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||||
XPath xpath = xPathFactory.newXPath();
|
|
||||||
|
|
||||||
// Check if 'system' certificate exists
|
// Check if 'system' certificate exists
|
||||||
XPathExpression systemCertExpr = xpath.compile("//certificates[@src='system']");
|
XPathExpression systemCertExpr = xPath.compile("//certificates[@src='system']");
|
||||||
NodeList systemCertNodes = (NodeList) systemCertExpr.evaluate(document, XPathConstants.NODESET);
|
NodeList systemCertNodes = (NodeList) systemCertExpr.evaluate(doc, XPathConstants.NODESET);
|
||||||
assertTrue(systemCertNodes.getLength() > 0);
|
assertTrue(systemCertNodes.getLength() > 0);
|
||||||
|
|
||||||
// Check if 'user' certificate exists
|
// Check if 'user' certificate exists
|
||||||
XPathExpression userCertExpr = xpath.compile("//certificates[@src='user']");
|
XPathExpression userCertExpr = xPath.compile("//certificates[@src='user']");
|
||||||
NodeList userCertNodes = (NodeList) userCertExpr.evaluate(document, XPathConstants.NODESET);
|
NodeList userCertNodes = (NodeList) userCertExpr.evaluate(doc, XPathConstants.NODESET);
|
||||||
assertTrue(userCertNodes.getLength() > 0);
|
assertTrue(userCertNodes.getLength() > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@ import org.junit.Test;
|
|||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
public class NonStandardPkgIdTest extends BaseTest {
|
public class NonStandardPkgIdTest extends BaseTest {
|
||||||
|
private static ResTable mResTable;
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void beforeClass() throws Exception {
|
public static void beforeClass() throws Exception {
|
||||||
@ -90,6 +91,4 @@ public class NonStandardPkgIdTest extends BaseTest {
|
|||||||
assertEquals(0x80, mResTable.getResSpec(0x80020001).getPackage().getId());
|
assertEquals(0x80, mResTable.getResSpec(0x80020001).getPackage().getId());
|
||||||
assertEquals(0x80, mResTable.getResSpec(0x80030000).getPackage().getId());
|
assertEquals(0x80, mResTable.getResSpec(0x80030000).getPackage().getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ResTable mResTable;
|
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ public class ApkInfoReaderTest {
|
|||||||
assertNotNull(apkInfo.usesFramework);
|
assertNotNull(apkInfo.usesFramework);
|
||||||
assertNotNull(apkInfo.usesFramework.ids);
|
assertNotNull(apkInfo.usesFramework.ids);
|
||||||
assertEquals(1, apkInfo.usesFramework.ids.size());
|
assertEquals(1, apkInfo.usesFramework.ids.size());
|
||||||
assertEquals(1, (long)apkInfo.usesFramework.ids.get(0));
|
assertEquals(1, (long) apkInfo.usesFramework.ids.get(0));
|
||||||
assertNull(apkInfo.usesFramework.tag);
|
assertNull(apkInfo.usesFramework.tag);
|
||||||
assertNotNull(apkInfo.versionInfo);
|
assertNotNull(apkInfo.versionInfo);
|
||||||
assertNull(apkInfo.versionInfo.versionCode);
|
assertNull(apkInfo.versionInfo.versionCode);
|
||||||
@ -50,7 +50,7 @@ public class ApkInfoReaderTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testStandard() throws AndrolibException {
|
public void testStandard() throws AndrolibException {
|
||||||
ApkInfo apkInfo = ApkInfo.load(
|
ApkInfo apkInfo = ApkInfo.load(
|
||||||
this.getClass().getResourceAsStream("/apk/standard.yml"));
|
getClass().getResourceAsStream("/apk/standard.yml"));
|
||||||
checkStandard(apkInfo);
|
checkStandard(apkInfo);
|
||||||
assertEquals("2.8.1", apkInfo.version);
|
assertEquals("2.8.1", apkInfo.version);
|
||||||
}
|
}
|
||||||
@ -58,7 +58,7 @@ public class ApkInfoReaderTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testUnknownFields() throws AndrolibException {
|
public void testUnknownFields() throws AndrolibException {
|
||||||
ApkInfo apkInfo = ApkInfo.load(
|
ApkInfo apkInfo = ApkInfo.load(
|
||||||
this.getClass().getResourceAsStream("/apk/unknown_fields.yml"));
|
getClass().getResourceAsStream("/apk/unknown_fields.yml"));
|
||||||
checkStandard(apkInfo);
|
checkStandard(apkInfo);
|
||||||
assertEquals("2.8.1", apkInfo.version);
|
assertEquals("2.8.1", apkInfo.version);
|
||||||
}
|
}
|
||||||
@ -66,7 +66,7 @@ public class ApkInfoReaderTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testSkipIncorrectIndent() throws AndrolibException {
|
public void testSkipIncorrectIndent() throws AndrolibException {
|
||||||
ApkInfo apkInfo = ApkInfo.load(
|
ApkInfo apkInfo = ApkInfo.load(
|
||||||
this.getClass().getResourceAsStream("/apk/skip_incorrect_indent.yml"));
|
getClass().getResourceAsStream("/apk/skip_incorrect_indent.yml"));
|
||||||
checkStandard(apkInfo);
|
checkStandard(apkInfo);
|
||||||
assertNotEquals("2.0.0", apkInfo.version);
|
assertNotEquals("2.0.0", apkInfo.version);
|
||||||
}
|
}
|
||||||
@ -74,7 +74,7 @@ public class ApkInfoReaderTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testFirstIncorrectIndent() throws AndrolibException {
|
public void testFirstIncorrectIndent() throws AndrolibException {
|
||||||
ApkInfo apkInfo = ApkInfo.load(
|
ApkInfo apkInfo = ApkInfo.load(
|
||||||
this.getClass().getResourceAsStream("/apk/first_incorrect_indent.yml"));
|
getClass().getResourceAsStream("/apk/first_incorrect_indent.yml"));
|
||||||
checkStandard(apkInfo);
|
checkStandard(apkInfo);
|
||||||
assertNotEquals("2.0.0", apkInfo.version);
|
assertNotEquals("2.0.0", apkInfo.version);
|
||||||
}
|
}
|
||||||
@ -82,13 +82,13 @@ public class ApkInfoReaderTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testUnknownFiles() throws AndrolibException {
|
public void testUnknownFiles() throws AndrolibException {
|
||||||
ApkInfo apkInfo = ApkInfo.load(
|
ApkInfo apkInfo = ApkInfo.load(
|
||||||
this.getClass().getResourceAsStream("/apk/unknown_files.yml"));
|
getClass().getResourceAsStream("/apk/unknown_files.yml"));
|
||||||
assertEquals("2.0.0", apkInfo.version);
|
assertEquals("2.0.0", apkInfo.version);
|
||||||
assertEquals("testapp.apk", apkInfo.apkFileName);
|
assertEquals("testapp.apk", apkInfo.apkFileName);
|
||||||
assertFalse(apkInfo.isFrameworkApk);
|
assertFalse(apkInfo.isFrameworkApk);
|
||||||
assertNotNull(apkInfo.usesFramework);
|
assertNotNull(apkInfo.usesFramework);
|
||||||
assertEquals(1, apkInfo.usesFramework.ids.size());
|
assertEquals(1, apkInfo.usesFramework.ids.size());
|
||||||
assertEquals(1, (long)apkInfo.usesFramework.ids.get(0));
|
assertEquals(1, (long) apkInfo.usesFramework.ids.get(0));
|
||||||
assertNotNull(apkInfo.packageInfo);
|
assertNotNull(apkInfo.packageInfo);
|
||||||
assertEquals("127", apkInfo.packageInfo.forcedPackageId);
|
assertEquals("127", apkInfo.packageInfo.forcedPackageId);
|
||||||
assertNotNull(apkInfo.versionInfo);
|
assertNotNull(apkInfo.versionInfo);
|
||||||
@ -106,13 +106,13 @@ public class ApkInfoReaderTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testUlist_with_indent() throws AndrolibException {
|
public void testUlist_with_indent() throws AndrolibException {
|
||||||
ApkInfo apkInfo = ApkInfo.load(
|
ApkInfo apkInfo = ApkInfo.load(
|
||||||
this.getClass().getResourceAsStream("/apk/list_with_indent.yml"));
|
getClass().getResourceAsStream("/apk/list_with_indent.yml"));
|
||||||
assertEquals("2.8.0", apkInfo.version);
|
assertEquals("2.8.0", apkInfo.version);
|
||||||
assertEquals("basic.apk", apkInfo.apkFileName);
|
assertEquals("basic.apk", apkInfo.apkFileName);
|
||||||
assertFalse(apkInfo.isFrameworkApk);
|
assertFalse(apkInfo.isFrameworkApk);
|
||||||
assertNotNull(apkInfo.usesFramework);
|
assertNotNull(apkInfo.usesFramework);
|
||||||
assertEquals(1, apkInfo.usesFramework.ids.size());
|
assertEquals(1, apkInfo.usesFramework.ids.size());
|
||||||
assertEquals(1, (long)apkInfo.usesFramework.ids.get(0));
|
assertEquals(1, (long) apkInfo.usesFramework.ids.get(0));
|
||||||
assertEquals("tag", apkInfo.usesFramework.tag);
|
assertEquals("tag", apkInfo.usesFramework.tag);
|
||||||
assertNotNull(apkInfo.packageInfo);
|
assertNotNull(apkInfo.packageInfo);
|
||||||
assertEquals("127", apkInfo.packageInfo.forcedPackageId);
|
assertEquals("127", apkInfo.packageInfo.forcedPackageId);
|
||||||
|
@ -23,25 +23,24 @@ import org.junit.Test;
|
|||||||
import org.junit.rules.TemporaryFolder;
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
public class ApkInfoSerializationTest {
|
public class ApkInfoSerializationTest {
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public TemporaryFolder folder = new TemporaryFolder();
|
public TemporaryFolder folder = new TemporaryFolder();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void checkApkInfoSerialization() throws IOException, AndrolibException {
|
public void checkApkInfoSerialization() throws IOException, AndrolibException {
|
||||||
ApkInfo control = ApkInfo.load(
|
ApkInfo control = ApkInfo.load(
|
||||||
this.getClass().getResourceAsStream("/apk/unknown_files.yml"));
|
getClass().getResourceAsStream("/apk/unknown_files.yml"));
|
||||||
check(control);
|
check(control);
|
||||||
|
|
||||||
File savedApkInfo = folder.newFile("saved.yml");
|
File savedApkInfo = folder.newFile("saved.yml");
|
||||||
control.save(savedApkInfo);
|
control.save(savedApkInfo);
|
||||||
try (FileInputStream fis = new FileInputStream(savedApkInfo)) {
|
try (InputStream in = Files.newInputStream(savedApkInfo.toPath())) {
|
||||||
ApkInfo saved = ApkInfo.load(fis);
|
check(ApkInfo.load(in));
|
||||||
check(saved);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,7 +50,7 @@ public class ApkInfoSerializationTest {
|
|||||||
assertFalse(apkInfo.isFrameworkApk);
|
assertFalse(apkInfo.isFrameworkApk);
|
||||||
assertNotNull(apkInfo.usesFramework);
|
assertNotNull(apkInfo.usesFramework);
|
||||||
assertEquals(1, apkInfo.usesFramework.ids.size());
|
assertEquals(1, apkInfo.usesFramework.ids.size());
|
||||||
assertEquals(1, (long)apkInfo.usesFramework.ids.get(0));
|
assertEquals(1, (long) apkInfo.usesFramework.ids.get(0));
|
||||||
assertNotNull(apkInfo.packageInfo);
|
assertNotNull(apkInfo.packageInfo);
|
||||||
assertEquals("127", apkInfo.packageInfo.forcedPackageId);
|
assertEquals("127", apkInfo.packageInfo.forcedPackageId);
|
||||||
assertNotNull(apkInfo.versionInfo);
|
assertNotNull(apkInfo.versionInfo);
|
||||||
|
@ -26,7 +26,7 @@ public class ConsistentPropertyTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testAssertingAllKnownApkInfoProperties() throws AndrolibException {
|
public void testAssertingAllKnownApkInfoProperties() throws AndrolibException {
|
||||||
ApkInfo apkInfo = ApkInfo.load(
|
ApkInfo apkInfo = ApkInfo.load(
|
||||||
this.getClass().getResourceAsStream("/apk/basic.yml"));
|
getClass().getResourceAsStream("/apk/basic.yml"));
|
||||||
|
|
||||||
assertEquals("2.8.0", apkInfo.version);
|
assertEquals("2.8.0", apkInfo.version);
|
||||||
assertEquals("basic.apk", apkInfo.apkFileName);
|
assertEquals("basic.apk", apkInfo.apkFileName);
|
||||||
|
@ -26,7 +26,7 @@ public class DoNotCompressHieroglyphTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testHieroglyph() throws AndrolibException {
|
public void testHieroglyph() throws AndrolibException {
|
||||||
ApkInfo apkInfo = ApkInfo.load(
|
ApkInfo apkInfo = ApkInfo.load(
|
||||||
this.getClass().getResourceAsStream("/apk/donotcompress_with_hieroglyph.yml"));
|
getClass().getResourceAsStream("/apk/donotcompress_with_hieroglyph.yml"));
|
||||||
assertEquals("2.0.0", apkInfo.version);
|
assertEquals("2.0.0", apkInfo.version);
|
||||||
assertEquals("testapp.apk", apkInfo.apkFileName);
|
assertEquals("testapp.apk", apkInfo.apkFileName);
|
||||||
assertEquals(2, apkInfo.doNotCompress.size());
|
assertEquals(2, apkInfo.doNotCompress.size());
|
||||||
|
@ -26,7 +26,7 @@ public class MaliciousYamlTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testMaliciousYaml() throws AndrolibException {
|
public void testMaliciousYaml() throws AndrolibException {
|
||||||
ApkInfo apkInfo = ApkInfo.load(
|
ApkInfo apkInfo = ApkInfo.load(
|
||||||
this.getClass().getResourceAsStream("/apk/cve20220476.yml"));
|
getClass().getResourceAsStream("/apk/cve20220476.yml"));
|
||||||
assertEquals("2.6.1-ddc4bb-SNAPSHOT", apkInfo.version);
|
assertEquals("2.6.1-ddc4bb-SNAPSHOT", apkInfo.version);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,8 +34,7 @@ import java.util.Arrays;
|
|||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
public class ForceManifestDecodeNoResourcesTest extends BaseTest {
|
public class ForceManifestDecodeNoResourcesTest extends BaseTest {
|
||||||
|
private static final byte[] XML_HEADER = {
|
||||||
private final byte[] xmlHeader = new byte[] {
|
|
||||||
0x3C, // <
|
0x3C, // <
|
||||||
0x3F, // ?
|
0x3F, // ?
|
||||||
0x78, // x
|
0x78, // x
|
||||||
@ -68,7 +67,7 @@ public class ForceManifestDecodeNoResourcesTest extends BaseTest {
|
|||||||
// let's probe filetype of manifest, we should detect XML
|
// let's probe filetype of manifest, we should detect XML
|
||||||
File manifestFile = new File(output + File.separator + "AndroidManifest.xml");
|
File manifestFile = new File(output + File.separator + "AndroidManifest.xml");
|
||||||
byte[] magic = TestUtils.readHeaderOfFile(manifestFile, 6);
|
byte[] magic = TestUtils.readHeaderOfFile(manifestFile, 6);
|
||||||
assertArrayEquals(this.xmlHeader, magic);
|
assertArrayEquals(XML_HEADER, magic);
|
||||||
|
|
||||||
// confirm resources.arsc still exists, as its raw
|
// confirm resources.arsc still exists, as its raw
|
||||||
File resourcesArsc = new File(output + File.separator + "resources.arsc");
|
File resourcesArsc = new File(output + File.separator + "resources.arsc");
|
||||||
@ -87,7 +86,7 @@ public class ForceManifestDecodeNoResourcesTest extends BaseTest {
|
|||||||
// let's probe filetype of manifest, we should detect XML
|
// let's probe filetype of manifest, we should detect XML
|
||||||
File manifestFile = new File(output + File.separator + "AndroidManifest.xml");
|
File manifestFile = new File(output + File.separator + "AndroidManifest.xml");
|
||||||
byte[] magic = TestUtils.readHeaderOfFile(manifestFile, 6);
|
byte[] magic = TestUtils.readHeaderOfFile(manifestFile, 6);
|
||||||
assertArrayEquals(this.xmlHeader, magic);
|
assertArrayEquals(XML_HEADER, magic);
|
||||||
|
|
||||||
// confirm resources.arsc does not exist
|
// confirm resources.arsc does not exist
|
||||||
File resourcesArsc = new File(output + File.separator + "resources.arsc");
|
File resourcesArsc = new File(output + File.separator + "resources.arsc");
|
||||||
@ -106,7 +105,7 @@ public class ForceManifestDecodeNoResourcesTest extends BaseTest {
|
|||||||
// lets probe filetype of manifest, we should detect XML
|
// lets probe filetype of manifest, we should detect XML
|
||||||
File manifestFile = new File(output + File.separator + "AndroidManifest.xml");
|
File manifestFile = new File(output + File.separator + "AndroidManifest.xml");
|
||||||
byte[] magic = TestUtils.readHeaderOfFile(manifestFile, 6);
|
byte[] magic = TestUtils.readHeaderOfFile(manifestFile, 6);
|
||||||
assertArrayEquals(this.xmlHeader, magic);
|
assertArrayEquals(XML_HEADER, magic);
|
||||||
|
|
||||||
// confirm resources.arsc does not exist
|
// confirm resources.arsc does not exist
|
||||||
File resourcesArsc = new File(output + File.separator + "resources.arsc");
|
File resourcesArsc = new File(output + File.separator + "resources.arsc");
|
||||||
@ -125,7 +124,7 @@ public class ForceManifestDecodeNoResourcesTest extends BaseTest {
|
|||||||
// lets probe filetype of manifest, we should not detect XML
|
// lets probe filetype of manifest, we should not detect XML
|
||||||
File manifestFile = new File(output + File.separator + "AndroidManifest.xml");
|
File manifestFile = new File(output + File.separator + "AndroidManifest.xml");
|
||||||
byte[] magic = TestUtils.readHeaderOfFile(manifestFile, 6);
|
byte[] magic = TestUtils.readHeaderOfFile(manifestFile, 6);
|
||||||
assertFalse(Arrays.equals(this.xmlHeader, magic));
|
assertFalse(Arrays.equals(XML_HEADER, magic));
|
||||||
|
|
||||||
// confirm resources.arsc exists
|
// confirm resources.arsc exists
|
||||||
File resourcesArsc = new File(output + File.separator + "resources.arsc");
|
File resourcesArsc = new File(output + File.separator + "resources.arsc");
|
||||||
|
@ -29,10 +29,12 @@ import org.junit.Test;
|
|||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
public class MissingDiv9PatchTest extends BaseTest {
|
public class MissingDiv9PatchTest extends BaseTest {
|
||||||
|
private static final int NP_COLOR = 0xff000000;
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void beforeClass() throws Exception {
|
public static void beforeClass() throws Exception {
|
||||||
@ -48,26 +50,22 @@ public class MissingDiv9PatchTest extends BaseTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void assertMissingDivAdded() throws Exception {
|
public void assertMissingDivAdded() throws Exception {
|
||||||
InputStream inputStream = getFileInputStream();
|
File file = new File(sTmpDir, "pip_dismiss_scrim.9.png");
|
||||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
byte[] data;
|
||||||
|
|
||||||
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
|
try (InputStream in = Files.newInputStream(file.toPath())) {
|
||||||
decoder.decode(inputStream, outputStream);
|
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
decoder.decode(in, out);
|
||||||
|
data = out.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
BufferedImage image = ImageIO.read(new ByteArrayInputStream(outputStream.toByteArray()));
|
BufferedImage image = ImageIO.read(new ByteArrayInputStream(data));
|
||||||
int height = image.getHeight() - 1;
|
int height = image.getHeight() - 1;
|
||||||
|
|
||||||
// First and last pixel will be invisible, so lets check the first column and ensure its all black
|
// First and last pixel will be invisible, so lets check the first column and ensure its all black
|
||||||
for (int y = 1; y < height; y++) {
|
for (int y = 1; y < height; y++) {
|
||||||
assertEquals("y coordinate failed at: " + y, NP_COLOR, image.getRGB(0, y));
|
assertEquals("y coordinate failed at: " + y, NP_COLOR, image.getRGB(0, y));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private FileInputStream getFileInputStream() throws IOException {
|
|
||||||
File file = new File(sTmpDir, "pip_dismiss_scrim.9.png");
|
|
||||||
return new FileInputStream(file.toPath().toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final int NP_COLOR = 0xff000000;
|
|
||||||
}
|
}
|
||||||
|
@ -25,48 +25,55 @@ import static org.junit.Assert.assertEquals;
|
|||||||
public class StringBlockWithSurrogatePairInUtf8Test {
|
public class StringBlockWithSurrogatePairInUtf8Test {
|
||||||
@Test
|
@Test
|
||||||
public void decodeSingleOctet() {
|
public void decodeSingleOctet() {
|
||||||
final String actual = new StringBlock("abcDEF123".getBytes(StandardCharsets.UTF_8), true).decodeString(0, 9);
|
final byte[] bytes = "abcDEF123".getBytes(StandardCharsets.UTF_8);
|
||||||
|
final String actual = new StringBlock(bytes, true).decodeString(0, 9);
|
||||||
assertEquals("Incorrect decoding", "abcDEF123", actual);
|
assertEquals("Incorrect decoding", "abcDEF123", actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void decodeTwoOctets() {
|
public void decodeTwoOctets() {
|
||||||
final String actual0 = new StringBlock(new byte[] {(byte) 0xC2, (byte) 0x80}, true).decodeString(0, 2);
|
final byte[] bytes0 = { (byte) 0xC2, (byte) 0x80 };
|
||||||
|
final String actual0 = new StringBlock(bytes0, true).decodeString(0, 2);
|
||||||
assertEquals("Incorrect decoding", "\u0080", actual0);
|
assertEquals("Incorrect decoding", "\u0080", actual0);
|
||||||
|
|
||||||
final String actual1 = new StringBlock(new byte[] {(byte) 0xDF, (byte) 0xBF}, true).decodeString(0, 2);
|
final byte[] bytes1 = { (byte) 0xDF, (byte) 0xBF };
|
||||||
|
final String actual1 = new StringBlock(bytes1, true).decodeString(0, 2);
|
||||||
assertEquals("Incorrect decoding", "\u07FF", actual1);
|
assertEquals("Incorrect decoding", "\u07FF", actual1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void decodeThreeOctets() {
|
public void decodeThreeOctets() {
|
||||||
final String actual0 = new StringBlock(new byte[] {(byte) 0xE0, (byte) 0xA0, (byte) 0x80}, true).decodeString(0, 3);
|
final byte[] bytes0 = { (byte) 0xE0, (byte) 0xA0, (byte) 0x80 };
|
||||||
|
final String actual0 = new StringBlock(bytes0, true).decodeString(0, 3);
|
||||||
assertEquals("Incorrect decoding", "\u0800", actual0);
|
assertEquals("Incorrect decoding", "\u0800", actual0);
|
||||||
|
|
||||||
final String actual1 = new StringBlock(new byte[] {(byte) 0xEF, (byte) 0xBF, (byte) 0xBF}, true).decodeString(0, 3);
|
final byte[] bytes1 = { (byte) 0xEF, (byte) 0xBF, (byte) 0xBF };
|
||||||
|
final String actual1 = new StringBlock(bytes1, true).decodeString(0, 3);
|
||||||
assertEquals("Incorrect decoding", "\uFFFF", actual1);
|
assertEquals("Incorrect decoding", "\uFFFF", actual1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void decodeSurrogatePair_when_givesAsThreeOctetsFromInvalidRangeOfUtf8() {
|
public void decodeSurrogatePair_when_givesAsThreeOctetsFromInvalidRangeOfUtf8() {
|
||||||
// See: https://github.com/iBotPeaches/Apktool/issues/2299
|
// See: https://github.com/iBotPeaches/Apktool/issues/2299
|
||||||
final String actual = new StringBlock(new byte[] {(byte) 0xED, (byte) 0xA0, (byte) 0xBD, (byte) 0xED, (byte) 0xB4, (byte) 0x86}, true).decodeString(0, 6);
|
final byte[] bytes0 = { (byte) 0xED, (byte) 0xA0, (byte) 0xBD, (byte) 0xED, (byte) 0xB4, (byte) 0x86 };
|
||||||
assertEquals("Incorrect decoding", "\uD83D\uDD06", actual);
|
final String actual0 = new StringBlock(bytes0, true).decodeString(0, 6);
|
||||||
|
assertEquals("Incorrect decoding", "\uD83D\uDD06", actual0);
|
||||||
|
|
||||||
// See: https://github.com/iBotPeaches/Apktool/issues/2546
|
// See: https://github.com/iBotPeaches/Apktool/issues/2546
|
||||||
final byte[] bytesWithCharactersBeforeSurrogatePair = {'G', 'o', 'o', 'd', ' ', 'm', 'o', 'r', 'n', 'i', 'n', 'g', '!', ' ',
|
// Bytes with characters before surrogate pair
|
||||||
|
final byte[] bytes1 = {
|
||||||
|
'G', 'o', 'o', 'd', ' ', 'm', 'o', 'r', 'n', 'i', 'n', 'g', '!', ' ',
|
||||||
(byte) 0xED, (byte) 0xA0, (byte) 0xBD, (byte) 0xED, (byte) 0xB1, (byte) 0x8B,
|
(byte) 0xED, (byte) 0xA0, (byte) 0xBD, (byte) 0xED, (byte) 0xB1, (byte) 0x8B,
|
||||||
' ', 'S', 'u', 'n', ' ',
|
' ', 'S', 'u', 'n', ' ',
|
||||||
(byte) 0xED, (byte) 0xA0, (byte) 0xBC, (byte) 0xED, (byte) 0xBC, (byte) 0x9E
|
(byte) 0xED, (byte) 0xA0, (byte) 0xBC, (byte) 0xED, (byte) 0xBC, (byte) 0x9E
|
||||||
};
|
};
|
||||||
final String actual2 = new StringBlock(bytesWithCharactersBeforeSurrogatePair, true).decodeString(0, 31);
|
final String actual1 = new StringBlock(bytes1, true).decodeString(0, 31);
|
||||||
|
|
||||||
// D83D -> 0xED 0xA0 0xBD
|
// D83D -> 0xED 0xA0 0xBD
|
||||||
// DC4B -> 0xED 0xB1 0x8B
|
// DC4B -> 0xED 0xB1 0x8B
|
||||||
// D83C -> 0xED 0xA0 0xBC
|
// D83C -> 0xED 0xA0 0xBC
|
||||||
// DF1E -> 0xED 0xBC 0x9E
|
// DF1E -> 0xED 0xBC 0x9E
|
||||||
assertEquals("Incorrect decoding when there are valid characters before the surrogate pair",
|
assertEquals("Incorrect decoding when there are valid characters before the surrogate pair",
|
||||||
"Good morning! \uD83D\uDC4B Sun \uD83C\uDF1E", actual2);
|
"Good morning! \uD83D\uDC4B Sun \uD83C\uDF1E", actual1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -74,7 +81,8 @@ public class StringBlockWithSurrogatePairInUtf8Test {
|
|||||||
// \u10FFFF is encoded in UTF-8 as "0xDBFF 0xDFFF" (4-byte encoding),
|
// \u10FFFF is encoded in UTF-8 as "0xDBFF 0xDFFF" (4-byte encoding),
|
||||||
// but when used in Android resources which are encoded in UTF-8, 3-byte encoding is used,
|
// but when used in Android resources which are encoded in UTF-8, 3-byte encoding is used,
|
||||||
// so each of these is encoded as 3-bytes
|
// so each of these is encoded as 3-bytes
|
||||||
final String actual = new StringBlock(new byte[] {(byte) 0xED, (byte) 0xAF, (byte) 0xBF, (byte) 0xED, (byte) 0xBF, (byte) 0xBF}, true).decodeString(0, 6);
|
final byte[] bytes = { (byte) 0xED, (byte) 0xAF, (byte) 0xBF, (byte) 0xED, (byte) 0xBF, (byte) 0xBF };
|
||||||
|
final String actual = new StringBlock(bytes, true).decodeString(0, 6);
|
||||||
assertEquals("Incorrect decoding", "\uDBFF\uDFFF", actual);
|
assertEquals("Incorrect decoding", "\uDBFF\uDFFF", actual);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,9 +26,9 @@ public class AaptVersionTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAapt2Iterations() throws BrutException {
|
public void testAapt2Iterations() throws BrutException {
|
||||||
assertEquals(2, AaptManager.getAppVersionFromString("Android Asset Packaging Tool (aapt) 2:17"));
|
assertEquals(2, AaptManager.getAaptVersionFromString("Android Asset Packaging Tool (aapt) 2:17"));
|
||||||
assertEquals(2, AaptManager.getAppVersionFromString("Android Asset Packaging Tool (aapt) 2.17"));
|
assertEquals(2, AaptManager.getAaptVersionFromString("Android Asset Packaging Tool (aapt) 2.17"));
|
||||||
assertEquals(1, AaptManager.getAppVersionFromString("Android Asset Packaging Tool, v0.9"));
|
assertEquals(1, AaptManager.getAaptVersionFromString("Android Asset Packaging Tool, v0.9"));
|
||||||
assertEquals(1, AaptManager.getAppVersionFromString("Android Asset Packaging Tool, v0.2-2679779"));
|
assertEquals(1, AaptManager.getAaptVersionFromString("Android Asset Packaging Tool, v0.2-2679779"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,15 @@
|
|||||||
package brut.common;
|
package brut.common;
|
||||||
|
|
||||||
public class BrutException extends Exception {
|
public class BrutException extends Exception {
|
||||||
|
|
||||||
|
public BrutException() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BrutException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
public BrutException(Throwable cause) {
|
public BrutException(Throwable cause) {
|
||||||
super(cause);
|
super(cause);
|
||||||
}
|
}
|
||||||
@ -24,11 +33,4 @@ public class BrutException extends Exception {
|
|||||||
public BrutException(String message, Throwable cause) {
|
public BrutException(String message, Throwable cause) {
|
||||||
super(message, cause);
|
super(message, cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BrutException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public BrutException() {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package brut.common;
|
package brut.common;
|
||||||
|
|
||||||
public class InvalidUnknownFileException extends BrutException {
|
public class InvalidUnknownFileException extends BrutException {
|
||||||
|
|
||||||
public InvalidUnknownFileException(String message) {
|
public InvalidUnknownFileException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package brut.common;
|
package brut.common;
|
||||||
|
|
||||||
public class RootUnknownFileException extends BrutException {
|
public class RootUnknownFileException extends BrutException {
|
||||||
|
|
||||||
public RootUnknownFileException(String message) {
|
public RootUnknownFileException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package brut.common;
|
package brut.common;
|
||||||
|
|
||||||
public class TraversalUnknownFileException extends BrutException {
|
public class TraversalUnknownFileException extends BrutException {
|
||||||
|
|
||||||
public TraversalUnknownFileException(String message) {
|
public TraversalUnknownFileException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
@ -180,31 +180,31 @@ public abstract class AbstractDirectory implements Directory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void copyToDir(Directory out) throws DirectoryException {
|
public void copyToDir(Directory out) throws DirectoryException {
|
||||||
DirUtil.copyToDir(out, out);
|
DirUtils.copyToDir(out, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void copyToDir(Directory out, String[] fileNames)
|
public void copyToDir(Directory out, String[] fileNames)
|
||||||
throws DirectoryException {
|
throws DirectoryException {
|
||||||
DirUtil.copyToDir(out, out, fileNames);
|
DirUtils.copyToDir(out, out, fileNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void copyToDir(Directory out, String fileName)
|
public void copyToDir(Directory out, String fileName)
|
||||||
throws DirectoryException {
|
throws DirectoryException {
|
||||||
DirUtil.copyToDir(out, out, fileName);
|
DirUtils.copyToDir(out, out, fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void copyToDir(File out) throws DirectoryException {
|
public void copyToDir(File out) throws DirectoryException {
|
||||||
DirUtil.copyToDir(this, out);
|
DirUtils.copyToDir(this, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void copyToDir(File out, String[] fileNames)
|
public void copyToDir(File out, String[] fileNames)
|
||||||
throws DirectoryException {
|
throws DirectoryException {
|
||||||
DirUtil.copyToDir(this, out, fileNames);
|
DirUtils.copyToDir(this, out, fileNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void copyToDir(File out, String fileName)
|
public void copyToDir(File out, String fileName)
|
||||||
throws DirectoryException {
|
throws DirectoryException {
|
||||||
DirUtil.copyToDir(this, out, fileName);
|
DirUtils.copyToDir(this, out, fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getCompressionLevel(String fileName)
|
public int getCompressionLevel(String fileName)
|
||||||
@ -269,6 +269,7 @@ public abstract class AbstractDirectory implements Directory {
|
|||||||
private static class ParsedPath {
|
private static class ParsedPath {
|
||||||
public final String dir;
|
public final String dir;
|
||||||
public final String subpath;
|
public final String subpath;
|
||||||
|
|
||||||
public ParsedPath(String dir, String subpath) {
|
public ParsedPath(String dir, String subpath) {
|
||||||
this.dir = dir;
|
this.dir = dir;
|
||||||
this.subpath = subpath;
|
this.subpath = subpath;
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user