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:
Igor Eisberg 2024-12-11 17:55:13 +02:00 committed by GitHub
parent 858c07143d
commit 542b66cbd0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
120 changed files with 1485 additions and 1513 deletions

View File

@ -36,6 +36,19 @@ import java.util.logging.*;
* Main entry point of the apktool.
*/
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 {
// headless
@ -239,7 +252,7 @@ public class Main {
System.exit(1);
} catch (CantFindFrameworkResException ex) {
System.err
.println("Can't find framework resources for package of id: "
.println("Could not find framework resources for package of id: "
+ ex.getPkgId()
+ ". You must install proper "
+ "framework files, see project website for more info.");
@ -271,15 +284,8 @@ public class Main {
}
try {
String aaptPath = cli.getOptionValue("a");
int aaptVersion = AaptManager.getAaptVersion(aaptPath);
if (aaptVersion < AaptManager.AAPT_VERSION_MIN && aaptVersion > AaptManager.AAPT_VERSION_MAX) {
System.err.println("AAPT version " + aaptVersion + " is not supported");
System.exit(1);
}
config.aaptPath = aaptPath;
config.aaptVersion = aaptVersion;
config.aaptBinary = new File(cli.getOptionValue("a"));
config.aaptVersion = AaptManager.getAaptVersion(config.aaptBinary);
} catch (BrutException ex) {
System.err.println(ex.getMessage());
System.exit(1);
@ -310,7 +316,7 @@ public class Main {
}
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);
}
@ -724,31 +730,4 @@ public class Main {
private static void setAdvanceMode() {
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();
}
}

View File

@ -3,9 +3,8 @@ val apktoolVersion: String by rootProject.extra
tasks {
processResources {
from("src/main/resources/properties") {
include("**/*.properties")
into("properties")
from("src/main/resources") {
include("apktool.properties")
expand("version" to apktoolVersion, "gitrev" to gitRevision)
duplicatesStrategy = DuplicatesStrategy.INCLUDE
}

View File

@ -27,7 +27,7 @@ import org.xmlpull.v1.XmlPullParser;
public interface XmlResourceParser extends XmlPullParser, AttributeSet {
/**
* Close this interface to the resource. Calls on the interface are no
* longer value after this call.
* longer valid after this call.
*/
void close();
}

View File

@ -216,7 +216,7 @@ public class TypedValue {
public int type;
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,
1.0f / (1 << 15) * MANTISSA_MULT, 1.0f / (1 << 23) * MANTISSA_MULT };
@ -237,9 +237,10 @@ public class TypedValue {
& TypedValue.COMPLEX_RADIX_MASK];
}
private static final String[] DIMENSION_UNIT_STRS = new String[] { "px",
"dip", "sp", "pt", "in", "mm" };
private static final String[] FRACTION_UNIT_STRS = new String[] { "%", "%p" };
private static final String[] DIMENSION_UNIT_STRS = {
"px", "dip", "sp", "pt", "in", "mm"
};
private static final String[] FRACTION_UNIT_STRS = { "%", "%p" };
/**
* Perform type conversion as per coerceToString on an explicitly

View File

@ -27,44 +27,40 @@ import java.util.*;
import java.util.logging.Logger;
public class AaptInvoker {
private static final Logger LOGGER = Logger.getLogger(AaptInvoker.class.getName());
private final Config mConfig;
private final ApkInfo mApkInfo;
private final static Logger LOGGER = Logger.getLogger(AaptInvoker.class.getName());
public AaptInvoker(Config config, ApkInfo apkInfo) {
mConfig = config;
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)
throws AndrolibException {
File aaptBinary = mConfig.aaptBinary;
String aaptPath = mConfig.aaptPath;
boolean customAapt = !aaptPath.isEmpty();
List<String> cmd = new ArrayList<>();
String aaptPath;
boolean customAapt;
try {
String aaptCommand = AaptManager.getAaptExecutionCommand(aaptPath, getAaptBinaryFile());
cmd.add(aaptCommand);
} catch (BrutException ex) {
LOGGER.warning("aapt: " + ex.getMessage() + " (defaulting to $PATH binary)");
cmd.add(AaptManager.getAaptBinaryName(mConfig.aaptVersion));
if (mConfig.aaptBinary != null) {
aaptPath = mConfig.aaptBinary.getPath();
customAapt = true;
} else {
try {
aaptPath = AaptManager.getAaptBinary(mConfig.aaptVersion).getPath();
customAapt = false;
} catch (BrutException ex) {
aaptPath = AaptManager.getAaptName(mConfig.aaptVersion);
customAapt = true;
LOGGER.warning(aaptPath + ": " + ex.getMessage() + " (defaulting to $PATH binary)");
}
}
cmd.add(aaptPath);
switch (mConfig.aaptVersion) {
case 2:
invokeAapt2(apkFile, manifest, resDir, rawDir, assetDir, include, cmd, customAapt);
@ -77,7 +73,6 @@ public class AaptInvoker {
private void invokeAapt2(File apkFile, File manifest, File resDir, File rawDir, File assetDir, File[] include,
List<String> cmd, boolean customAapt) throws AndrolibException {
List<String> compileCommand = new ArrayList<>(cmd);
File resourcesZip = null;
@ -87,7 +82,6 @@ public class AaptInvoker {
}
if (resDir != null && !resourcesZip.exists()) {
// Compile the files into flat arsc files
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,
List<String> cmd, boolean customAapt) throws AndrolibException {
cmd.add("p");
if (mConfig.verbose) { // output aapt verbose

View File

@ -21,7 +21,7 @@ import brut.androlib.apk.ApkInfo;
import brut.androlib.apk.UsesFramework;
import brut.androlib.res.Framework;
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.common.BrutException;
import brut.common.InvalidUnknownFileException;
@ -47,14 +47,15 @@ import java.util.logging.Logger;
import java.util.zip.ZipOutputStream;
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 Config mConfig;
private final AtomicReference<AndrolibException> mBuildError;
private ApkInfo mApkInfo;
private int mMinSdkVersion = 0;
private int mMinSdkVersion;
private BackgroundWorker mWorker;
private final AtomicReference<AndrolibException> mBuildError = new AtomicReference<>(null);
public ApkBuilder(ExtFile apkDir) {
this(apkDir, Config.getDefaultConfig());
@ -63,6 +64,7 @@ public class ApkBuilder {
public ApkBuilder(ExtFile apkDir, Config config) {
mApkDir = apkDir;
mConfig = config;
mBuildError = new AtomicReference<>(null);
}
public void build(File outApk) throws AndrolibException {
@ -122,19 +124,19 @@ public class ApkBuilder {
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
try {
ZipUtils.zipDir(outDir, outStream, mApkInfo.doNotCompress);
ZipUtils.zipDir(outDir, out, mApkInfo.doNotCompress);
} catch (IOException ex) {
throw new AndrolibException(ex);
}
// zip remaining standard files
importRawFiles(outStream);
importRawFiles(out);
// zip unknown files
importUnknownFiles(outStream);
importUnknownFiles(out);
} catch (IOException ex) {
throw new AndrolibException(ex);
}
@ -242,10 +244,10 @@ public class ApkBuilder {
//noinspection ResultOfMethodCallIgnored
dex.delete();
int apiLevel = mConfig.apiLevel > 0 ? mConfig.apiLevel : mMinSdkVersion;
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 {
@ -265,7 +267,7 @@ public class ApkBuilder {
try {
FileUtils.copyFile(manifest, manifestOrig);
ResXmlPatcher.fixingPublicAttrsInProviderAttributes(manifest);
ResXmlUtils.fixingPublicAttrsInProviderAttributes(manifest);
} catch (IOException ex) {
throw new AndrolibException(ex);
}
@ -327,10 +329,10 @@ public class ApkBuilder {
try {
if (mConfig.debugMode) {
if (mConfig.aaptVersion == 2) {
LOGGER.info("Using aapt2 - setting 'debuggable' attribute to 'true' in AndroidManifest.xml");
ResXmlPatcher.setApplicationDebugTagTrue(manifest);
LOGGER.info("Setting 'debuggable' attribute to 'true' in AndroidManifest.xml");
ResXmlUtils.setApplicationDebugTagTrue(manifest);
} 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");
ResXmlPatcher.modNetworkSecurityConfig(netSecConfOrig);
ResXmlPatcher.setNetworkSecurityConfig(manifest);
ResXmlUtils.modNetworkSecurityConfig(netSecConfOrig);
ResXmlUtils.setNetworkSecurityConfig(manifest);
LOGGER.info("Added permissive network security config in manifest");
}
} catch (IOException | ParserConfigurationException | TransformerException | SAXException ex) {
@ -366,7 +368,7 @@ public class ApkBuilder {
ninePatch = null;
}
LOGGER.info("Building resources with " + AaptManager.getAaptBinaryName(mConfig.aaptVersion) + "...");
LOGGER.info("Building resources with " + AaptManager.getAaptName(mConfig.aaptVersion) + "...");
try {
AaptInvoker invoker = new AaptInvoker(mConfig, mApkInfo);
@ -406,7 +408,7 @@ public class ApkBuilder {
ninePatch = null;
}
LOGGER.info("Building AndroidManifest.xml with " + AaptManager.getAaptBinaryName(mConfig.aaptVersion) + "...");
LOGGER.info("Building AndroidManifest.xml with " + AaptManager.getAaptName(mConfig.aaptVersion) + "...");
try {
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) {
File rawDir = new File(mApkDir, dirName);
if (!rawDir.isDirectory()) {
@ -469,14 +471,14 @@ public class ApkBuilder {
LOGGER.info("Importing " + dirName + "...");
try {
ZipUtils.zipDir(mApkDir, dirName, outStream, mApkInfo.doNotCompress);
ZipUtils.zipDir(mApkDir, dirName, out, mApkInfo.doNotCompress);
} catch (IOException 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");
if (!unknownDir.isDirectory()) {
return;
@ -484,7 +486,7 @@ public class ApkBuilder {
LOGGER.info("Importing unknown files...");
try {
ZipUtils.zipDir(unknownDir, outStream, mApkInfo.doNotCompress);
ZipUtils.zipDir(unknownDir, out, mApkInfo.doNotCompress);
} catch (IOException ex) {
throw new AndrolibException(ex);
}

View File

@ -37,20 +37,21 @@ import java.util.logging.Logger;
import java.util.regex.Pattern;
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
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|" +
"rtttl|imy|xmf|mp4|m4a|m4v|3gp|3gpp|3g2|3gpp2|amr|awb|wma|wmv|webm|webp|mkv");
private final ExtFile mApkFile;
private final Config mConfig;
private final AtomicReference<AndrolibException> mBuildError;
private ApkInfo mApkInfo;
private ResourcesDecoder mResDecoder;
private volatile int mMinSdkVersion = 0;
private volatile int mMinSdkVersion;
private BackgroundWorker mWorker;
private final AtomicReference<AndrolibException> mBuildError = new AtomicReference<>(null);
public ApkDecoder(ExtFile apkFile) {
this(apkFile, Config.getDefaultConfig());
@ -59,6 +60,7 @@ public class ApkDecoder {
public ApkDecoder(ExtFile apkFile, Config config) {
mApkFile = apkFile;
mConfig = config;
mBuildError = new AtomicReference<>(null);
}
public ApkInfo decode(File outDir) throws AndrolibException {
@ -199,8 +201,9 @@ public class ApkDecoder {
smaliDir.mkdirs();
LOGGER.info("Baksmaling " + fileName + "...");
DexFile dexFile = SmaliDecoder.decode(mApkFile, smaliDir, fileName,
SmaliDecoder decoder = new SmaliDecoder(mApkFile, fileName,
mConfig.baksmaliDebugMode, mConfig.apiLevel);
DexFile dexFile = decoder.decode(smaliDir);
// record minSdkVersion for jars
int minSdkVersion = dexFile.getOpcodes().api;

View File

@ -42,20 +42,20 @@ public class ApktoolProperties {
}
private static void loadProps() {
InputStream in = ApktoolProperties.class.getResourceAsStream("/properties/apktool.properties");
InputStream in = ApktoolProperties.class.getResourceAsStream("/apktool.properties");
sProps = new Properties();
try {
sProps.load(in);
in.close();
} catch (NullPointerException | IOException ex) {
LOGGER.warning("Can't load properties.");
LOGGER.warning("Could not load properties.");
}
InputStream templateStream = null;
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) {
LOGGER.warning("Can't load baksmali properties.");
LOGGER.warning("Could not load baksmali properties.");
}
Properties properties = new Properties();
String version = "(unknown)";
@ -71,9 +71,9 @@ public class ApktoolProperties {
templateStream = null;
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) {
LOGGER.warning("Can't load smali properties.");
LOGGER.warning("Could not load smali properties.");
}
properties = new Properties();
version = "(unknown)";

View File

@ -17,15 +17,18 @@
package brut.androlib;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class BackgroundWorker {
private final ArrayList<Future<?>> mWorkerFutures = new ArrayList<>();
private final ExecutorService mExecutor;
private volatile boolean mSubmitAllowed = true;
private final List<Future<?>> mWorkerFutures;
private volatile boolean mSubmitAllowed;
public BackgroundWorker(int threads) {
mExecutor = Executors.newFixedThreadPool(threads);
mWorkerFutures = new ArrayList<>();
mSubmitAllowed = true;
}
public void waitForFinish() {

View File

@ -22,26 +22,27 @@ import brut.util.OSDetection;
import java.io.File;
import java.util.logging.Logger;
public class Config {
private static Config instance = null;
private final static Logger LOGGER = Logger.getLogger(Config.class.getName());
public final class Config {
private static final Logger LOGGER = Logger.getLogger(Config.class.getName());
public final static short DECODE_SOURCES_NONE = 0x0000;
public final static short DECODE_SOURCES_SMALI = 0x0001;
public final static short DECODE_SOURCES_SMALI_ONLY_MAIN_CLASSES = 0x0010;
public static final short DECODE_SOURCES_NONE = 0x0000;
public static final short DECODE_SOURCES_SMALI = 0x0001;
public static final short DECODE_SOURCES_SMALI_ONLY_MAIN_CLASSES = 0x0010;
public final static short DECODE_RESOURCES_NONE = 0x0100;
public final static short DECODE_RESOURCES_FULL = 0x0101;
public static final short DECODE_RESOURCES_NONE = 0x0100;
public static final short DECODE_RESOURCES_FULL = 0x0101;
public final static short FORCE_DECODE_MANIFEST_NONE = 0x0000;
public final static short FORCE_DECODE_MANIFEST_FULL = 0x0001;
public static final short FORCE_DECODE_MANIFEST_NONE = 0x0000;
public static final short FORCE_DECODE_MANIFEST_FULL = 0x0001;
public final static short DECODE_ASSETS_NONE = 0x0000;
public final static short DECODE_ASSETS_FULL = 0x0001;
public static final short DECODE_ASSETS_NONE = 0x0000;
public static final short DECODE_ASSETS_FULL = 0x0001;
public final static short DECODE_RES_RESOLVE_REMOVE = 0x0000;
public final static short DECODE_RES_RESOLVE_DUMMY = 0x0001;
public final static short DECODE_RES_RESOLVE_RETAIN = 0x0002;
public static final short DECODE_RES_RESOLVE_REMOVE = 0x0000;
public static final short DECODE_RES_RESOLVE_DUMMY = 0x0001;
public static final short DECODE_RES_RESOLVE_RETAIN = 0x0002;
private static Config sInstance;
// Build options
public boolean forceBuildAll = false;
@ -70,7 +71,7 @@ public class Config {
public int jobs = Math.min(Runtime.getRuntime().availableProcessors(), 8);
public String frameworkDirectory = null;
public String frameworkTag = null;
public String aaptPath = "";
public File aaptBinary = null;
public int aaptVersion = 2; // default to v2
// Utility functions
@ -83,14 +84,14 @@ public class Config {
}
private Config() {
instance = this;
sInstance = this;
}
public static Config getInstance() {
if (instance == null) {
instance = new Config();
if (sInstance == null) {
sInstance = new Config();
}
return instance;
return sInstance;
}
private void setDefaultFrameworkDirectory() {

View File

@ -28,13 +28,13 @@ import java.util.*;
import java.util.regex.Pattern;
public class ApkInfo implements YamlSerializable {
public final static String[] RESOURCES_DIRNAMES = new String[] { "res", "r", "R" };
public final static String[] RAW_DIRNAMES = new String[] { "assets", "lib", "libs", "kotlin", "META-INF/services" };
public static final String[] RESOURCES_DIRNAMES = { "res", "r", "R" };
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");
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) + "|" +
String.join("|", RAW_DIRNAMES) + ")/.*|" + ORIGINAL_FILENAMES_PATTERN.pattern());
@ -194,10 +194,7 @@ public class ApkInfo implements YamlSerializable {
}
public void save(File file) throws AndrolibException {
try (
OutputStream out = Files.newOutputStream(file.toPath());
YamlWriter writer = new YamlWriter(out)
) {
try (YamlWriter writer = new YamlWriter(Files.newOutputStream(file.toPath()))) {
write(writer);
} catch (FileNotFoundException ex) {
throw new AndrolibException("File not found");

View File

@ -19,7 +19,6 @@ package brut.androlib.apk;
import java.util.Objects;
public class YamlLine {
public int indent = 0;
private String key = "";
private String value = "";
@ -62,7 +61,7 @@ public class YamlLine {
if (isItem) {
// array item line has only the value
value = line.substring(1).trim();
} else {
} else {
// split line to key - value
String[] parts = line.split(":");
if (parts.length > 0) {

View File

@ -22,9 +22,8 @@ import java.io.InputStream;
import java.util.*;
public class YamlReader {
private ArrayList<YamlLine> mLines;
private int mCurrent = 0;
private int mCurrent;
public YamlReader(InputStream in) {
mLines = new ArrayList<>();

View File

@ -20,5 +20,6 @@ import brut.androlib.exceptions.AndrolibException;
public interface YamlSerializable {
void readItem(YamlReader reader) throws AndrolibException;
void write(YamlWriter writer);
}

View File

@ -23,7 +23,11 @@ import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
public class YamlStringEscapeUtils {
public final class YamlStringEscapeUtils {
private YamlStringEscapeUtils() {
// Private constructor for utility class
}
public static String escapeString(String 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
* @throws IOException if an IOException occurs
*/
private static void escapeJavaStyleString(Writer out, String str) throws IOException {
if (out == null) {
private static void escapeJavaStyleString(Writer writer, String str) throws IOException {
if (writer == null) {
throw new IllegalArgumentException("The Writer must not be null");
}
if (str == null) {
@ -66,51 +70,51 @@ public class YamlStringEscapeUtils {
// "[^\t\n\r\u0020-\u007E\u0085\u00A0-\uD7FF\uE000-\uFFFD]"
// handle unicode
if (ch > 0xFFFD) {
out.write("\\u" + CharSequenceTranslator.hex(ch));
writer.write("\\u" + CharSequenceTranslator.hex(ch));
} 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) {
out.write("\\u00" + CharSequenceTranslator.hex(ch));
writer.write("\\u00" + CharSequenceTranslator.hex(ch));
} else if (ch < 32) {
switch (ch) {
case '\t' :
out.write('\\');
out.write('t');
writer.write('\\');
writer.write('t');
break;
case '\n' :
out.write('\\');
out.write('n');
writer.write('\\');
writer.write('n');
break;
case '\r' :
out.write('\\');
out.write('r');
writer.write('\\');
writer.write('r');
break;
default :
if (ch > 0xf) {
out.write("\\u00" + CharSequenceTranslator.hex(ch));
writer.write("\\u00" + CharSequenceTranslator.hex(ch));
} else {
out.write("\\u000" + CharSequenceTranslator.hex(ch));
writer.write("\\u000" + CharSequenceTranslator.hex(ch));
}
break;
}
} else {
switch (ch) {
case '\'' :
out.write('\'');
writer.write('\'');
break;
case '"' :
out.write('\\');
out.write('"');
writer.write('\\');
writer.write('"');
break;
case '\\' :
out.write('\\');
out.write('\\');
writer.write('\\');
writer.write('\\');
break;
case '/' :
out.write('/');
writer.write('/');
break;
default :
out.write(ch);
writer.write(ch);
break;
}
}

View File

@ -21,10 +21,10 @@ import java.nio.charset.StandardCharsets;
import java.util.*;
public class YamlWriter implements Closeable {
private static final String QUOTE = "'";
private int mIndent = 0;
private final PrintWriter mWriter;
private final String QUOTE = "'";
private int mIndent;
public YamlWriter(OutputStream out) {
mWriter = new PrintWriter(new BufferedWriter(

View File

@ -17,6 +17,7 @@
package brut.androlib.exceptions;
public class AXmlDecodingException extends AndrolibException {
public AXmlDecodingException(String message, Throwable cause) {
super(message, cause);
}

View File

@ -19,18 +19,20 @@ package brut.androlib.exceptions;
import brut.common.BrutException;
public class AndrolibException extends BrutException {
public AndrolibException() {
super();
}
public AndrolibException(String message) {
super(message);
}
public AndrolibException(String message, Throwable cause) {
super(message, cause);
}
public AndrolibException(Throwable cause) {
super(cause);
}
public AndrolibException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -17,6 +17,7 @@
package brut.androlib.exceptions;
public class CantFind9PatchChunkException extends AndrolibException {
public CantFind9PatchChunkException(String message, Throwable cause) {
super(message, cause);
}

View File

@ -17,8 +17,10 @@
package brut.androlib.exceptions;
public class CantFindFrameworkResException extends AndrolibException {
public CantFindFrameworkResException(int id) {
mPkgId = id;
private final int mPkgId;
public CantFindFrameworkResException(int pkgId) {
mPkgId = pkgId;
}
public int getPkgId() {
@ -27,8 +29,6 @@ public class CantFindFrameworkResException extends AndrolibException {
@Override
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;
}

View File

@ -17,6 +17,8 @@
package brut.androlib.exceptions;
public class InFileNotFoundException extends AndrolibException {
public InFileNotFoundException() {
super();
}
}

View File

@ -17,6 +17,8 @@
package brut.androlib.exceptions;
public class OutDirExistsException extends AndrolibException {
public OutDirExistsException() {
super();
}
}

View File

@ -17,6 +17,7 @@
package brut.androlib.exceptions;
public class RawXmlEncounteredException extends AndrolibException {
public RawXmlEncounteredException(String message, Throwable cause) {
super(message, cause);
}

View File

@ -17,6 +17,7 @@
package brut.androlib.exceptions;
public class UndefinedResObjectException extends AndrolibException {
public UndefinedResObjectException(String message) {
super(message);
}

View File

@ -30,13 +30,16 @@ import java.io.*;
import java.nio.charset.StandardCharsets;
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,
boolean printTokens) throws IOException, RecognitionException {
try (
InputStream in = Files.newInputStream(smaliFile.toPath());
InputStreamReader reader = new InputStreamReader(in, StandardCharsets.UTF_8)
) {
try (InputStreamReader reader = new InputStreamReader(
Files.newInputStream(smaliFile.toPath()), StandardCharsets.UTF_8)) {
smaliFlexLexer lexer = new smaliFlexLexer(reader, apiLevel);
lexer.setSourceFile(smaliFile);
CommonTokenStream tokens = new CommonTokenStream(lexer);

View File

@ -23,7 +23,6 @@ import brut.androlib.res.decoder.ARSCDecoder;
import brut.androlib.res.data.arsc.ARSCData;
import brut.androlib.res.data.arsc.FlagsOffset;
import brut.util.BrutIO;
import brut.util.Jar;
import java.io.*;
import java.nio.file.Files;
@ -35,18 +34,17 @@ import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
public class Framework {
private final Config config;
private static final Logger LOGGER = Logger.getLogger(Framework.class.getName());
private File mFrameworkDirectory = null;
private final static Logger LOGGER = Logger.getLogger(Framework.class.getName());
private final Config mConfig;
private File mFrameworkDirectory;
public Framework(Config config) {
this.config = config;
mConfig = config;
}
public void installFramework(File frameFile) throws AndrolibException {
installFramework(frameFile, config.frameworkTag);
installFramework(frameFile, mConfig.frameworkTag);
}
public void installFramework(File frameFile, String tag) throws AndrolibException {
@ -54,17 +52,16 @@ public class Framework {
ZipEntry entry = zip.getEntry("resources.arsc");
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));
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());
File outFile = new File(getFrameworkDirectory(), arsc
.getOnePackage().getId()
+ (tag == null ? "" : '-' + tag)
+ ".apk");
File outFile = new File(getFrameworkDirectory(),
arsc.getOnePackage().getId() + (tag == null ? "" : '-' + tag) + ".apk");
try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(outFile.toPath()))) {
out.setMethod(ZipOutputStream.STORED);
@ -116,8 +113,10 @@ public class Framework {
public void publicizeResources(File arscFile) throws AndrolibException {
byte[] data = new byte[(int) arscFile.length()];
try(InputStream in = Files.newInputStream(arscFile.toPath());
OutputStream out = Files.newOutputStream(arscFile.toPath())) {
try (
InputStream in = Files.newInputStream(arscFile.toPath());
OutputStream out = Files.newOutputStream(arscFile.toPath())
) {
//noinspection ResultOfMethodCallIgnored
in.read(data);
publicizeResources(data);
@ -127,16 +126,18 @@ public class Framework {
}
}
private void publicizeResources(byte[] arsc) throws AndrolibException {
publicizeResources(arsc, ARSCDecoder.decode(new ByteArrayInputStream(arsc), true, true).getFlagsOffsets());
private void publicizeResources(byte[] data) throws AndrolibException {
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) {
int offset = flags.offset + 3;
int end = offset + 4 * flags.count;
while (offset < end) {
arsc[offset] |= (byte) 0x40;
data[offset] |= (byte) 0x40;
offset += 4;
}
}
@ -150,7 +151,7 @@ public class Framework {
String path;
// use default framework path or specified on the command line
path = config.frameworkDirectory;
path = mConfig.frameworkDirectory;
File dir = new File(path);
@ -162,19 +163,19 @@ public class Framework {
throw new AndrolibException("Please remove file at " + dir.getParentFile());
}
if (! dir.exists()) {
if (! dir.mkdirs()) {
if (config.frameworkDirectory != null) {
LOGGER.severe("Can't create Framework directory: " + dir);
if (!dir.exists()) {
if (!dir.mkdirs()) {
if (mConfig.frameworkDirectory != null) {
LOGGER.severe("Could not create Framework directory: " + dir);
}
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 (! dir.canWrite()) {
if (mConfig.frameworkDirectory == null) {
if (!dir.canWrite()) {
LOGGER.severe(String.format("WARNING: Could not write to (%1$s), using %2$s instead...",
dir.getAbsolutePath(), System.getProperty("java.io.tmpdir")));
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");
if (! apk.exists()) {
LOGGER.warning("Can't empty framework directory, no file found at: " + apk.getAbsolutePath());
if (!apk.exists()) {
LOGGER.warning("Could not empty framework directory, no file found at: " + apk.getAbsolutePath());
} else {
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.");
} else {
for (File file : Objects.requireNonNull(dir.listFiles())) {
@ -244,6 +245,6 @@ public class Framework {
}
private InputStream getAndroidFrameworkResourcesAsStream() {
return Jar.class.getResourceAsStream("/brut/androlib/android-framework.jar");
return Framework.class.getResourceAsStream("/prebuilt/android-framework.jar");
}
}

View File

@ -22,11 +22,12 @@ import brut.androlib.exceptions.AndrolibException;
import brut.androlib.res.data.*;
import brut.androlib.res.decoder.*;
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.DirectoryException;
import brut.directory.ExtFile;
import brut.xmlpull.MXSerializer;
import com.google.common.collect.Sets;
import org.xmlpull.v1.XmlSerializer;
import java.io.*;
@ -34,21 +35,23 @@ import java.util.*;
import java.util.logging.Logger;
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 ApkInfo mApkInfo;
private final ResTable mResTable;
private final Map<String, String> mResFileMapping = new HashMap<>();
private final static String[] IGNORED_PACKAGES = new String[] {
"android", "com.htc", "com.lge", "com.lge.internal", "yi", "flyme", "air.com.adobe.appentry",
"FFFFFFFFFFFFFFFFFFFFFF" };
private final Map<String, String> mResFileMapping;
public ResourcesDecoder(Config config, ApkInfo apkInfo) {
mConfig = config;
mApkInfo = apkInfo;
mResTable = new ResTable(mConfig, mApkInfo);
mResFileMapping = new HashMap<>();
}
public ResTable getResTable() throws AndrolibException {
@ -59,7 +62,7 @@ public class ResourcesDecoder {
return mResTable;
}
public Map<String, String> getResFileMapping() {
public Map<String, String> getResFileMapping() {
return mResFileMapping;
}
@ -67,7 +70,7 @@ public class ResourcesDecoder {
mResTable.loadMainPkg(mApkInfo.getApkFile());
}
public void decodeManifest(File outDir) throws AndrolibException {
public void decodeManifest(File apkDir) throws AndrolibException {
if (!mApkInfo.hasManifest()) {
return;
}
@ -76,10 +79,10 @@ public class ResourcesDecoder {
XmlSerializer xmlSerializer = newXmlSerializer();
ResStreamDecoder fileDecoder = new AndroidManifestPullStreamDecoder(axmlParser, xmlSerializer);
Directory in, out;
Directory inDir, outDir;
try {
in = mApkInfo.getApkFile().getDirectory();
out = new ExtFile(outDir).getDirectory();
inDir = mApkInfo.getApkFile().getDirectory();
outDir = new ExtFile(apkDir).getDirectory();
if (mApkInfo.hasResources()) {
LOGGER.info("Decoding AndroidManifest.xml with resources...");
@ -88,16 +91,16 @@ public class ResourcesDecoder {
}
try (
InputStream is = in.getFileInput("AndroidManifest.xml");
OutputStream os = out.getFileOutput("AndroidManifest.xml")
InputStream in = inDir.getFileInput("AndroidManifest.xml");
OutputStream out = outDir.getFileOutput("AndroidManifest.xml")
) {
fileDecoder.decode(is, os);
fileDecoder.decode(in, out);
}
} catch (DirectoryException | IOException ex) {
throw new AndrolibException(ex);
}
File manifest = new File(outDir, "AndroidManifest.xml");
File manifest = new File(apkDir, "AndroidManifest.xml");
if (mApkInfo.hasResources() && !mConfig.analysisMode) {
// 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
adjustPackageManifest(manifest);
ResXmlPatcher.removeManifestVersions(manifest);
ResXmlUtils.removeManifestVersions(manifest);
// update apk info
mApkInfo.packageInfo.forcedPackageId = String.valueOf(mResTable.getPackageId());
}
// record feature flags
List<String> featureFlags = ResXmlPatcher.pullManifestFeatureFlags(manifest);
List<String> featureFlags = ResXmlUtils.pullManifestFeatureFlags(manifest);
if (featureFlags != null) {
for (String flag : featureFlags) {
mApkInfo.addFeatureFlag(flag, true);
@ -123,8 +126,8 @@ public class ResourcesDecoder {
}
}
public void updateApkInfo(File outDir) throws AndrolibException {
mResTable.initApkInfo(mApkInfo, outDir);
public void updateApkInfo(File apkDir) throws AndrolibException {
mResTable.initApkInfo(mApkInfo, apkDir);
}
private void adjustPackageManifest(File manifest) throws AndrolibException {
@ -141,15 +144,15 @@ public class ResourcesDecoder {
// 3) Check if pkgOriginal === mPackageRenamed
// 4) Check if pkgOriginal is ignored via IGNORED_PACKAGES
if (pkgOriginal == null || pkgRenamed == null || pkgOriginal.equals(pkgRenamed)
|| (Arrays.asList(IGNORED_PACKAGES).contains(pkgOriginal))) {
|| IGNORED_PACKAGES.contains(pkgOriginal)) {
LOGGER.info("Regular manifest package...");
} else {
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()) {
return;
}
@ -165,11 +168,11 @@ public class ResourcesDecoder {
decoders.setDecoder("xml", new ResXmlPullStreamDecoder(axmlParser, xmlSerializer));
ResFileDecoder fileDecoder = new ResFileDecoder(decoders);
Directory in, out, outRes;
Directory inDir, outDir;
try {
in = mApkInfo.getApkFile().getDirectory();
out = new ExtFile(outDir).getDirectory().createDir("res");
inDir = mApkInfo.getApkFile().getDirectory();
outDir = new ExtFile(apkDir).getDirectory().createDir("res");
} catch (DirectoryException ex) {
throw new AndrolibException(ex);
}
@ -177,15 +180,15 @@ public class ResourcesDecoder {
for (ResPackage pkg : mResTable.listMainPackages()) {
LOGGER.info("Decoding file-resources...");
for (ResResource res : pkg.listFiles()) {
fileDecoder.decode(res, in, out, mResFileMapping);
fileDecoder.decode(res, inDir, outDir, mResFileMapping);
}
LOGGER.info("Decoding values */* XMLs...");
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();
@ -207,11 +210,10 @@ public class ResourcesDecoder {
}
}
private void generateValuesFile(ResValuesFile valuesFile, Directory out,
XmlSerializer serial) throws AndrolibException {
try {
OutputStream outStream = out.getFileOutput(valuesFile.getPath());
serial.setOutput(outStream, null);
private void generateValuesFile(ResValuesFile valuesFile, Directory resDir, XmlSerializer serial)
throws AndrolibException {
try (OutputStream out = resDir.getFileOutput(valuesFile.getPath())) {
serial.setOutput(out, null);
serial.startDocument(null, null);
serial.startTag(null, "resources");
@ -225,17 +227,15 @@ public class ResourcesDecoder {
serial.endTag(null, "resources");
serial.endDocument();
serial.flush();
outStream.close();
} catch (DirectoryException | IOException ex) {
throw new AndrolibException("Could not generate: " + valuesFile.getPath(), ex);
}
}
private void generatePublicXml(ResPackage pkg, Directory out,
XmlSerializer serial) throws AndrolibException {
try {
OutputStream outStream = out.getFileOutput("values/public.xml");
serial.setOutput(outStream, null);
private void generatePublicXml(ResPackage pkg, Directory resDir, XmlSerializer serial)
throws AndrolibException {
try (OutputStream out = resDir.getFileOutput("values/public.xml")) {
serial.setOutput(out, null);
serial.startDocument(null, null);
serial.startTag(null, "resources");
@ -250,7 +250,6 @@ public class ResourcesDecoder {
serial.endTag(null, "resources");
serial.endDocument();
serial.flush();
outStream.close();
} catch (DirectoryException | IOException ex) {
throw new AndrolibException("Could not generate public.xml file", ex);
}

View File

@ -19,6 +19,165 @@ package brut.androlib.res.data;
import java.util.logging.Logger;
public class ResConfigFlags {
private static final Logger LOGGER = Logger.getLogger(ResConfigFlags.class.getName());
public static final byte SDK_BASE = 1;
public static final byte SDK_BASE_1_1 = 2;
public static final byte SDK_CUPCAKE = 3;
public static final byte SDK_DONUT = 4;
public static final byte SDK_ECLAIR = 5;
public static final byte SDK_ECLAIR_0_1 = 6;
public static final byte SDK_ECLAIR_MR1 = 7;
public static final byte SDK_FROYO = 8;
public static final byte SDK_GINGERBREAD = 9;
public static final byte SDK_GINGERBREAD_MR1 = 10;
public static final byte SDK_HONEYCOMB = 11;
public static final byte SDK_HONEYCOMB_MR1 = 12;
public static final byte SDK_HONEYCOMB_MR2 = 13;
public static final byte SDK_ICE_CREAM_SANDWICH = 14;
public static final byte SDK_ICE_CREAM_SANDWICH_MR1 = 15;
public static final byte SDK_JELLY_BEAN = 16;
public static final byte SDK_JELLY_BEAN_MR1 = 17;
public static final byte SDK_JELLY_BEAN_MR2 = 18;
public static final byte SDK_KITKAT = 19;
public static final byte SDK_LOLLIPOP = 21;
public static final byte SDK_LOLLIPOP_MR1 = 22;
public static final byte SDK_MNC = 23;
public static final byte SDK_NOUGAT = 24;
public static final byte SDK_NOUGAT_MR1 = 25;
public static final byte SDK_OREO = 26;
public static final byte SDK_OREO_MR1 = 27;
public static final byte SDK_P = 28;
public static final byte SDK_Q = 29;
public static final byte SDK_R = 30;
public static final byte SDK_S = 31;
public static final byte SDK_S_V2 = 32;
public static final byte SDK_TIRAMISU = 33;
public static final byte SDK_UPSIDEDOWN_CAKE = 34;
public static final byte SDK_VANILLA_ICE_CREAM = 35;
// 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 mnc;
@ -137,7 +296,7 @@ public class ResConfigFlags {
if (localeVariant[0] == '\00') {
localeVariant = null;
}
} else {
} else {
localeVariant = null;
}
@ -522,172 +681,13 @@ public class ResConfigFlags {
return false;
}
final ResConfigFlags other = (ResConfigFlags) obj;
return this.mQualifiers.equals(other.mQualifiers);
return mQualifiers.equals(other.mQualifiers);
}
@Override
public int hashCode() {
int hash = 17;
hash = 31 * hash + this.mQualifiers.hashCode();
hash = 31 * hash + mQualifiers.hashCode();
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());
}

View File

@ -45,7 +45,7 @@ public class ResID {
@Override
public int hashCode() {
int hash = 17;
hash = 31 * hash + this.id;
hash = 31 * hash + id;
return hash;
}
@ -58,6 +58,6 @@ public class ResID {
return false;
}
final ResID other = (ResID) obj;
return this.id == other.id;
return id == other.id;
}
}

View File

@ -26,20 +26,26 @@ import java.util.*;
import java.util.logging.Logger;
public class ResPackage {
private static final Logger LOGGER = Logger.getLogger(ResPackage.class.getName());
private final ResTable mResTable;
private final int mId;
private final String mName;
private final Map<ResID, ResResSpec> mResSpecs = new LinkedHashMap<>();
private final Map<ResConfigFlags, ResType> mConfigs = new LinkedHashMap<>();
private final Map<String, ResTypeSpec> mTypes = new LinkedHashMap<>();
private final Set<ResID> mSynthesizedRes = new HashSet<>();
private final Map<ResID, ResResSpec> mResSpecs;
private final Map<ResConfigFlags, ResType> mConfigs;
private final Map<String, ResTypeSpec> mTypes;
private final Set<ResID> mSynthesizedRes;
private ResValueFactory mValueFactory;
public ResPackage(ResTable resTable, int id, String name) {
this.mResTable = resTable;
this.mId = id;
this.mName = name;
mResTable = resTable;
mId = id;
mName = name;
mResSpecs = new LinkedHashMap<>();
mConfigs = new LinkedHashMap<>();
mTypes = new LinkedHashMap<>();
mSynthesizedRes = new HashSet<>();
}
public List<ResResSpec> listResSpecs() {
@ -159,17 +165,17 @@ public class ResPackage {
return false;
}
final ResPackage other = (ResPackage) obj;
if (!Objects.equals(this.mResTable, other.mResTable)) {
if (!Objects.equals(mResTable, other.mResTable)) {
return false;
}
return this.mId == other.mId;
return mId == other.mId;
}
@Override
public int hashCode() {
int hash = 17;
hash = 31 * hash + (this.mResTable != null ? this.mResTable.hashCode() : 0);
hash = 31 * hash + this.mId;
hash = 31 * hash + (mResTable != null ? mResTable.hashCode() : 0);
hash = 31 * hash + mId;
return hash;
}
@ -179,6 +185,4 @@ public class ResPackage {
}
return mValueFactory;
}
private final static Logger LOGGER = Logger.getLogger(ResPackage.class.getName());
}

View File

@ -18,37 +18,36 @@ package brut.androlib.res.data;
import brut.androlib.exceptions.AndrolibException;
import brut.androlib.exceptions.UndefinedResObjectException;
import com.google.common.collect.Sets;
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.LinkedHashSet;
import java.util.Map;
import java.util.Set;
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 String mName;
private final ResPackage mPackage;
private final ResTypeSpec mType;
private final Map<ResConfigFlags, ResResource> mResources = new LinkedHashMap<>();
private static final Set<String> EMPTY_RESOURCE_NAMES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
"0_resource_name_obfuscated",
"(name removed)"
)));
private final Map<ResConfigFlags, ResResource> mResources;
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)) {
name = "APKTOOL_DUMMYVAL_" + id.toString();
} else if (type.getResSpecUnsafe(name) != null) {
name = String.format("APKTOOL_DUPLICATE_%s_%s", type, id.toString());
}
this.mName = name;
this.mPackage = pkg;
this.mType = type;
mName = name;
mPackage = pkg;
mType = type;
mResources = new LinkedHashMap<>();
}
public Set<ResResource> listResources() {

View File

@ -25,9 +25,9 @@ public class ResResource {
private final ResValue mValue;
public ResResource(ResType config, ResResSpec spec, ResValue value) {
this.mConfig = config;
this.mResSpec = spec;
this.mValue = value;
mConfig = config;
mResSpec = spec;
mValue = value;
}
public String getFilePath() {

View File

@ -25,33 +25,30 @@ import brut.androlib.apk.UsesFramework;
import brut.androlib.res.Framework;
import brut.androlib.res.data.value.ResValue;
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.DirectoryException;
import brut.directory.ExtFile;
import com.google.common.base.Strings;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.*;
import java.util.*;
import java.util.logging.Logger;
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 ApkInfo mApkInfo;
private final Map<Integer, ResPackage> mPackagesById = new HashMap<>();
private final Map<String, ResPackage> mPackagesByName = new HashMap<>();
private final Set<ResPackage> mMainPackages = new LinkedHashSet<>();
private final Set<ResPackage> mFramePackages = new LinkedHashSet<>();
private final Map<Integer, ResPackage> mPackagesById;
private final Map<String, ResPackage> mPackagesByName;
private final Set<ResPackage> mMainPackages;
private final Set<ResPackage> mFramePackages;
private String mPackageRenamed;
private String mPackageOriginal;
private int mPackageId;
private boolean mMainPkgLoaded = false;
private boolean mMainPkgLoaded;
public ResTable() {
this(Config.getDefaultConfig(), new ApkInfo());
@ -64,6 +61,10 @@ public class ResTable {
public ResTable(Config config, ApkInfo apkInfo) {
mConfig = config;
mApkInfo = apkInfo;
mPackagesById = new HashMap<>();
mPackagesByName = new HashMap<>();
mMainPackages = new LinkedHashSet<>();
mFramePackages = new LinkedHashSet<>();
}
public boolean getAnalysisMode() {
@ -175,11 +176,13 @@ public class ResTable {
return pkg;
}
private ResPackage[] loadResPackagesFromApk(ExtFile apkFile, boolean keepBrokenResources) throws AndrolibException {
private ResPackage[] loadResPackagesFromApk(ExtFile apkFile, boolean keepBrokenResources)
throws AndrolibException {
try {
Directory dir = apkFile.getDirectory();
try (BufferedInputStream bis = new BufferedInputStream(dir.getFileInput("resources.arsc"))) {
return ARSCDecoder.decode(bis, false, keepBrokenResources, this).getPackages();
try (BufferedInputStream in = new BufferedInputStream(dir.getFileInput("resources.arsc"))) {
ARSCDecoder decoder = new ARSCDecoder(in, this, false, keepBrokenResources);
return decoder.decode().getPackages();
}
} catch (DirectoryException | IOException ex) {
throw new AndrolibException("Could not load resources.arsc from file: " + apkFile, ex);
@ -220,8 +223,8 @@ public class ResTable {
return pkg;
}
public ResValue getValue(String package_, String type, String name) throws AndrolibException {
return getPackage(package_).getType(type).getResSpec(name).getDefaultResource().getValue();
public ResValue getValue(String pkg, String type, String name) throws AndrolibException {
return getPackage(pkg).getType(type).getResSpec(name).getDefaultResource().getValue();
}
public void addPackage(ResPackage pkg, boolean main) throws AndrolibException {
@ -304,14 +307,14 @@ public class ResTable {
return false;
}
public void initApkInfo(ApkInfo apkInfo, File outDir) throws AndrolibException {
public void initApkInfo(ApkInfo apkInfo, File apkDir) throws AndrolibException {
apkInfo.isFrameworkApk = isFrameworkApk();
apkInfo.usesFramework = getUsesFramework();
if (!mApkInfo.sdkInfo.isEmpty()) {
updateSdkInfoFromResources(outDir);
updateSdkInfoFromResources(apkDir);
}
initPackageInfo();
loadVersionName(outDir);
loadVersionName(apkDir);
}
private UsesFramework getUsesFramework() {
@ -327,24 +330,24 @@ public class ResTable {
return info;
}
private void updateSdkInfoFromResources(File outDir) {
private void updateSdkInfoFromResources(File apkDir) {
String minSdkVersion = mApkInfo.getMinSdkVersion();
if (minSdkVersion != null) {
String refValue = ResXmlPatcher.pullValueFromIntegers(outDir, minSdkVersion);
String refValue = ResXmlUtils.pullValueFromIntegers(apkDir, minSdkVersion);
if (refValue != null) {
mApkInfo.setMinSdkVersion(refValue);
}
}
String targetSdkVersion = mApkInfo.getTargetSdkVersion();
if (targetSdkVersion != null) {
String refValue = ResXmlPatcher.pullValueFromIntegers(outDir, targetSdkVersion);
String refValue = ResXmlUtils.pullValueFromIntegers(apkDir, targetSdkVersion);
if (refValue != null) {
mApkInfo.setTargetSdkVersion(refValue);
}
}
String maxSdkVersion = mApkInfo.getMaxSdkVersion();
if (maxSdkVersion != null) {
String refValue = ResXmlPatcher.pullValueFromIntegers(outDir, maxSdkVersion);
String refValue = ResXmlUtils.pullValueFromIntegers(apkDir, maxSdkVersion);
if (refValue != null) {
mApkInfo.setMaxSdkVersion(refValue);
}
@ -371,9 +374,9 @@ public class ResTable {
mApkInfo.packageInfo.forcedPackageId = String.valueOf(id);
}
private void loadVersionName(File outDir) {
private void loadVersionName(File apkDir) {
String versionName = mApkInfo.versionInfo.versionName;
String refValue = ResXmlPatcher.pullValueFromStrings(outDir, versionName);
String refValue = ResXmlUtils.pullValueFromStrings(apkDir, versionName);
if (refValue != null) {
mApkInfo.versionInfo.versionName = refValue;
}

View File

@ -22,10 +22,11 @@ import java.util.*;
public class ResType {
private final ResConfigFlags mFlags;
private final Map<ResResSpec, ResResource> mResources = new LinkedHashMap<>();
private final Map<ResResSpec, ResResource> mResources;
public ResType(ResConfigFlags flags) {
this.mFlags = flags;
mFlags = flags;
mResources = new LinkedHashMap<>();
}
public ResResource getResource(ResResSpec spec) throws AndrolibException {

View File

@ -21,7 +21,6 @@ import brut.androlib.exceptions.UndefinedResObjectException;
import java.util.*;
public final class ResTypeSpec {
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_PRIVATE = "^attr-private";
@ -30,13 +29,13 @@ public final class ResTypeSpec {
public static final String RES_TYPE_NAME_STYLES = "style";
private final String mName;
private final Map<String, ResResSpec> mResSpecs = new LinkedHashMap<>();
private final int mId;
private final Map<String, ResResSpec> mResSpecs;
public ResTypeSpec(String name, int id) {
this.mName = name;
this.mId = id;
mName = name;
mId = id;
mResSpecs = new LinkedHashMap<>();
}
public String getName() {

View File

@ -24,12 +24,13 @@ public class ResValuesFile {
private final ResPackage mPackage;
private final ResTypeSpec mType;
private final ResType mConfig;
private final Set<ResResource> mResources = new LinkedHashSet<>();
private final Set<ResResource> mResources;
public ResValuesFile(ResPackage pkg, ResTypeSpec type, ResType config) {
this.mPackage = pkg;
this.mType = type;
this.mConfig = config;
mPackage = pkg;
mType = type;
mConfig = config;
mResources = new LinkedHashSet<>();
}
public String getPath() {
@ -63,17 +64,17 @@ public class ResValuesFile {
return false;
}
final ResValuesFile other = (ResValuesFile) obj;
if (!Objects.equals(this.mType, other.mType)) {
if (!Objects.equals(mType, other.mType)) {
return false;
}
return Objects.equals(this.mConfig, other.mConfig);
return Objects.equals(mConfig, other.mConfig);
}
@Override
public int hashCode() {
int hash = 17;
hash = 31 * hash + (this.mType != null ? this.mType.hashCode() : 0);
hash = 31 * hash + (this.mConfig != null ? this.mConfig.hashCode() : 0);
hash = 31 * hash + (mType != null ? mType.hashCode() : 0);
hash = 31 * hash + (mConfig != null ? mConfig.hashCode() : 0);
return hash;
}
}

View File

@ -22,11 +22,13 @@ import brut.androlib.res.data.ResPackage;
import java.util.logging.Logger;
public class ARSCData {
private static final Logger LOGGER = Logger.getLogger(ARSCData.class.getName());
private final ResPackage[] mPackages;
private final FlagsOffset[] mFlagsOffsets;
public ARSCData(ResPackage[] packages, FlagsOffset[] flagsOffsets) {
mPackages = packages;
public ARSCData(ResPackage[] pkgs, FlagsOffset[] flagsOffsets) {
mPackages = pkgs;
mFlagsOffsets = flagsOffsets;
}
@ -63,6 +65,4 @@ public class ARSCData {
}
return id;
}
private static final Logger LOGGER = Logger.getLogger(ARSCData.class.getName());
}

View File

@ -16,7 +16,6 @@
*/
package brut.androlib.res.data.arsc;
import brut.util.ExtCountingDataInput;
import brut.util.ExtDataInput;
import java.io.EOFException;
@ -25,13 +24,40 @@ import java.math.BigInteger;
import java.util.logging.Logger;
public class ARSCHeader {
private static final Logger LOGGER = Logger.getLogger(ARSCHeader.class.getName());
public static final short RES_NONE_TYPE = -1;
public static final short RES_NULL_TYPE = 0x0000;
public static final short RES_STRING_POOL_TYPE = 0x0001;
public static final short RES_TABLE_TYPE = 0x0002;
public static final short RES_XML_TYPE = 0x0003;
// 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 int headerSize;
public final int chunkSize;
public final int startPosition;
public final int endPosition;
public final long startPosition;
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.headerSize = headerSize;
this.chunkSize = chunkSize;
@ -39,9 +65,9 @@ public class ARSCHeader {
this.endPosition = headerStart + chunkSize;
}
public static ARSCHeader read(ExtCountingDataInput in) throws IOException {
public static ARSCHeader read(ExtDataInput in) throws IOException {
short type;
int start = in.position();
long start = in.position();
try {
type = in.readShort();
} catch (EOFException ex) {
@ -50,13 +76,13 @@ public class ARSCHeader {
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
// 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
// header and the body.
int actualHeaderSize = in.position() - this.startPosition;
int exceedingSize = this.headerSize - actualHeaderSize;
int actualHeaderSize = (int) (in.position() - startPosition);
int exceedingSize = headerSize - actualHeaderSize;
if (exceedingSize > 0) {
byte[] buf = new byte[exceedingSize];
in.readFully(buf);
@ -64,11 +90,11 @@ public class ARSCHeader {
if (exceedingBI.equals(BigInteger.ZERO)) {
LOGGER.fine(String.format("Chunk header size (%d), read (%d), but exceeding bytes are all zero.",
this.headerSize, actualHeaderSize
headerSize, actualHeaderSize
));
} else {
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 {
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());
}

View File

@ -19,7 +19,7 @@ package brut.androlib.res.data.arsc;
import brut.androlib.res.data.value.ResValue;
public class EntryData {
public short mFlags;
public int mSpecNamesId;
public ResValue mValue;
public short flags;
public int specNamesId;
public ResValue value;
}

View File

@ -31,38 +31,38 @@ package brut.androlib.res.data.axml;
* !! functions expect 'prefix'+'uri' pairs, not 'uri'+'prefix' !!
*/
public final class NamespaceStack {
private int[] m_data;
private int m_dataLength;
private int m_depth;
private int[] mData;
private int mDataLength;
private int mDepth;
public NamespaceStack() {
m_data = new int[32];
mData = new int[32];
}
public void reset() {
m_dataLength = 0;
m_depth = 0;
mDataLength = 0;
mDepth = 0;
}
public int getCurrentCount() {
if (m_dataLength == 0) {
if (mDataLength == 0) {
return 0;
}
int offset = m_dataLength - 1;
return m_data[offset];
int offset = mDataLength - 1;
return mData[offset];
}
public int getAccumulatedCount(int depth) {
if (m_dataLength == 0 || depth < 0) {
if (mDataLength == 0 || depth < 0) {
return 0;
}
if (depth > m_depth) {
depth = m_depth;
if (depth > mDepth) {
depth = mDepth;
}
int accumulatedCount = 0;
int offset = 0;
for (; depth != 0; --depth) {
int count = m_data[offset];
int count = mData[offset];
accumulatedCount += count;
offset += (2 + count * 2);
}
@ -70,34 +70,34 @@ public final class NamespaceStack {
}
public void push(int prefix, int uri) {
if (m_depth == 0) {
if (mDepth == 0) {
increaseDepth();
}
ensureDataCapacity(2);
int offset = m_dataLength - 1;
int count = m_data[offset];
m_data[offset - 1 - count * 2] = count + 1;
m_data[offset] = prefix;
m_data[offset + 1] = uri;
m_data[offset + 2] = count + 1;
m_dataLength += 2;
int offset = mDataLength - 1;
int count = mData[offset];
mData[offset - 1 - count * 2] = count + 1;
mData[offset] = prefix;
mData[offset + 1] = uri;
mData[offset + 2] = count + 1;
mDataLength += 2;
}
public boolean pop() {
if (m_dataLength == 0) {
if (mDataLength == 0) {
return false;
}
int offset = m_dataLength - 1;
int count = m_data[offset];
int offset = mDataLength - 1;
int count = mData[offset];
if (count == 0) {
return false;
}
count -= 1;
offset -= 2;
m_data[offset] = count;
mData[offset] = count;
offset -= (1 + count * 2);
m_data[offset] = count;
m_dataLength -= 2;
mData[offset] = count;
mDataLength -= 2;
return true;
}
@ -114,58 +114,58 @@ public final class NamespaceStack {
}
public int getDepth() {
return m_depth;
return mDepth;
}
public void increaseDepth() {
ensureDataCapacity(2);
int offset = m_dataLength;
m_data[offset] = 0;
m_data[offset + 1] = 0;
m_dataLength += 2;
m_depth += 1;
int offset = mDataLength;
mData[offset] = 0;
mData[offset + 1] = 0;
mDataLength += 2;
mDepth += 1;
}
public void decreaseDepth() {
if (m_dataLength == 0) {
if (mDataLength == 0) {
return;
}
int offset = m_dataLength - 1;
int count = m_data[offset];
int offset = mDataLength - 1;
int count = mData[offset];
if ((offset - 1 - count * 2) == 0) {
return;
}
m_dataLength -= 2 + count * 2;
m_depth -= 1;
mDataLength -= 2 + count * 2;
mDepth -= 1;
}
private void ensureDataCapacity(int capacity) {
int available = (m_data.length - m_dataLength);
int available = (mData.length - mDataLength);
if (available > capacity) {
return;
}
int newLength = (m_data.length + available) * 2;
int newLength = (mData.length + available) * 2;
int[] newData = new int[newLength];
System.arraycopy(m_data, 0, newData, 0, m_dataLength);
m_data = newData;
System.arraycopy(mData, 0, newData, 0, mDataLength);
mData = newData;
}
private int find(int prefixOrUri, boolean prefix) {
if (m_dataLength == 0) {
if (mDataLength == 0) {
return -1;
}
int offset = m_dataLength - 1;
for (int i = m_depth; i != 0; --i) {
int count = m_data[offset];
int offset = mDataLength - 1;
for (int i = mDepth; i != 0; --i) {
int count = mData[offset];
offset -= 2;
for (; count != 0; --count) {
if (prefix) {
if (m_data[offset] == prefixOrUri) {
return m_data[offset + 1];
if (mData[offset] == prefixOrUri) {
return mData[offset + 1];
}
} else {
if (m_data[offset + 1] == prefixOrUri) {
return m_data[offset];
if (mData[offset + 1] == prefixOrUri) {
return mData[offset];
}
}
offset -= 2;
@ -175,12 +175,12 @@ public final class NamespaceStack {
}
private int get(int index, boolean prefix) {
if (m_dataLength == 0 || index < 0) {
if (mDataLength == 0 || index < 0) {
return -1;
}
int offset = 0;
for (int i = m_depth; i != 0; --i) {
int count = m_data[offset];
for (int i = mDepth; i != 0; --i) {
int count = mData[offset];
if (index >= count) {
index -= count;
offset += (2 + count * 2);
@ -190,7 +190,7 @@ public final class NamespaceStack {
if (!prefix) {
offset += 1;
}
return m_data[offset];
return mData[offset];
}
return -1;
}

View File

@ -17,6 +17,7 @@
package brut.androlib.res.data.ninepatch;
import brut.util.ExtDataInput;
import java.io.IOException;
public class NinePatchData {
@ -32,19 +33,19 @@ public class NinePatchData {
this.yDivs = yDivs;
}
public static NinePatchData decode(ExtDataInput di) throws IOException {
di.skipBytes(1); // wasDeserialized
byte numXDivs = di.readByte();
byte numYDivs = di.readByte();
di.skipBytes(1); // numColors
di.skipBytes(8); // xDivs/yDivs offset
int padLeft = di.readInt();
int padRight = di.readInt();
int padTop = di.readInt();
int padBottom = di.readInt();
di.skipBytes(4); // colorsOffset
int[] xDivs = di.readIntArray(numXDivs);
int[] yDivs = di.readIntArray(numYDivs);
public static NinePatchData decode(ExtDataInput in) throws IOException {
in.skipBytes(1); // wasDeserialized
byte numXDivs = in.readByte();
byte numYDivs = in.readByte();
in.skipBytes(1); // numColors
in.skipBytes(8); // xDivs/yDivs offset
int padLeft = in.readInt();
int padRight = in.readInt();
int padTop = in.readInt();
int padBottom = in.readInt();
in.skipBytes(4); // colorsOffset
int[] xDivs = in.readIntArray(numXDivs);
int[] yDivs = in.readIntArray(numYDivs);
return new NinePatchData(padLeft, padRight, padTop, padBottom, xDivs, yDivs);
}

View File

@ -17,6 +17,7 @@
package brut.androlib.res.data.ninepatch;
import brut.util.ExtDataInput;
import java.io.IOException;
public class OpticalInset {
@ -29,11 +30,11 @@ public class OpticalInset {
this.layoutBoundsBottom = layoutBoundsBottom;
}
public static OpticalInset decode(ExtDataInput di) throws IOException {
int layoutBoundsLeft = Integer.reverseBytes(di.readInt());
int layoutBoundsTop = Integer.reverseBytes(di.readInt());
int layoutBoundsRight = Integer.reverseBytes(di.readInt());
int layoutBoundsBottom = Integer.reverseBytes(di.readInt());
public static OpticalInset decode(ExtDataInput in) throws IOException {
int layoutBoundsLeft = Integer.reverseBytes(in.readInt());
int layoutBoundsTop = Integer.reverseBytes(in.readInt());
int layoutBoundsRight = Integer.reverseBytes(in.readInt());
int layoutBoundsBottom = Integer.reverseBytes(in.readInt());
return new OpticalInset(layoutBoundsLeft, layoutBoundsTop, layoutBoundsRight, layoutBoundsBottom);
}
}

View File

@ -20,15 +20,19 @@ import brut.androlib.exceptions.AndrolibException;
import brut.androlib.res.data.ResResource;
import brut.androlib.res.xml.ResValuesXmlSerializable;
import brut.util.Duo;
import com.google.common.collect.Sets;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.util.Arrays;
import java.util.Set;
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) {
super(parent);
mItems = new ResScalarValue[items.length];
for (int i = 0; i < items.length; i++) {
mItems[i] = items[i].m2;
@ -83,12 +87,9 @@ public class ResArrayValue extends ResBagValue implements ResValuesXmlSerializab
return null;
}
}
if (!Arrays.asList(AllowedArrayTypes).contains(type)) {
if (!ALLOWED_ARRAY_TYPES.contains(type)) {
return "string";
}
return type;
}
private final ResScalarValue[] mItems;
private final String[] AllowedArrayTypes = {"string", "integer"};
}

View File

@ -26,6 +26,28 @@ import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
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) {
super(parentVal);
mType = type;
@ -139,26 +161,4 @@ public class ResAttr extends ResBagValue implements ResValuesXmlSerializable {
}
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;
}

View File

@ -28,7 +28,7 @@ public class ResBagValue extends ResValue implements ResValuesXmlSerializable {
protected final ResReferenceValue mParent;
public ResBagValue(ResReferenceValue parent) {
this.mParent = parent;
mParent = parent;
}
@Override

View File

@ -21,7 +21,7 @@ public class ResBoolValue extends ResScalarValue {
public ResBoolValue(boolean value, int rawIntValue, String rawValue) {
super("bool", rawIntValue, rawValue);
this.mValue = value;
mValue = value;
}
public boolean getValue() {

View File

@ -17,6 +17,7 @@
package brut.androlib.res.data.value;
public class ResColorValue extends ResIntValue {
public ResColorValue(int value, String rawValue) {
super(value, rawValue, "color");
}

View File

@ -20,6 +20,7 @@ import android.util.TypedValue;
import brut.androlib.exceptions.AndrolibException;
public class ResDimenValue extends ResIntValue {
public ResDimenValue(int value, String rawValue) {
super(value, rawValue, "dimen");
}

View File

@ -20,18 +20,18 @@ import brut.androlib.exceptions.AndrolibException;
public class ResEmptyValue extends ResScalarValue {
protected final int mValue;
protected int type;
protected int mType;
public ResEmptyValue(int value, String rawValue, int type) {
this(value, rawValue, "integer");
this.type = type;
mType = type;
}
public ResEmptyValue(int value, String rawValue, String type) {
super(type, value, rawValue);
if (value != 1)
throw new UnsupportedOperationException();
this.mValue = value;
mValue = value;
}
public int getValue() {

View File

@ -28,10 +28,16 @@ import java.util.Map;
import java.util.logging.Logger;
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,
Boolean l10n, Duo<ResReferenceValue, ResScalarValue>[] items) {
super(parent, type, min, max, l10n);
mItems = items;
mItemsCache = new HashMap<>();
}
@Override
@ -85,9 +91,4 @@ public class ResEnumAttr extends ResAttr {
}
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());
}

View File

@ -23,7 +23,7 @@ public class ResFileValue extends ResIntBasedValue {
public ResFileValue(String path, int rawIntValue) {
super(rawIntValue);
this.mPath = path;
mPath = path;
}
public String getStrippedPath() throws AndrolibException {

View File

@ -28,6 +28,12 @@ import java.util.Arrays;
import java.util.logging.Logger;
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,
Boolean l10n, Duo<ResReferenceValue, ResScalarValue>[] items) {
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)));
}
private final FlagItem[] mItems;
private FlagItem[] mZeroFlags;
private FlagItem[] mFlags;
private static final Logger LOGGER = Logger.getLogger(ResFlagsAttr.class.getName());
}

View File

@ -21,7 +21,7 @@ public class ResFloatValue extends ResScalarValue {
public ResFloatValue(float value, int rawIntValue, String rawValue) {
super("float", rawIntValue, rawValue);
this.mValue = value;
mValue = value;
}
public float getValue() {

View File

@ -20,6 +20,7 @@ import android.util.TypedValue;
import brut.androlib.exceptions.AndrolibException;
public class ResFractionValue extends ResIntValue {
public ResFractionValue(int value, String rawValue) {
super(value, rawValue, "fraction");
}

View File

@ -23,6 +23,7 @@ import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
public class ResIdValue extends ResValue implements ResValuesXmlSerializable {
@Override
public void serializeToResValuesXml(XmlSerializer serializer, ResResource res) throws IOException {
serializer.startTag(null, "item");

View File

@ -21,16 +21,16 @@ import brut.androlib.exceptions.AndrolibException;
public class ResIntValue extends ResScalarValue {
protected final int mValue;
private int type;
private int mType;
public ResIntValue(int value, String rawValue, int type) {
this(value, rawValue, "integer");
this.type = type;
mType = type;
}
public ResIntValue(int value, String rawValue, String type) {
super(type, value, rawValue);
this.mValue = value;
mValue = value;
}
public int getValue() {
@ -39,6 +39,6 @@ public class ResIntValue extends ResScalarValue {
@Override
protected String encodeAsResXml() throws AndrolibException {
return TypedValue.coerceToString(type, mValue);
return TypedValue.coerceToString(mType, mValue);
}
}

View File

@ -26,9 +26,14 @@ import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
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) {
super(parent);
mItems = new ResScalarValue[6];
for (Duo<Integer, ResScalarValue> item : items) {
mItems[item.m1 - BAG_KEY_PLURALS_START] = item.m2;
@ -53,9 +58,4 @@ public class ResPluralsValue extends ResBagValue implements ResValuesXmlSerializ
}
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" };
}

View File

@ -25,14 +25,13 @@ public class ResReferenceValue extends ResIntValue {
private final ResPackage mPackage;
private final boolean mTheme;
public ResReferenceValue(ResPackage package_, int value, String rawValue) {
this(package_, value, rawValue, false);
public ResReferenceValue(ResPackage pkg, int value, String rawValue) {
this(pkg, value, rawValue, false);
}
public ResReferenceValue(ResPackage package_, int value, String rawValue,
boolean theme) {
public ResReferenceValue(ResPackage pkg, int value, String rawValue, boolean theme) {
super(value, rawValue, "reference");
mPackage = package_;
mPackage = pkg;
mTheme = theme;
}

View File

@ -115,8 +115,9 @@ public abstract class ResScalarValue extends ResIntBasedValue implements
return mType;
}
protected void serializeExtraXmlAttrs(XmlSerializer serializer,
ResResource res) throws IOException {
protected void serializeExtraXmlAttrs(XmlSerializer serializer, ResResource res)
throws IOException {
// stub
}
protected abstract String encodeAsResXml() throws AndrolibException;

View File

@ -25,6 +25,8 @@ import java.io.IOException;
import java.util.regex.Pattern;
public class ResStringValue extends ResScalarValue {
private static final Pattern ALL_DIGITS = Pattern.compile("\\d{9,}");
public ResStringValue(String value, int rawValue) {
this(value, rawValue, "string");
}
@ -64,8 +66,6 @@ public class ResStringValue extends ResScalarValue {
if (val == null || val.isEmpty()) {
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,}");
}

View File

@ -29,9 +29,12 @@ import java.util.Set;
import java.util.logging.Logger;
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) {
super(parent);
mItems = new Duo[items.length];
for (int i = 0; i < items.length; i++) {
mItems[i] = new Duo<>(
@ -97,8 +100,4 @@ public class ResStyleValue extends ResBagValue implements ResValuesXmlSerializab
serializer.endTag(null, "style");
processedNames.clear();
}
private final Duo<ResReferenceValue, ResScalarValue>[] mItems;
private static final Logger LOGGER = Logger.getLogger(ResStyleValue.class.getName());
}

View File

@ -19,6 +19,7 @@ package brut.androlib.res.data.value;
import brut.androlib.Config;
public class ResValue {
public boolean shouldRemoveUnknownRes() {
return Config.getInstance().isDecodeResolveModeRemoving();
}

View File

@ -25,8 +25,8 @@ import brut.util.Duo;
public class ResValueFactory {
private final ResPackage mPackage;
public ResValueFactory(ResPackage package_) {
this.mPackage = package_;
public ResValueFactory(ResPackage pkg) {
mPackage = pkg;
}
public ResScalarValue factory(int type, int value, String rawValue) throws AndrolibException {

View File

@ -22,8 +22,7 @@ import brut.androlib.res.data.*;
import brut.androlib.res.data.arsc.*;
import brut.androlib.res.data.value.*;
import brut.util.Duo;
import brut.util.ExtCountingDataInput;
import com.google.common.io.LittleEndianDataInputStream;
import brut.util.ExtDataInputStream;
import java.io.*;
import java.math.BigInteger;
@ -31,37 +30,57 @@ import java.util.*;
import java.util.logging.Logger;
public class ARSCDecoder {
public static ARSCData decode(InputStream arscStream, boolean findFlagsOffsets, boolean keepBroken)
throws AndrolibException {
return decode(arscStream, findFlagsOffsets, keepBroken, new ResTable());
private static final Logger LOGGER = Logger.getLogger(ARSCDecoder.class.getName());
private static final short ENTRY_FLAG_COMPLEX = 0x0001;
private static final short ENTRY_FLAG_PUBLIC = 0x0002;
private static final short ENTRY_FLAG_WEAK = 0x0004;
private static final short ENTRY_FLAG_COMPACT = 0x0008;
private static final 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,
ResTable resTable)
throws AndrolibException {
public ARSCData decode() throws AndrolibException {
try {
ARSCDecoder decoder = new ARSCDecoder(arscStream, resTable, findFlagsOffsets, keepBroken);
ResPackage[] pkgs = decoder.readResourceTable();
return new ARSCData(pkgs, decoder.mFlagsOffsets == null
? null
: decoder.mFlagsOffsets.toArray(new FlagsOffset[0]));
ResPackage[] pkgs = readResourceTable();
FlagsOffset[] flagsOffsets = mFlagsOffsets != null ? mFlagsOffsets.toArray(new FlagsOffset[0]) : null;
return new ARSCData(pkgs, flagsOffsets);
} catch (IOException 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 {
Set<ResPackage> pkgs = new LinkedHashSet<>();
ResTypeSpec typeSpec;
@ -170,7 +189,7 @@ public class ARSCDecoder {
// TypeIdOffset was added platform_frameworks_base/@f90f2f8dc36e7243b85e0b6a7fd5a590893c827e
// 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) {
mTypeIdOffset = mIn.readInt();
}
@ -246,7 +265,7 @@ public class ARSCDecoder {
int entryCount = mIn.readInt();
if (mFlagsOffsets != null) {
mFlagsOffsets.add(new FlagsOffset(mIn.position(), entryCount));
mFlagsOffsets.add(new FlagsOffset((int) mIn.position(), entryCount));
}
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;
// #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) {
long bytesSkipped = mIn.skip(entriesStartAligned - mIn.position());
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.mFlags = flags;
entryData.mSpecNamesId = specNamesId;
entryData.mValue = value;
entryData.flags = flags;
entryData.specNamesId = specNamesId;
entryData.value = value;
return entryData;
}
private void readEntry(EntryData entryData) throws AndrolibException {
int specNamesId = entryData.mSpecNamesId;
ResValue value = entryData.mValue;
int specNamesId = entryData.specNamesId;
ResValue value = entryData.value;
if (mTypeSpec.isString() && value instanceof ResFileValue) {
value = new ResStringValue(value.toString(), ((ResFileValue) value).getRawIntValue());
@ -481,8 +500,8 @@ public class ARSCDecoder {
int data = mIn.readInt();
return type == TypedValue.TYPE_STRING
? mPkg.getValueFactory().factory(mTableStrings.getHTML(data), data)
: mPkg.getValueFactory().factory(type, data, null);
? mPkg.getValueFactory().factory(mTableStrings.getHTML(data), data)
: mPkg.getValueFactory().factory(type, data, null);
}
private ResConfigFlags readConfigFlags() throws IOException, AndrolibException {
@ -684,36 +703,4 @@ public class ARSCDecoder {
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());
}

View File

@ -29,8 +29,7 @@ import brut.androlib.res.data.axml.NamespaceStack;
import brut.androlib.res.data.value.ResAttr;
import brut.androlib.res.data.value.ResScalarValue;
import brut.androlib.res.xml.ResXmlEncoders;
import brut.util.ExtCountingDataInput;
import com.google.common.io.LittleEndianDataInputStream;
import brut.util.ExtDataInputStream;
import org.xmlpull.v1.XmlPullParserException;
import java.io.*;
@ -47,9 +46,45 @@ import java.util.logging.Logger;
* this state methods return invalid values or throw exceptions.
*/
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) {
mResTable = resTable;
mNamespaces = new NamespaceStack();
resetEventInfo();
}
@ -64,16 +99,16 @@ public class AXmlResourceParser implements XmlResourceParser {
public void open(InputStream stream) {
close();
if (stream != null) {
mIn = new ExtCountingDataInput(new LittleEndianDataInputStream(stream));
mIn = ExtDataInputStream.littleEndian(stream);
}
}
@Override
public void close() {
if (!isOperational) {
if (!mIsOperational) {
return;
}
isOperational = false;
mIsOperational = false;
mIn = null;
mStringBlock = null;
mResourceIds = null;
@ -314,17 +349,16 @@ public class AXmlResourceParser implements XmlResourceParser {
private String getNonDefaultNamespaceUri(int offset) {
String prefix = mStringBlock.getString(mNamespaces.getPrefix(offset));
if (prefix != null) {
return mStringBlock.getString(mNamespaces.getUri(offset));
if (prefix == null) {
// 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;
}
// 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));
}
@Override
@ -404,7 +438,9 @@ public class AXmlResourceParser implements XmlResourceParser {
int valueRaw = mAttributes[offset + ATTRIBUTE_IX_VALUE_STRING];
try {
String stringBlockValue = valueRaw == -1 ? null : ResXmlEncoders.escapeXmlChars(mStringBlock.getString(valueRaw));
String stringBlockValue = valueRaw != -1
? ResXmlEncoders.escapeXmlChars(mStringBlock.getString(valueRaw))
: null;
String resourceMapValue = null;
// 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) {
int offset = getAttributeOffset(index);
int valueType = mAttributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
if (valueType == TypedValue.TYPE_FLOAT) {
int valueData = mAttributes[offset + ATTRIBUTE_IX_VALUE_DATA];
return Float.intBitsToFloat(valueData);
if (valueType != TypedValue.TYPE_FLOAT) {
return defaultValue;
}
return defaultValue;
return Float.intBitsToFloat(mAttributes[offset + ATTRIBUTE_IX_VALUE_DATA]);
}
@Override
public int getAttributeIntValue(int index, int defaultValue) {
int offset = getAttributeOffset(index);
int valueType = mAttributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
if (valueType >= TypedValue.TYPE_FIRST_INT && valueType <= TypedValue.TYPE_LAST_INT) {
return mAttributes[offset + ATTRIBUTE_IX_VALUE_DATA];
if (valueType < TypedValue.TYPE_FIRST_INT || valueType > TypedValue.TYPE_LAST_INT) {
return defaultValue;
}
return defaultValue;
return mAttributes[offset + ATTRIBUTE_IX_VALUE_DATA];
}
@Override
@ -479,10 +514,10 @@ public class AXmlResourceParser implements XmlResourceParser {
public int getAttributeResourceValue(int index, int defaultValue) {
int offset = getAttributeOffset(index);
int valueType = mAttributes[offset + ATTRIBUTE_IX_VALUE_TYPE];
if (valueType == TypedValue.TYPE_REFERENCE) {
return mAttributes[offset + ATTRIBUTE_IX_VALUE_DATA];
if (valueType != TypedValue.TYPE_REFERENCE) {
return defaultValue;
}
return defaultValue;
return mAttributes[offset + ATTRIBUTE_IX_VALUE_DATA];
}
@Override
@ -687,7 +722,7 @@ public class AXmlResourceParser implements XmlResourceParser {
mStringBlock = StringBlock.readWithChunk(mIn);
mNamespaces.increaseDepth();
isOperational = true;
mIsOperational = true;
}
if (mEvent == END_DOCUMENT) {
@ -698,8 +733,8 @@ public class AXmlResourceParser implements XmlResourceParser {
resetEventInfo();
while (true) {
if (m_decreaseDepth) {
m_decreaseDepth = false;
if (mDecreaseDepth) {
mDecreaseDepth = false;
mNamespaces.decreaseDepth();
}
@ -710,7 +745,7 @@ public class AXmlResourceParser implements XmlResourceParser {
}
// #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()));
mEvent = END_DOCUMENT;
break;
@ -809,7 +844,7 @@ public class AXmlResourceParser implements XmlResourceParser {
mNamespaceIndex = mIn.readInt();
mNameIndex = mIn.readInt();
mEvent = END_TAG;
m_decreaseDepth = true;
mDecreaseDepth = true;
break;
}
@ -828,40 +863,4 @@ public class AXmlResourceParser implements XmlResourceParser {
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";
}

View File

@ -25,11 +25,6 @@ import java.util.regex.Pattern;
* AXmlResourceParser specifically for parsing encoded AndroidManifest.xml.
*/
public class AndroidManifestResourceParser extends AXmlResourceParser {
public AndroidManifestResourceParser(ResTable resTable) {
super(resTable);
}
/**
* 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,
@ -39,6 +34,10 @@ public class AndroidManifestResourceParser extends AXmlResourceParser {
*/
private static final Pattern PATTERN_NUMERIC_STRING = Pattern.compile("\\s?\\d+");
public AndroidManifestResourceParser(ResTable resTable) {
super(resTable);
}
@Override
public String getAttributeValue(int index) {
String value = super.getAttributeValue(index);

View File

@ -21,6 +21,7 @@ import brut.androlib.exceptions.CantFind9PatchChunkException;
import brut.androlib.res.data.ninepatch.NinePatchData;
import brut.androlib.res.data.ninepatch.OpticalInset;
import brut.util.ExtDataInput;
import brut.util.ExtDataInputStream;
import org.apache.commons.io.IOUtils;
import javax.imageio.ImageIO;
@ -30,6 +31,11 @@ import java.awt.image.WritableRaster;
import java.io.*;
public class Res9patchStreamDecoder implements ResStreamDecoder {
private static final int NP_CHUNK_TYPE = 0x6e705463; // npTc
private static final int OI_CHUNK_TYPE = 0x6e704c62; // npLb
private static final int NP_COLOR = 0xff000000;
private static final int OI_COLOR = 0xffff0000;
@Override
public void decode(InputStream in, OutputStream out) throws AndrolibException {
try {
@ -122,32 +128,32 @@ public class Res9patchStreamDecoder implements ResStreamDecoder {
private NinePatchData getNinePatch(byte[] data) throws AndrolibException,
IOException {
ExtDataInput di = new ExtDataInput(new ByteArrayInputStream(data));
find9patchChunk(di, NP_CHUNK_TYPE);
return NinePatchData.decode(di);
ExtDataInput in = ExtDataInputStream.bigEndian(new ByteArrayInputStream(data));
find9patchChunk(in, NP_CHUNK_TYPE);
return NinePatchData.decode(in);
}
private OpticalInset getOpticalInset(byte[] data) throws AndrolibException,
IOException {
ExtDataInput di = new ExtDataInput(new ByteArrayInputStream(data));
find9patchChunk(di, OI_CHUNK_TYPE);
return OpticalInset.decode(di);
ExtDataInput in = ExtDataInputStream.bigEndian(new ByteArrayInputStream(data));
find9patchChunk(in, OI_CHUNK_TYPE);
return OpticalInset.decode(in);
}
private void find9patchChunk(DataInput di, int magic) throws AndrolibException,
private void find9patchChunk(DataInput in, int magic) throws AndrolibException,
IOException {
di.skipBytes(8);
in.skipBytes(8);
while (true) {
int size;
try {
size = di.readInt();
size = in.readInt();
} 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;
}
di.skipBytes(size + 4);
in.skipBytes(size + 4);
}
}
@ -162,9 +168,4 @@ public class Res9patchStreamDecoder implements ResStreamDecoder {
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;
}

View File

@ -24,7 +24,7 @@ import brut.androlib.res.data.value.ResBoolValue;
import brut.androlib.res.data.value.ResFileValue;
import brut.directory.Directory;
import brut.directory.DirectoryException;
import brut.directory.DirUtil;
import brut.directory.DirUtils;
import brut.util.BrutIO;
import java.io.*;
@ -33,6 +33,17 @@ import java.util.logging.Level;
import java.util.logging.Logger;
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;
public ResFileDecoder(ResStreamDecoderContainer decoders) {
@ -109,7 +120,7 @@ public class ResFileDecoder {
return;
} catch (CantFind9PatchChunkException ex) {
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);
outDir.removeFile(outFileName);
outFileName = outResName + ext;
@ -156,24 +167,12 @@ public class ResFileDecoder {
}
}
public void copyRaw(Directory inDir, Directory outDir, String inFilename,
String outFilename) throws AndrolibException {
public void copyRaw(Directory inDir, Directory outDir, String inFileName,
String outFileName) throws AndrolibException {
try {
DirUtil.copyToDir(inDir, outDir, inFilename, outFilename);
DirUtils.copyToDir(inDir, outDir, inFileName, outFileName);
} catch (DirectoryException 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
};
}

View File

@ -22,6 +22,7 @@ import org.apache.commons.io.IOUtils;
import java.io.*;
public class ResRawStreamDecoder implements ResStreamDecoder {
@Override
public void decode(InputStream in, OutputStream out) throws AndrolibException {
try {

View File

@ -21,7 +21,11 @@ import java.io.*;
import java.util.*;
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)
throws AndrolibException {

View File

@ -18,7 +18,7 @@ package brut.androlib.res.decoder;
import brut.androlib.res.data.arsc.ARSCHeader;
import brut.androlib.res.xml.ResXmlEncoders;
import brut.util.ExtCountingDataInput;
import brut.util.ExtDataInput;
import com.google.common.annotations.VisibleForTesting;
import java.io.IOException;
@ -29,8 +29,23 @@ import java.util.List;
import java.util.logging.Logger;
public class StringBlock {
public static StringBlock readWithChunk(ExtCountingDataInput reader) throws IOException {
int startPosition = reader.position();
private static final Logger LOGGER = Logger.getLogger(StringBlock.class.getName());
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);
int headerSize = reader.readShort();
int chunkSize = reader.readInt();
@ -38,9 +53,8 @@ public class StringBlock {
return readWithoutChunk(reader, startPosition, headerSize, chunkSize);
}
public static StringBlock readWithoutChunk(ExtCountingDataInput reader, int startPosition, int headerSize,
int chunkSize) throws IOException
{
public static StringBlock readWithoutChunk(ExtDataInput reader, long startPosition,
int headerSize, int chunkSize) throws IOException {
// ResStringPool_header
int stringCount = reader.readInt();
int styleCount = reader.readInt();
@ -90,6 +104,15 @@ public class StringBlock {
return block;
}
private StringBlock() {
}
@VisibleForTesting
StringBlock(byte[] strings, boolean isUTF8) {
mStrings = strings;
mIsUtf8 = isUTF8;
}
/**
* Returns raw string (without any styling information) at specified index.
* @param index int
@ -111,6 +134,7 @@ public class StringBlock {
offset += val[0];
}
length = val[1];
return decodeString(offset, length);
}
@ -154,14 +178,14 @@ public class StringBlock {
if (string == null) {
return -1;
}
for (int i = 0; i != mStringOffsets.length; ++i) {
for (int i = 0; i != mStringOffsets.length; i++) {
int offset = mStringOffsets[i];
int length = getShort(mStrings, offset);
if (length != string.length()) {
continue;
}
int j = 0;
for (; j != length; ++j) {
for (; j != length; j++) {
offset += 2;
if (string.charAt(j) != getShort(mStrings, offset)) {
break;
@ -174,15 +198,6 @@ public class StringBlock {
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:
* * 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[] style;
for (int i = offset; i < mStyles.length; ++i) {
for (int i = offset; i < mStyles.length; i++) {
if (mStyles[i] == -1) {
break;
}
@ -262,39 +277,25 @@ public class StringBlock {
val = array[offset];
offset += 1;
if ((val & 0x80) != 0) {
int low = (array[offset] & 0xFF);
int low = array[offset] & 0xFF;
length = ((val & 0x7F) << 8) + low;
offset += 1;
} else {
length = val;
}
return new int[] { offset, length};
return new int[] { offset, length };
}
private static int[] getUtf16(byte[] array, int offset) {
int val = ((array[offset + 1] & 0xFF) << 8 | array[offset] & 0xFF);
int val = (array[offset + 1] & 0xFF) << 8 | array[offset] & 0xFF;
if ((val & 0x8000) != 0) {
int high = (array[offset + 3] & 0xFF) << 8;
int low = (array[offset + 2] & 0xFF);
int len_value = ((val & 0x7FFF) << 16) + (high + low);
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;
}

View File

@ -27,6 +27,8 @@ import java.util.Map;
import java.util.logging.Logger;
public class StyledString {
private static final Logger LOGGER = Logger.getLogger(StyledString.class.getName());
private final String mText;
private final List<Span> mSpans;
@ -52,63 +54,63 @@ public class StyledString {
private static final MapSplitter ATTRIBUTES_SPLITTER =
Splitter.on(';').omitEmptyStrings().withKeyValueSeparator(Splitter.on('=').limit(2));
private final String tag;
private final int firstChar;
private final int lastChar;
private final String mTag;
private final int mFirstChar;
private final int mLastChar;
public Span(String tag, int firstChar, int lastChar) {
this.tag = tag;
this.firstChar = firstChar;
this.lastChar = lastChar;
mTag = tag;
mFirstChar = firstChar;
mLastChar = lastChar;
}
public String getTag() {
return tag;
return mTag;
}
public int getFirstChar() {
return firstChar;
return mFirstChar;
}
public int getLastChar() {
return lastChar;
return mLastChar;
}
public String getName() {
int separatorIdx = tag.indexOf(';');
return separatorIdx == -1 ? tag : tag.substring(0, separatorIdx);
int separatorIdx = mTag.indexOf(';');
return separatorIdx == -1 ? mTag : mTag.substring(0, separatorIdx);
}
public Map<String, String> getAttributes() {
int separatorIdx = tag.indexOf(';');
return separatorIdx == -1 ? null : ATTRIBUTES_SPLITTER.split(
tag.substring(separatorIdx + 1, tag.endsWith(";") ? tag.length() - 1 : tag.length())
);
int separatorIdx = mTag.indexOf(';');
return separatorIdx != -1 ? ATTRIBUTES_SPLITTER.split(
mTag.substring(separatorIdx + 1, mTag.endsWith(";") ? mTag.length() - 1 : mTag.length())
) : null;
}
@Override
public int compareTo(Span o) {
int res = Integer.compare(firstChar, o.firstChar);
int res = Integer.compare(mFirstChar, o.mFirstChar);
if (res != 0) {
return res;
}
res = Integer.compare(lastChar, o.lastChar);
res = Integer.compare(mLastChar, o.mLastChar);
if (res != 0) {
return -res;
}
return -tag.compareTo(o.tag);
return -mTag.compareTo(o.mTag);
}
}
private static class Decoder {
private String text;
private StringBuilder xmlValue;
private int lastOffset;
private String mText;
private StringBuilder mXmlValue;
private int mLastOffset;
String decode(StyledString styledString) {
text = styledString.getText();
xmlValue = new StringBuilder(text.length() * 2);
lastOffset = 0;
public String decode(StyledString styledString) {
mText = styledString.getText();
mXmlValue = new StringBuilder(mText.length() * 2);
mLastOffset = 0;
// recurse top-level tags
PeekingIterator<Span> it = Iterators.peekingIterator(styledString.getSpans().iterator());
@ -116,11 +118,11 @@ public class StyledString {
decodeIterate(it);
}
// write the remaining encoded raw text
if (lastOffset < text.length()) {
xmlValue.append(ResXmlEncoders.escapeXmlChars(text.substring(lastOffset)));
// write the remaining encoded raw mText
if (mLastOffset < mText.length()) {
mXmlValue.append(ResXmlEncoders.escapeXmlChars(mText.substring(mLastOffset)));
}
return xmlValue.toString();
return mXmlValue.toString();
}
private void decodeIterate(PeekingIterator<Span> it) {
@ -130,45 +132,43 @@ public class StyledString {
int spanStart = span.getFirstChar();
int spanEnd = span.getLastChar() + 1;
// write encoded raw text preceding the opening tag
if (spanStart > lastOffset) {
xmlValue.append(ResXmlEncoders.escapeXmlChars(text.substring(lastOffset, spanStart)));
// write encoded raw mText preceding the opening tag
if (spanStart > mLastOffset) {
mXmlValue.append(ResXmlEncoders.escapeXmlChars(mText.substring(mLastOffset, spanStart)));
}
lastOffset = spanStart;
mLastOffset = spanStart;
// write opening tag
xmlValue.append('<').append(name);
mXmlValue.append('<').append(name);
if (attributes != null) {
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('"');
}
}
// if an opening tag is followed by a matching closing tag, write as an empty-element tag
if (spanStart == spanEnd) {
xmlValue.append("/>");
mXmlValue.append("/>");
return;
}
xmlValue.append('>');
mXmlValue.append('>');
// recurse nested tags
while (it.hasNext() && it.peek().getFirstChar() < spanEnd) {
decodeIterate(it);
}
// write encoded raw text preceding the closing tag
if (spanEnd > lastOffset && text.length() >= spanEnd) {
xmlValue.append(ResXmlEncoders.escapeXmlChars(text.substring(lastOffset, spanEnd)));
} else if (text.length() >= lastOffset && text.length() < spanEnd) {
LOGGER.warning("Span (" + name + ") exceeds text length " + text.length());
xmlValue.append(ResXmlEncoders.escapeXmlChars(text.substring(lastOffset)));
// write encoded raw mText preceding the closing tag
if (spanEnd > mLastOffset && mText.length() >= spanEnd) {
mXmlValue.append(ResXmlEncoders.escapeXmlChars(mText.substring(mLastOffset, spanEnd)));
} else if (mText.length() >= mLastOffset && mText.length() < spanEnd) {
LOGGER.warning("Span (" + name + ") exceeds mText length " + mText.length());
mXmlValue.append(ResXmlEncoders.escapeXmlChars(mText.substring(mLastOffset)));
}
lastOffset = spanEnd;
mLastOffset = spanEnd;
// write closing tag
xmlValue.append("</").append(name).append('>');
mXmlValue.append("</").append(name).append('>');
}
}
private static final Logger LOGGER = Logger.getLogger(StyledString.class.getName());
}

View File

@ -23,6 +23,6 @@ import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
public interface ResValuesXmlSerializable {
void serializeToResValuesXml(XmlSerializer serializer,
ResResource res) throws IOException, AndrolibException;
void serializeToResValuesXml(XmlSerializer serializer, ResResource res)
throws IOException, AndrolibException;
}

View File

@ -25,6 +25,10 @@ import java.util.List;
public final class ResXmlEncoders {
private ResXmlEncoders() {
// Private constructor for utility class
}
public static String escapeXmlChars(String str) {
return StringUtils.replace(StringUtils.replace(str, "&", "&amp;"), "<", "&lt;");
}

View File

@ -41,7 +41,15 @@ import java.nio.file.Files;
import java.util.*;
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
@ -50,9 +58,6 @@ public final class ResXmlPatcher {
* @throws AndrolibException Error reading Manifest file
*/
public static void removeApplicationDebugTag(File file) throws AndrolibException {
if (!file.exists()) {
return;
}
try {
Document doc = loadDocument(file);
Node application = doc.getElementsByTagName("application").item(0);
@ -67,7 +72,6 @@ public final class ResXmlPatcher {
}
saveDocument(file, doc);
} catch (SAXException | ParserConfigurationException | IOException | TransformerException ignored) {
}
}
@ -78,9 +82,6 @@ public final class ResXmlPatcher {
* @param file AndroidManifest file
*/
public static void setApplicationDebugTagTrue(File file) {
if (!file.exists()) {
return;
}
try {
Document doc = loadDocument(file);
Node application = doc.getElementsByTagName("application").item(0);
@ -98,7 +99,6 @@ public final class ResXmlPatcher {
debugAttr.setNodeValue("true");
saveDocument(file, doc);
} catch (SAXException | ParserConfigurationException | IOException | TransformerException ignored) {
}
}
@ -109,9 +109,6 @@ public final class ResXmlPatcher {
* @param file AndroidManifest file
*/
public static void setNetworkSecurityConfig(File file) {
if (!file.exists()) {
return;
}
try {
Document doc = loadDocument(file);
Node application = doc.getElementsByTagName("application").item(0);
@ -130,7 +127,6 @@ public final class ResXmlPatcher {
netSecConfAttr.setNodeValue("@xml/network_security_config");
saveDocument(file, doc);
} catch (SAXException | ParserConfigurationException | IOException | TransformerException ignored) {
}
}
@ -155,13 +151,13 @@ public final class ResXmlPatcher {
} else {
document = documentBuilder.newDocument();
}
Element root = (Element) document.getElementsByTagName("network-security-config").item(0);
if (root == null) {
root = document.createElement("network-security-config");
document.appendChild(root);
}
Element baseConfig = (Element) document.getElementsByTagName("base-config").item(0);
if (baseConfig == null) {
baseConfig = document.createElement("base-config");
@ -192,13 +188,13 @@ public final class ResXmlPatcher {
certSystem.setAttribute("src", "system");
trustAnchors.appendChild(certSystem);
}
if (!hasUserCert) {
Element certUser = document.createElement("certificates");
certUser.setAttribute("src", "user");
trustAnchors.appendChild(certUser);
}
saveDocument(file, document);
}
@ -214,9 +210,6 @@ public final class ResXmlPatcher {
* @param file File for AndroidManifest.xml
*/
public static void fixingPublicAttrsInProviderAttributes(File file) {
if (!file.exists()) {
return;
}
try {
Document doc = loadDocument(file);
XPath xPath = XPathFactory.newInstance().newXPath();
@ -257,7 +250,6 @@ public final class ResXmlPatcher {
if (saved) {
saveDocument(file, doc);
}
} catch (SAXException | ParserConfigurationException | IOException
| XPathExpressionException | TransformerException ignored) {
}
@ -285,16 +277,16 @@ public final class ResXmlPatcher {
/**
* 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)
* @return String|null
*/
public static String pullValueFromStrings(File directory, String key) {
public static String pullValueFromStrings(File apkDir, String key) {
if (key == null || ! key.contains("@")) {
return null;
}
File file = new File(directory, "/res/values/strings.xml");
File file = new File(apkDir, "/res/values/strings.xml");
key = key.replace("@string/", "");
if (!file.exists()) {
@ -307,7 +299,6 @@ public final class ResXmlPatcher {
Object result = expression.evaluate(doc, XPathConstants.STRING);
return result != null ? (String) result : null;
} catch (SAXException | ParserConfigurationException | IOException | XPathExpressionException ignored) {
return null;
}
@ -316,16 +307,16 @@ public final class ResXmlPatcher {
/**
* 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)
* @return String|null
*/
public static String pullValueFromIntegers(File directory, String key) {
public static String pullValueFromIntegers(File apkDir, String key) {
if (key == null || ! key.contains("@")) {
return null;
}
File file = new File(directory, "/res/values/integers.xml");
File file = new File(apkDir, "/res/values/integers.xml");
key = key.replace("@integer/", "");
if (!file.exists()) {
@ -350,9 +341,6 @@ public final class ResXmlPatcher {
* @param file File representing AndroidManifest.xml
*/
public static void removeManifestVersions(File file) {
if (!file.exists()) {
return;
}
try {
Document doc = loadDocument(file);
Node manifest = doc.getFirstChild();
@ -390,7 +378,6 @@ public final class ResXmlPatcher {
Node nodeAttr = attr.getNamedItem("package");
nodeAttr.setNodeValue(packageOriginal);
saveDocument(file, doc);
} catch (SAXException | ParserConfigurationException | IOException | TransformerException ignored) {
}
}
@ -401,9 +388,6 @@ public final class ResXmlPatcher {
* @param file File for AndroidManifest.xml
*/
public static List<String> pullManifestFeatureFlags(File file) {
if (!file.exists()) {
return null;
}
try {
Document doc = loadDocument(file);
XPath xPath = XPathFactory.newInstance().newXPath();
@ -425,7 +409,6 @@ public final class ResXmlPatcher {
}
return featureFlags;
} catch (SAXException | ParserConfigurationException | IOException | XPathExpressionException ignored) {
return null;
}
@ -441,23 +424,22 @@ public final class ResXmlPatcher {
*/
public static Document loadDocument(File file)
throws IOException, SAXException, ParserConfigurationException {
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
docFactory.setFeature(FEATURE_DISABLE_DOCTYPE_DECL, true);
docFactory.setFeature(FEATURE_LOAD_DTD, false);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature(FEATURE_DISABLE_DOCTYPE_DECL, true);
factory.setFeature(FEATURE_LOAD_DTD, false);
try {
docFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, " ");
docFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, " ");
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, " ");
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, " ");
} catch (IllegalArgumentException ex) {
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
// to close it. Somehow parse(File) does not seem to close the file in all cases.
try (FileInputStream inputStream = new FileInputStream(file)) {
return docBuilder.parse(inputStream);
try (InputStream in = Files.newInputStream(file.toPath())) {
return builder.parse(in);
}
}
@ -472,7 +454,6 @@ public final class ResXmlPatcher {
*/
private static void saveDocument(File file, Document doc)
throws IOException, SAXException, ParserConfigurationException, TransformerException {
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer();
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[] newLine = System.getProperty("line.separator").getBytes(StandardCharsets.US_ASCII);
try (OutputStream output = Files.newOutputStream(file.toPath())) {
output.write(xmlDecl);
output.write(newLine);
transformer.transform(new DOMSource(doc), new StreamResult(output));
output.write(newLine);
try (OutputStream out = Files.newOutputStream(file.toPath())) {
out.write(xmlDecl);
out.write(newLine);
transformer.transform(new DOMSource(doc), new StreamResult(out));
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());
}

View File

@ -29,30 +29,27 @@ import java.nio.file.Files;
import java.util.logging.Logger;
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 {
new SmaliBuilder(smaliDir, dexFile, apiLevel).build();
}
private final ExtFile mSmaliDir;
private final int mApiLevel;
private SmaliBuilder(File smaliDir, File dexFile, int apiLevel) {
public SmaliBuilder(File smaliDir, int apiLevel) {
mSmaliDir = new ExtFile(smaliDir);
mDexFile = dexFile;
mApiLevel = apiLevel;
}
private void build() throws AndrolibException {
public void build(File dexFile) throws AndrolibException {
try {
DexBuilder dexBuilder;
if (mApiLevel > 0) {
dexBuilder = new DexBuilder(Opcodes.forApi(mApiLevel));
} else {
dexBuilder = new DexBuilder(Opcodes.getDefault());
}
DexBuilder dexBuilder = mApiLevel > 0
? new DexBuilder(Opcodes.forApi(mApiLevel))
: new DexBuilder(Opcodes.getDefault());
for (String fileName : mSmaliDir.getDirectory().getFiles(true)) {
buildFile(fileName, dexBuilder);
}
dexBuilder.writeTo(new FileDataStore(new File(mDexFile.getAbsolutePath())));
dexBuilder.writeTo(new FileDataStore(dexFile));
} catch (DirectoryException | IOException | RuntimeException ex) {
throw new AndrolibException("Could not smali folder: " + mSmaliDir.getName(), ex);
}
@ -82,10 +79,4 @@ public class SmaliBuilder {
throw ex;
}
}
private final ExtFile mSmaliDir;
private final File mDexFile;
private final int mApiLevel;
private final static Logger LOGGER = Logger.getLogger(SmaliBuilder.class.getName());
}

View File

@ -30,21 +30,19 @@ import com.android.tools.smali.dexlib2.iface.MultiDexContainer;
import java.io.*;
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)
throws AndrolibException {
return new SmaliDecoder(apkFile, outDir, dexName, bakDeb, apiLevel).decode();
}
private SmaliDecoder(File apkFile, File outDir, String dexName, boolean bakDeb, int apiLevel) {
public SmaliDecoder(File apkFile, String dexName, boolean bakDeb, int apiLevel) {
mApkFile = apkFile;
mOutDir = outDir;
mDexName = dexName;
mBakDeb = bakDeb;
mApiLevel = apiLevel;
}
private DexFile decode() throws AndrolibException {
public DexFile decode(File outDir) throws AndrolibException {
try {
final BaksmaliOptions options = new BaksmaliOptions();
@ -93,20 +91,14 @@ public class SmaliDecoder {
if (dexFile instanceof DexBackedOdexFile) {
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;
} catch (IOException 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;
}

View File

@ -31,6 +31,7 @@ import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
import java.nio.file.Files;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
@ -39,6 +40,16 @@ import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual;
import static org.junit.Assert.*;
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 {
boolean exists = true;
@ -125,24 +136,24 @@ public class BaseTest {
assertTrue(path + ": " + diff.getAllDifferences().toString(), diff.similar());
}
protected static Document loadDocument(File file) throws IOException, SAXException, ParserConfigurationException {
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
docFactory.setFeature(FEATURE_DISABLE_DOCTYPE_DECL, true);
docFactory.setFeature(FEATURE_LOAD_DTD, false);
protected static Document loadDocument(File file)
throws IOException, SAXException, ParserConfigurationException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature(FEATURE_DISABLE_DOCTYPE_DECL, true);
factory.setFeature(FEATURE_LOAD_DTD, false);
try {
docFactory.setAttribute(ACCESS_EXTERNAL_DTD, " ");
docFactory.setAttribute(ACCESS_EXTERNAL_SCHEMA, " ");
factory.setAttribute(ACCESS_EXTERNAL_DTD, " ");
factory.setAttribute(ACCESS_EXTERNAL_SCHEMA, " ");
} catch (IllegalArgumentException ex) {
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
// to close it. Somehow parse(File) does not seem to close the file in all cases.
try (FileInputStream inputStream = new FileInputStream(file)) {
return docBuilder.parse(inputStream);
try (InputStream in = Files.newInputStream(file.toPath())) {
return builder.parse(in);
}
}
@ -164,21 +175,11 @@ public class BaseTest {
NodeList children = element.getChildNodes();
for (int i = 0; i < children.getLength(); 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 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";
}

View File

@ -18,9 +18,9 @@ package brut.androlib;
import brut.androlib.exceptions.AndrolibException;
import brut.androlib.res.Framework;
import brut.androlib.res.xml.ResXmlPatcher;
import brut.androlib.res.xml.ResXmlUtils;
import brut.common.BrutException;
import brut.directory.DirUtil;
import brut.directory.DirUtils;
import brut.directory.Directory;
import brut.directory.FileDirectory;
import brut.util.OS;
@ -42,7 +42,11 @@ import java.nio.file.Files;
import java.util.HashMap;
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 {
try {
@ -68,28 +72,28 @@ public abstract class TestUtils {
public static Document getDocumentFromFile(File file) throws BrutException {
try {
return ResXmlPatcher.loadDocument(file);
return ResXmlUtils.loadDocument(file);
} catch (IOException | SAXException | ParserConfigurationException 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()) {
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 {
if (class_ == null) {
class_ = Class.class;
public static void copyResourceDir(Class<?> clz, String dirPath, Directory out) throws BrutException {
if (clz == null) {
clz = Class.class;
}
URL dirURL = class_.getClassLoader().getResource(dirPath);
URL dirURL = clz.getClassLoader().getResource(dirPath);
if (dirURL != null && dirURL.getProtocol().equals("file")) {
try {
DirUtil.copyToDir(new FileDirectory(dirURL.getFile()), out);
DirUtils.copyToDir(new FileDirectory(dirURL.getFile()), out);
} catch (UnsupportedEncodingException ex) {
throw new BrutException(ex);
}
@ -97,15 +101,15 @@ public abstract class TestUtils {
}
if (dirURL == null) {
String className = class_.getName().replace(".", "/") + ".class";
dirURL = class_.getClassLoader().getResource(className);
String className = clz.getName().replace(".", "/") + ".class";
dirURL = clz.getClassLoader().getResource(className);
}
if (dirURL.getProtocol().equals("jar")) {
String jarPath;
try {
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) {
throw new BrutException(ex);
}
@ -122,11 +126,12 @@ public abstract class TestUtils {
public static byte[] readHeaderOfFile(File file, int size) throws IOException {
byte[] buffer = new byte[size];
InputStream inputStream = Files.newInputStream(file.toPath());
if (inputStream.read(buffer) != buffer.length) {
throw new IOException("File size too small for buffer length: " + size);
try (InputStream in = Files.newInputStream(file.toPath())) {
if (in.read(buffer) != buffer.length) {
throw new IOException("File size too small for buffer length: " + size);
}
}
inputStream.close();
return buffer;
}

View File

@ -30,6 +30,7 @@ import java.io.File;
import static org.junit.Assert.assertTrue;
public class AndroidOreoNotSparseTest extends BaseTest {
@BeforeClass
public static void beforeClass() throws Exception {
TestUtils.cleanFrameworkFile();

View File

@ -30,6 +30,7 @@ import java.io.File;
import static org.junit.Assert.assertTrue;
public class AndroidOreoSparseTest extends BaseTest {
@BeforeClass
public static void beforeClass() throws Exception {
TestUtils.cleanFrameworkFile();

View File

@ -33,6 +33,12 @@ import java.util.logging.Logger;
import static org.junit.Assert.assertTrue;
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
public static void beforeClass() throws Exception {
TestUtils.cleanFrameworkFile();
@ -64,10 +70,4 @@ public class EmptyResourcesArscTest {
assertTrue(sTestNewDir.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());
}

View File

@ -37,7 +37,6 @@ import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
@ -88,20 +87,19 @@ public class NetworkConfigTest extends BaseTest {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
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
XPathFactory xPathFactory = XPathFactory.newInstance();
XPath xpath = xPathFactory.newXPath();
XPath xPath = XPathFactory.newInstance().newXPath();
// Check if 'system' certificate exists
XPathExpression systemCertExpr = xpath.compile("//certificates[@src='system']");
NodeList systemCertNodes = (NodeList) systemCertExpr.evaluate(document, XPathConstants.NODESET);
XPathExpression systemCertExpr = xPath.compile("//certificates[@src='system']");
NodeList systemCertNodes = (NodeList) systemCertExpr.evaluate(doc, XPathConstants.NODESET);
assertTrue(systemCertNodes.getLength() > 0);
// Check if 'user' certificate exists
XPathExpression userCertExpr = xpath.compile("//certificates[@src='user']");
NodeList userCertNodes = (NodeList) userCertExpr.evaluate(document, XPathConstants.NODESET);
XPathExpression userCertExpr = xPath.compile("//certificates[@src='user']");
NodeList userCertNodes = (NodeList) userCertExpr.evaluate(doc, XPathConstants.NODESET);
assertTrue(userCertNodes.getLength() > 0);
}

View File

@ -31,6 +31,7 @@ import org.junit.Test;
import static org.junit.Assert.*;
public class NonStandardPkgIdTest extends BaseTest {
private static ResTable mResTable;
@BeforeClass
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(0x80030000).getPackage().getId());
}
private static ResTable mResTable;
}

View File

@ -40,7 +40,7 @@ public class ApkInfoReaderTest {
assertNotNull(apkInfo.usesFramework);
assertNotNull(apkInfo.usesFramework.ids);
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);
assertNotNull(apkInfo.versionInfo);
assertNull(apkInfo.versionInfo.versionCode);
@ -50,7 +50,7 @@ public class ApkInfoReaderTest {
@Test
public void testStandard() throws AndrolibException {
ApkInfo apkInfo = ApkInfo.load(
this.getClass().getResourceAsStream("/apk/standard.yml"));
getClass().getResourceAsStream("/apk/standard.yml"));
checkStandard(apkInfo);
assertEquals("2.8.1", apkInfo.version);
}
@ -58,7 +58,7 @@ public class ApkInfoReaderTest {
@Test
public void testUnknownFields() throws AndrolibException {
ApkInfo apkInfo = ApkInfo.load(
this.getClass().getResourceAsStream("/apk/unknown_fields.yml"));
getClass().getResourceAsStream("/apk/unknown_fields.yml"));
checkStandard(apkInfo);
assertEquals("2.8.1", apkInfo.version);
}
@ -66,7 +66,7 @@ public class ApkInfoReaderTest {
@Test
public void testSkipIncorrectIndent() throws AndrolibException {
ApkInfo apkInfo = ApkInfo.load(
this.getClass().getResourceAsStream("/apk/skip_incorrect_indent.yml"));
getClass().getResourceAsStream("/apk/skip_incorrect_indent.yml"));
checkStandard(apkInfo);
assertNotEquals("2.0.0", apkInfo.version);
}
@ -74,7 +74,7 @@ public class ApkInfoReaderTest {
@Test
public void testFirstIncorrectIndent() throws AndrolibException {
ApkInfo apkInfo = ApkInfo.load(
this.getClass().getResourceAsStream("/apk/first_incorrect_indent.yml"));
getClass().getResourceAsStream("/apk/first_incorrect_indent.yml"));
checkStandard(apkInfo);
assertNotEquals("2.0.0", apkInfo.version);
}
@ -82,13 +82,13 @@ public class ApkInfoReaderTest {
@Test
public void testUnknownFiles() throws AndrolibException {
ApkInfo apkInfo = ApkInfo.load(
this.getClass().getResourceAsStream("/apk/unknown_files.yml"));
getClass().getResourceAsStream("/apk/unknown_files.yml"));
assertEquals("2.0.0", apkInfo.version);
assertEquals("testapp.apk", apkInfo.apkFileName);
assertFalse(apkInfo.isFrameworkApk);
assertNotNull(apkInfo.usesFramework);
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);
assertEquals("127", apkInfo.packageInfo.forcedPackageId);
assertNotNull(apkInfo.versionInfo);
@ -106,13 +106,13 @@ public class ApkInfoReaderTest {
@Test
public void testUlist_with_indent() throws AndrolibException {
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("basic.apk", apkInfo.apkFileName);
assertFalse(apkInfo.isFrameworkApk);
assertNotNull(apkInfo.usesFramework);
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);
assertNotNull(apkInfo.packageInfo);
assertEquals("127", apkInfo.packageInfo.forcedPackageId);

View File

@ -23,25 +23,24 @@ import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.*;
import java.nio.file.Files;
import static org.junit.Assert.*;
public class ApkInfoSerializationTest {
@Rule
public TemporaryFolder folder = new TemporaryFolder();
@Test
public void checkApkInfoSerialization() throws IOException, AndrolibException {
ApkInfo control = ApkInfo.load(
this.getClass().getResourceAsStream("/apk/unknown_files.yml"));
getClass().getResourceAsStream("/apk/unknown_files.yml"));
check(control);
File savedApkInfo = folder.newFile("saved.yml");
control.save(savedApkInfo);
try (FileInputStream fis = new FileInputStream(savedApkInfo)) {
ApkInfo saved = ApkInfo.load(fis);
check(saved);
try (InputStream in = Files.newInputStream(savedApkInfo.toPath())) {
check(ApkInfo.load(in));
}
}
@ -51,7 +50,7 @@ public class ApkInfoSerializationTest {
assertFalse(apkInfo.isFrameworkApk);
assertNotNull(apkInfo.usesFramework);
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);
assertEquals("127", apkInfo.packageInfo.forcedPackageId);
assertNotNull(apkInfo.versionInfo);

View File

@ -26,7 +26,7 @@ public class ConsistentPropertyTest {
@Test
public void testAssertingAllKnownApkInfoProperties() throws AndrolibException {
ApkInfo apkInfo = ApkInfo.load(
this.getClass().getResourceAsStream("/apk/basic.yml"));
getClass().getResourceAsStream("/apk/basic.yml"));
assertEquals("2.8.0", apkInfo.version);
assertEquals("basic.apk", apkInfo.apkFileName);

View File

@ -26,7 +26,7 @@ public class DoNotCompressHieroglyphTest {
@Test
public void testHieroglyph() throws AndrolibException {
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("testapp.apk", apkInfo.apkFileName);
assertEquals(2, apkInfo.doNotCompress.size());

View File

@ -26,7 +26,7 @@ public class MaliciousYamlTest {
@Test
public void testMaliciousYaml() throws AndrolibException {
ApkInfo apkInfo = ApkInfo.load(
this.getClass().getResourceAsStream("/apk/cve20220476.yml"));
getClass().getResourceAsStream("/apk/cve20220476.yml"));
assertEquals("2.6.1-ddc4bb-SNAPSHOT", apkInfo.version);
}
}

View File

@ -34,8 +34,7 @@ import java.util.Arrays;
import static org.junit.Assert.*;
public class ForceManifestDecodeNoResourcesTest extends BaseTest {
private final byte[] xmlHeader = new byte[] {
private static final byte[] XML_HEADER = {
0x3C, // <
0x3F, // ?
0x78, // x
@ -68,7 +67,7 @@ public class ForceManifestDecodeNoResourcesTest extends BaseTest {
// let's probe filetype of manifest, we should detect XML
File manifestFile = new File(output + File.separator + "AndroidManifest.xml");
byte[] magic = TestUtils.readHeaderOfFile(manifestFile, 6);
assertArrayEquals(this.xmlHeader, magic);
assertArrayEquals(XML_HEADER, magic);
// confirm resources.arsc still exists, as its raw
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
File manifestFile = new File(output + File.separator + "AndroidManifest.xml");
byte[] magic = TestUtils.readHeaderOfFile(manifestFile, 6);
assertArrayEquals(this.xmlHeader, magic);
assertArrayEquals(XML_HEADER, magic);
// confirm resources.arsc does not exist
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
File manifestFile = new File(output + File.separator + "AndroidManifest.xml");
byte[] magic = TestUtils.readHeaderOfFile(manifestFile, 6);
assertArrayEquals(this.xmlHeader, magic);
assertArrayEquals(XML_HEADER, magic);
// confirm resources.arsc does not exist
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
File manifestFile = new File(output + File.separator + "AndroidManifest.xml");
byte[] magic = TestUtils.readHeaderOfFile(manifestFile, 6);
assertFalse(Arrays.equals(this.xmlHeader, magic));
assertFalse(Arrays.equals(XML_HEADER, magic));
// confirm resources.arsc exists
File resourcesArsc = new File(output + File.separator + "resources.arsc");

View File

@ -29,10 +29,12 @@ import org.junit.Test;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.file.Files;
import static org.junit.Assert.*;
public class MissingDiv9PatchTest extends BaseTest {
private static final int NP_COLOR = 0xff000000;
@BeforeClass
public static void beforeClass() throws Exception {
@ -48,26 +50,22 @@ public class MissingDiv9PatchTest extends BaseTest {
@Test
public void assertMissingDivAdded() throws Exception {
InputStream inputStream = getFileInputStream();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
File file = new File(sTmpDir, "pip_dismiss_scrim.9.png");
byte[] data;
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
decoder.decode(inputStream, outputStream);
try (InputStream in = Files.newInputStream(file.toPath())) {
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;
// 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++) {
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;
}

View File

@ -25,48 +25,55 @@ import static org.junit.Assert.assertEquals;
public class StringBlockWithSurrogatePairInUtf8Test {
@Test
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);
}
@Test
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);
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);
}
@Test
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);
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);
}
@Test
public void decodeSurrogatePair_when_givesAsThreeOctetsFromInvalidRangeOfUtf8() {
// 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);
assertEquals("Incorrect decoding", "\uD83D\uDD06", actual);
final byte[] bytes0 = { (byte) 0xED, (byte) 0xA0, (byte) 0xBD, (byte) 0xED, (byte) 0xB4, (byte) 0x86 };
final String actual0 = new StringBlock(bytes0, true).decodeString(0, 6);
assertEquals("Incorrect decoding", "\uD83D\uDD06", actual0);
// 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,
' ', 'S', 'u', 'n', ' ',
(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
// DC4B -> 0xED 0xB1 0x8B
// D83C -> 0xED 0xA0 0xBC
// DF1E -> 0xED 0xBC 0x9E
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
@ -74,7 +81,8 @@ public class StringBlockWithSurrogatePairInUtf8Test {
// \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,
// 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);
}
}

View File

@ -26,9 +26,9 @@ public class AaptVersionTest {
@Test
public void testAapt2Iterations() throws BrutException {
assertEquals(2, AaptManager.getAppVersionFromString("Android Asset Packaging Tool (aapt) 2:17"));
assertEquals(2, AaptManager.getAppVersionFromString("Android Asset Packaging Tool (aapt) 2.17"));
assertEquals(1, AaptManager.getAppVersionFromString("Android Asset Packaging Tool, v0.9"));
assertEquals(1, AaptManager.getAppVersionFromString("Android Asset Packaging Tool, v0.2-2679779"));
assertEquals(2, AaptManager.getAaptVersionFromString("Android Asset Packaging Tool (aapt) 2:17"));
assertEquals(2, AaptManager.getAaptVersionFromString("Android Asset Packaging Tool (aapt) 2.17"));
assertEquals(1, AaptManager.getAaptVersionFromString("Android Asset Packaging Tool, v0.9"));
assertEquals(1, AaptManager.getAaptVersionFromString("Android Asset Packaging Tool, v0.2-2679779"));
}
}

View File

@ -17,6 +17,15 @@
package brut.common;
public class BrutException extends Exception {
public BrutException() {
super();
}
public BrutException(String message) {
super(message);
}
public BrutException(Throwable cause) {
super(cause);
}
@ -24,11 +33,4 @@ public class BrutException extends Exception {
public BrutException(String message, Throwable cause) {
super(message, cause);
}
public BrutException(String message) {
super(message);
}
public BrutException() {
}
}

View File

@ -17,6 +17,7 @@
package brut.common;
public class InvalidUnknownFileException extends BrutException {
public InvalidUnknownFileException(String message) {
super(message);
}

View File

@ -17,6 +17,7 @@
package brut.common;
public class RootUnknownFileException extends BrutException {
public RootUnknownFileException(String message) {
super(message);
}

View File

@ -17,6 +17,7 @@
package brut.common;
public class TraversalUnknownFileException extends BrutException {
public TraversalUnknownFileException(String message) {
super(message);
}

View File

@ -180,31 +180,31 @@ public abstract class AbstractDirectory implements Directory {
}
public void copyToDir(Directory out) throws DirectoryException {
DirUtil.copyToDir(out, out);
DirUtils.copyToDir(out, out);
}
public void copyToDir(Directory out, String[] fileNames)
throws DirectoryException {
DirUtil.copyToDir(out, out, fileNames);
DirUtils.copyToDir(out, out, fileNames);
}
public void copyToDir(Directory out, String fileName)
throws DirectoryException {
DirUtil.copyToDir(out, out, fileName);
DirUtils.copyToDir(out, out, fileName);
}
public void copyToDir(File out) throws DirectoryException {
DirUtil.copyToDir(this, out);
DirUtils.copyToDir(this, out);
}
public void copyToDir(File out, String[] fileNames)
throws DirectoryException {
DirUtil.copyToDir(this, out, fileNames);
DirUtils.copyToDir(this, out, fileNames);
}
public void copyToDir(File out, String fileName)
throws DirectoryException {
DirUtil.copyToDir(this, out, fileName);
DirUtils.copyToDir(this, out, fileName);
}
public int getCompressionLevel(String fileName)
@ -269,6 +269,7 @@ public abstract class AbstractDirectory implements Directory {
private static class ParsedPath {
public final String dir;
public final String subpath;
public ParsedPath(String dir, String subpath) {
this.dir = dir;
this.subpath = subpath;

Some files were not shown because too many files have changed in this diff Show More