core: add resources methods to jadx API
This commit is contained in:
@@ -32,6 +32,12 @@ public final class JadxCLIArgs implements IJadxArgs {
|
||||
@Parameter(names = {"-f", "--fallback"}, description = "make simple dump (using goto instead of 'if', 'for', etc)")
|
||||
protected boolean fallbackMode = false;
|
||||
|
||||
@Parameter(names = {"-r", "--no-res"}, description = "do not decode resources")
|
||||
protected boolean skipResources = false;
|
||||
|
||||
@Parameter(names = {"-s", "--no-src"}, description = "do not decompile source code")
|
||||
protected boolean skipSources = false;
|
||||
|
||||
@Parameter(names = {"--show-bad-code"}, description = "show inconsistent code (incorrectly decompiled)")
|
||||
protected boolean showInconsistentCode = false;
|
||||
|
||||
@@ -47,9 +53,6 @@ public final class JadxCLIArgs implements IJadxArgs {
|
||||
@Parameter(names = {"-h", "--help"}, description = "print this help", help = true)
|
||||
protected boolean printHelp = false;
|
||||
|
||||
@Parameter(names = {"-x", "--xml"}, description = "try to decode the AndroidManifest.xml")
|
||||
protected boolean xmlTest = false;
|
||||
|
||||
private final List<File> input = new ArrayList<File>(1);
|
||||
private File outputDir;
|
||||
|
||||
@@ -168,8 +171,13 @@ public final class JadxCLIArgs implements IJadxArgs {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isXMLTest() {
|
||||
return xmlTest;
|
||||
public boolean isSkipResources() {
|
||||
return skipResources;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSkipSources() {
|
||||
return skipSources;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -40,7 +40,12 @@ public class DefaultJadxArgs implements IJadxArgs {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isXMLTest() {
|
||||
public boolean isSkipResources() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSkipSources() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,5 +17,7 @@ public interface IJadxArgs {
|
||||
|
||||
boolean isVerbose();
|
||||
|
||||
boolean isXMLTest();
|
||||
boolean isSkipResources();
|
||||
|
||||
boolean isSkipSources();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package jadx.api;
|
||||
|
||||
import jadx.core.Jadx;
|
||||
import jadx.core.ProcessClass;
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.dex.info.ClassInfo;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
@@ -55,6 +56,9 @@ public final class JadxDecompiler {
|
||||
private RootNode root;
|
||||
private List<IDexTreeVisitor> passes;
|
||||
private List<JavaClass> classes;
|
||||
private List<ResourceFile> resources;
|
||||
|
||||
private BinaryXMLParser xmlParser;
|
||||
|
||||
public JadxDecompiler() {
|
||||
this(new DefaultJadxArgs());
|
||||
@@ -82,6 +86,8 @@ public final class JadxDecompiler {
|
||||
void reset() {
|
||||
ClassInfo.clearCache();
|
||||
classes = null;
|
||||
resources = null;
|
||||
xmlParser = null;
|
||||
root = null;
|
||||
}
|
||||
|
||||
@@ -108,27 +114,21 @@ public final class JadxDecompiler {
|
||||
parse();
|
||||
}
|
||||
|
||||
public void parseAndSaveXML() {
|
||||
if (this.args.isXMLTest()) {
|
||||
InputFile inf = inputFiles.get(0);
|
||||
try {
|
||||
byte[] buffer = InputFile.loadXMLBuffer(inf.getFile());
|
||||
if (buffer != null) {
|
||||
File out = new File(outDir, "AndroidManifest.xml");
|
||||
BinaryXMLParser bxp = new BinaryXMLParser(root);
|
||||
bxp.parse(buffer, out);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.info("Decompiling AndroidManifest.xml failed!", e);
|
||||
}
|
||||
}
|
||||
public void save() {
|
||||
save(!args.isSkipSources(), !args.isSkipResources());
|
||||
}
|
||||
|
||||
public void save() {
|
||||
parseAndSaveXML();
|
||||
public void saveSources() {
|
||||
save(true, false);
|
||||
}
|
||||
|
||||
public void saveResources() {
|
||||
save(false, true);
|
||||
}
|
||||
|
||||
private void save(boolean saveSources, boolean saveResources) {
|
||||
try {
|
||||
ExecutorService ex = getSaveExecutor();
|
||||
ExecutorService ex = getSaveExecutor(saveSources, saveResources);
|
||||
ex.shutdown();
|
||||
ex.awaitTermination(1, TimeUnit.DAYS);
|
||||
} catch (InterruptedException e) {
|
||||
@@ -137,6 +137,10 @@ public final class JadxDecompiler {
|
||||
}
|
||||
|
||||
public ExecutorService getSaveExecutor() {
|
||||
return getSaveExecutor(!args.isSkipSources(), !args.isSkipResources());
|
||||
}
|
||||
|
||||
private ExecutorService getSaveExecutor(boolean saveSources, boolean saveResources) {
|
||||
if (root == null) {
|
||||
throw new JadxRuntimeException("No loaded files");
|
||||
}
|
||||
@@ -145,14 +149,31 @@ public final class JadxDecompiler {
|
||||
|
||||
LOG.info("processing ...");
|
||||
ExecutorService executor = Executors.newFixedThreadPool(threadsCount);
|
||||
for (final JavaClass cls : getClasses()) {
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
cls.decompile();
|
||||
SaveCode.save(outDir, args, cls.getClassNode());
|
||||
}
|
||||
});
|
||||
if (saveSources) {
|
||||
for (final JavaClass cls : getClasses()) {
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
cls.decompile();
|
||||
SaveCode.save(outDir, args, cls.getClassNode());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (saveResources) {
|
||||
for (final ResourceFile resourceFile : getResources()) {
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (ResourceType.isSupportedForUnpack(resourceFile.getType())) {
|
||||
CodeWriter cw = resourceFile.getContent();
|
||||
if (cw != null) {
|
||||
cw.save(new File(outDir, resourceFile.getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return executor;
|
||||
}
|
||||
@@ -172,6 +193,16 @@ public final class JadxDecompiler {
|
||||
return classes;
|
||||
}
|
||||
|
||||
public List<ResourceFile> getResources() {
|
||||
if (resources == null) {
|
||||
if (root == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
resources = new ResourcesLoader(this).load(inputFiles);
|
||||
}
|
||||
return resources;
|
||||
}
|
||||
|
||||
public List<JavaPackage> getPackages() {
|
||||
List<JavaClass> classList = getClasses();
|
||||
if (classList.isEmpty()) {
|
||||
@@ -232,6 +263,13 @@ public final class JadxDecompiler {
|
||||
return root;
|
||||
}
|
||||
|
||||
BinaryXMLParser getXmlParser() {
|
||||
if (xmlParser == null) {
|
||||
xmlParser = new BinaryXMLParser(root);
|
||||
}
|
||||
return xmlParser;
|
||||
}
|
||||
|
||||
JavaClass findJavaClass(ClassNode cls) {
|
||||
if (cls == null) {
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package jadx.api;
|
||||
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class ResourceFile {
|
||||
|
||||
public static final class ZipRef {
|
||||
private final File zipFile;
|
||||
private final String entryName;
|
||||
|
||||
public ZipRef(File zipFile, String entryName) {
|
||||
this.zipFile = zipFile;
|
||||
this.entryName = entryName;
|
||||
}
|
||||
|
||||
public File getZipFile() {
|
||||
return zipFile;
|
||||
}
|
||||
|
||||
public String getEntryName() {
|
||||
return entryName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ZipRef{" + zipFile + ", '" + entryName + "'}";
|
||||
}
|
||||
}
|
||||
|
||||
private final JadxDecompiler decompiler;
|
||||
private final String name;
|
||||
private final ResourceType type;
|
||||
private ZipRef zipRef;
|
||||
|
||||
ResourceFile(JadxDecompiler decompiler, String name, ResourceType type) {
|
||||
this.decompiler = decompiler;
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public ResourceType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public CodeWriter getContent() {
|
||||
return ResourcesLoader.loadContent(decompiler, zipRef, type);
|
||||
}
|
||||
|
||||
void setZipRef(ZipRef zipRef) {
|
||||
this.zipRef = zipRef;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ResourceFile{name='" + name + '\'' + ", type=" + type + "}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package jadx.api;
|
||||
|
||||
public enum ResourceType {
|
||||
CODE(".dex", ".class"),
|
||||
MANIFEST("AndroidManifest.xml"),
|
||||
XML(".xml"), // TODO binary or not?
|
||||
ARSC(".arsc"), // TODO decompile !!!
|
||||
FONT(".ttf"),
|
||||
IMG(".png", ".gif", ".jpg"),
|
||||
LIB(".so"),
|
||||
UNKNOWN;
|
||||
|
||||
private String[] exts;
|
||||
|
||||
ResourceType(String... exts) {
|
||||
this.exts = exts;
|
||||
}
|
||||
|
||||
public String[] getExts() {
|
||||
return exts;
|
||||
}
|
||||
|
||||
public static ResourceType getFileType(String fileName) {
|
||||
for (ResourceType type : ResourceType.values()) {
|
||||
for (String ext : type.getExts()) {
|
||||
if (fileName.endsWith(ext)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
public static boolean isSupportedForUnpack(ResourceType type) {
|
||||
switch (type) {
|
||||
case CODE:
|
||||
case ARSC:
|
||||
case LIB:
|
||||
case XML:
|
||||
case FONT:
|
||||
case IMG:
|
||||
case UNKNOWN:
|
||||
return false;
|
||||
|
||||
case MANIFEST:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package jadx.api;
|
||||
|
||||
import jadx.api.ResourceFile.ZipRef;
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.utils.files.InputFile;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
// TODO: move to core package
|
||||
final class ResourcesLoader {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ResourcesLoader.class);
|
||||
|
||||
private static final int READ_BUFFER_SIZE = 8 * 1024;
|
||||
private static final int LOAD_SIZE_LIMIT = 500 * 1024;
|
||||
|
||||
private JadxDecompiler jadxRef;
|
||||
|
||||
ResourcesLoader(JadxDecompiler jadxRef) {
|
||||
this.jadxRef = jadxRef;
|
||||
}
|
||||
|
||||
List<ResourceFile> load(List<InputFile> inputFiles) {
|
||||
List<ResourceFile> list = new ArrayList<ResourceFile>(inputFiles.size());
|
||||
for (InputFile file : inputFiles) {
|
||||
loadFile(list, file.getFile());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
static CodeWriter loadContent(JadxDecompiler jadxRef, ZipRef zipRef, ResourceType type) {
|
||||
if (zipRef == null) {
|
||||
return null;
|
||||
}
|
||||
ZipFile zipFile = null;
|
||||
InputStream inputStream = null;
|
||||
try {
|
||||
zipFile = new ZipFile(zipRef.getZipFile());
|
||||
ZipEntry entry = zipFile.getEntry(zipRef.getEntryName());
|
||||
if (entry != null) {
|
||||
if (entry.getSize() > LOAD_SIZE_LIMIT) {
|
||||
return new CodeWriter().add("File too big, size: "
|
||||
+ String.format("%.2f KB", entry.getSize() / 1024.));
|
||||
}
|
||||
inputStream = new BufferedInputStream(zipFile.getInputStream(entry));
|
||||
return decode(jadxRef, type, inputStream);
|
||||
} else {
|
||||
LOG.warn("Zip entry not found: {}", zipRef);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.error("Error load: " + zipRef, e);
|
||||
} finally {
|
||||
try {
|
||||
if (zipFile != null) {
|
||||
zipFile.close();
|
||||
}
|
||||
if (inputStream != null) {
|
||||
inputStream.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.debug("Error close zip file: " + zipRef, e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static CodeWriter decode(JadxDecompiler jadxRef, ResourceType type,
|
||||
InputStream inputStream) throws IOException {
|
||||
switch (type) {
|
||||
case MANIFEST:
|
||||
case XML:
|
||||
return jadxRef.getXmlParser().parse(inputStream);
|
||||
}
|
||||
return loadToCodeWriter(inputStream);
|
||||
}
|
||||
|
||||
private void loadFile(List<ResourceFile> list, File file) {
|
||||
if (file == null) {
|
||||
return;
|
||||
}
|
||||
ZipFile zip = null;
|
||||
try {
|
||||
zip = new ZipFile(file);
|
||||
Enumeration<? extends ZipEntry> entries = zip.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
ZipEntry entry = entries.nextElement();
|
||||
addEntry(list, file, entry);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.debug("Not a zip file: " + file.getAbsolutePath());
|
||||
} finally {
|
||||
if (zip != null) {
|
||||
try {
|
||||
zip.close();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Zip file close error: " + file.getAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addEntry(List<ResourceFile> list, File zipFile, ZipEntry entry) {
|
||||
if (entry.isDirectory()) {
|
||||
return;
|
||||
}
|
||||
String name = entry.getName();
|
||||
ResourceType type = ResourceType.getFileType(name);
|
||||
ResourceFile rf = new ResourceFile(jadxRef, name, type);
|
||||
rf.setZipRef(new ZipRef(zipFile, name));
|
||||
list.add(rf);
|
||||
// LOG.debug("Add resource entry: {}, size: {}", name, entry.getSize());
|
||||
}
|
||||
|
||||
private static CodeWriter loadToCodeWriter(InputStream is) throws IOException {
|
||||
CodeWriter cw = new CodeWriter();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(READ_BUFFER_SIZE);
|
||||
byte[] buffer = new byte[READ_BUFFER_SIZE];
|
||||
int count;
|
||||
try {
|
||||
while ((count = is.read(buffer)) != -1) {
|
||||
baos.write(buffer, 0, count);
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
is.close();
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
cw.add(baos.toString("UTF-8"));
|
||||
return cw;
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,12 @@ import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.DexNode;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -32,9 +33,11 @@ public class BinaryXMLParser {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(BinaryXMLParser.class);
|
||||
|
||||
private byte[] bytes;
|
||||
private static final Charset STRING_CHARSET = Charset.forName("UTF-16LE");
|
||||
|
||||
private CodeWriter writer;
|
||||
private InputStream input;
|
||||
private String[] strings;
|
||||
private int count;
|
||||
|
||||
private String nsPrefix = "ERROR";
|
||||
private String nsURI = "ERROR";
|
||||
@@ -43,9 +46,9 @@ public class BinaryXMLParser {
|
||||
private boolean firstElement;
|
||||
private boolean wasOneLiner = false;
|
||||
|
||||
private CodeWriter writer;
|
||||
private Map<Integer, String> styleMap = new HashMap<Integer, String>();
|
||||
private Map<Integer, FieldNode> localStyleMap = new HashMap<Integer, FieldNode>();
|
||||
|
||||
private final ManifestAttributes attributes;
|
||||
|
||||
public BinaryXMLParser(RootNode root) {
|
||||
@@ -70,30 +73,34 @@ public class BinaryXMLParser {
|
||||
}
|
||||
}
|
||||
|
||||
public void parse(byte[] xmlBytes, File out) {
|
||||
LOG.debug("Decoding AndroidManifest.xml, output: {}", out);
|
||||
|
||||
public synchronized CodeWriter parse(InputStream inputStream) {
|
||||
writer = new CodeWriter();
|
||||
writer.add("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
|
||||
bytes = xmlBytes;
|
||||
count = 0;
|
||||
input = inputStream;
|
||||
firstElement = true;
|
||||
decode();
|
||||
writer.save(out);
|
||||
try {
|
||||
decode();
|
||||
} catch (IOException e) {
|
||||
LOG.debug("Binary xml decode failed", e);
|
||||
CodeWriter cw = new CodeWriter();
|
||||
cw.add("Error decode binary xml");
|
||||
cw.startLine(Utils.getStackTrace(e));
|
||||
return cw;
|
||||
}
|
||||
writer.finish();
|
||||
return writer;
|
||||
}
|
||||
|
||||
void decode() {
|
||||
if (cInt16(bytes, count) != 0x0003) {
|
||||
void decode() throws IOException {
|
||||
if (cInt16() != 0x0003) {
|
||||
die("Version is not 3");
|
||||
}
|
||||
if (cInt16(bytes, count) != 0x0008) {
|
||||
if (cInt16() != 0x0008) {
|
||||
die("Size of header is not 8");
|
||||
}
|
||||
if (cInt32(bytes, count) != bytes.length) {
|
||||
die("Size of manifest doesn't match");
|
||||
}
|
||||
while ((count + 2) <= bytes.length) {
|
||||
int type = cInt16(bytes, count);
|
||||
cInt32();
|
||||
while (input.available() != 0) {
|
||||
int type = cInt16();
|
||||
switch (type) {
|
||||
case 0x0001:
|
||||
parseStringPool();
|
||||
@@ -124,105 +131,106 @@ public class BinaryXMLParser {
|
||||
}
|
||||
}
|
||||
|
||||
private void parseStringPool() {
|
||||
if (cInt16(bytes, count) != 0x001c) {
|
||||
private void parseStringPool() throws IOException {
|
||||
if (cInt16() != 0x001c) {
|
||||
die("Header header size not 28");
|
||||
}
|
||||
int hsize = cInt32(bytes, count);
|
||||
int stringCount = cInt32(bytes, count);
|
||||
int styleCount = cInt32(bytes, count);
|
||||
int flags = cInt32(bytes, count);
|
||||
int stringsStart = cInt32(bytes, count);
|
||||
int stylesStart = cInt32(bytes, count);
|
||||
int hsize = cInt32();
|
||||
int stringCount = cInt32();
|
||||
int styleCount = cInt32();
|
||||
int flags = cInt32();
|
||||
int stringsStart = cInt32();
|
||||
int stylesStart = cInt32();
|
||||
int[] stringsOffsets = new int[stringCount];
|
||||
for (int i = 0; i < stringCount; i++) {
|
||||
stringsOffsets[i] = cInt32(bytes, count);
|
||||
stringsOffsets[i] = cInt32();
|
||||
}
|
||||
strings = new String[stringCount];
|
||||
for (int i = 0; i < stringCount; i++) {
|
||||
int off = 8 + stringsStart + stringsOffsets[i];
|
||||
int strlen = cInt16(bytes, off);
|
||||
int strlen = cInt16();
|
||||
byte[] str = new byte[strlen * 2];
|
||||
System.arraycopy(bytes, count, str, 0, strlen * 2);
|
||||
count += strlen * 2;
|
||||
strings[i] = new String(str, Charset.forName("UTF-16LE"));
|
||||
count += 2;
|
||||
readToArray(str);
|
||||
strings[i] = new String(str, STRING_CHARSET);
|
||||
cInt16();
|
||||
}
|
||||
}
|
||||
|
||||
private void parseResourceMap() {
|
||||
if (cInt16(bytes, count) != 0x8) {
|
||||
private void parseResourceMap() throws IOException {
|
||||
if (cInt16() != 0x8) {
|
||||
die("Header size of resmap is not 8!");
|
||||
}
|
||||
int rhsize = cInt32(bytes, count);
|
||||
int rhsize = cInt32();
|
||||
int[] ids = new int[(rhsize - 8) / 4];
|
||||
for (int i = 0; i < ids.length; i++) {
|
||||
ids[i] = cInt32(bytes, count);
|
||||
ids[i] = cInt32();
|
||||
}
|
||||
}
|
||||
|
||||
private void parseNameSpace() {
|
||||
if (cInt16(bytes, count) != 0x0010) {
|
||||
private void parseNameSpace() throws IOException {
|
||||
if (cInt16() != 0x10) {
|
||||
die("NAMESPACE header is not 0x0010");
|
||||
}
|
||||
if (cInt32(bytes, count) != 0x18) {
|
||||
if (cInt32() != 0x18) {
|
||||
die("NAMESPACE header chunk is not 0x18 big");
|
||||
}
|
||||
int beginLineNumber = cInt32(bytes, count);
|
||||
int comment = cInt32(bytes, count);
|
||||
int beginPrefix = cInt32(bytes, count);
|
||||
int beginLineNumber = cInt32();
|
||||
int comment = cInt32();
|
||||
int beginPrefix = cInt32();
|
||||
nsPrefix = strings[beginPrefix];
|
||||
int beginURI = cInt32(bytes, count);
|
||||
int beginURI = cInt32();
|
||||
nsURI = strings[beginURI];
|
||||
}
|
||||
|
||||
private void parseNameSpaceEnd() {
|
||||
if (cInt16(bytes, count) != 0x0010) {
|
||||
private void parseNameSpaceEnd() throws IOException {
|
||||
if (cInt16() != 0x10) {
|
||||
die("NAMESPACE header is not 0x0010");
|
||||
}
|
||||
if (cInt32(bytes, count) != 0x18) {
|
||||
if (cInt32() != 0x18) {
|
||||
die("NAMESPACE header chunk is not 0x18 big");
|
||||
}
|
||||
int endLineNumber = cInt32(bytes, count);
|
||||
int comment = cInt32(bytes, count);
|
||||
int endPrefix = cInt32(bytes, count);
|
||||
int endLineNumber = cInt32();
|
||||
int comment = cInt32();
|
||||
int endPrefix = cInt32();
|
||||
nsPrefix = strings[endPrefix];
|
||||
int endURI = cInt32(bytes, count);
|
||||
int endURI = cInt32();
|
||||
nsURI = strings[endURI];
|
||||
}
|
||||
|
||||
private void parseElement() {
|
||||
private void parseElement() throws IOException {
|
||||
if (firstElement) {
|
||||
firstElement = false;
|
||||
} else {
|
||||
writer.incIndent();
|
||||
}
|
||||
if (cInt16(bytes, count) != 0x0010) {
|
||||
if (cInt16() != 0x10) {
|
||||
die("ELEMENT HEADER SIZE is not 0x10");
|
||||
}
|
||||
count += 4; // TODO: Check element chunk size
|
||||
int elementBegLineNumber = cInt32(bytes, count);
|
||||
int comment = cInt32(bytes, count);
|
||||
int startNS = cInt32(bytes, count);
|
||||
int startNSName = cInt32(bytes, count); // actually is elementName...
|
||||
// TODO: Check element chunk size
|
||||
cInt32();
|
||||
int elementBegLineNumber = cInt32();
|
||||
int comment = cInt32();
|
||||
int startNS = cInt32();
|
||||
int startNSName = cInt32(); // actually is elementName...
|
||||
if (!wasOneLiner && !"ERROR".equals(currentTag) && !currentTag.equals(strings[startNSName])) {
|
||||
writer.add(">");
|
||||
}
|
||||
wasOneLiner = false;
|
||||
currentTag = strings[startNSName];
|
||||
writer.startLine("<").add(strings[startNSName]);
|
||||
int attributeStart = cInt16(bytes, count);
|
||||
writer.attachSourceLine(elementBegLineNumber);
|
||||
int attributeStart = cInt16();
|
||||
if (attributeStart != 0x14) {
|
||||
die("startNS's attributeStart is not 0x14");
|
||||
}
|
||||
int attributeSize = cInt16(bytes, count);
|
||||
int attributeSize = cInt16();
|
||||
if (attributeSize != 0x14) {
|
||||
die("startNS's attributeSize is not 0x14");
|
||||
}
|
||||
int attributeCount = cInt16(bytes, count);
|
||||
int idIndex = cInt16(bytes, count);
|
||||
int classIndex = cInt16(bytes, count);
|
||||
int styleIndex = cInt16(bytes, count);
|
||||
int attributeCount = cInt16();
|
||||
int idIndex = cInt16();
|
||||
int classIndex = cInt16();
|
||||
int styleIndex = cInt16();
|
||||
if ("manifest".equals(strings[startNSName])) {
|
||||
writer.add(" xmlns:\"").add(nsURI).add("\"");
|
||||
}
|
||||
@@ -230,71 +238,7 @@ public class BinaryXMLParser {
|
||||
writer.add(" ");
|
||||
}
|
||||
for (int i = 0; i < attributeCount; i++) {
|
||||
int attributeNS = cInt32(bytes, count);
|
||||
int attributeName = cInt32(bytes, count);
|
||||
int attributeRawValue = cInt32(bytes, count);
|
||||
int attrValSize = cInt16(bytes, count);
|
||||
if (attrValSize != 0x08) {
|
||||
die("attrValSize != 0x08 not supported");
|
||||
}
|
||||
if (cInt8(bytes, count) != 0) {
|
||||
die("res0 is not 0");
|
||||
}
|
||||
int attrValDataType = cInt8(bytes, count);
|
||||
int attrValData = cInt32(bytes, count);
|
||||
if (attributeNS != -1) {
|
||||
writer.add(nsPrefix).add(':');
|
||||
}
|
||||
String attrName = strings[attributeName];
|
||||
writer.add(attrName).add("=\"");
|
||||
String decodedAttr = attributes.decode(attrName, attrValData);
|
||||
if (decodedAttr != null) {
|
||||
writer.add(decodedAttr);
|
||||
} else {
|
||||
switch (attrValDataType) {
|
||||
case 0x3:
|
||||
writer.add(strings[attrValData]);
|
||||
break;
|
||||
|
||||
case 0x10:
|
||||
writer.add(String.valueOf(attrValData));
|
||||
break;
|
||||
|
||||
case 0x12:
|
||||
// FIXME: What to do, when data is always -1?
|
||||
if (attrValData == 0) {
|
||||
writer.add("false");
|
||||
} else if (attrValData == 1 || attrValData == -1) {
|
||||
writer.add("true");
|
||||
} else {
|
||||
writer.add("UNKNOWN_BOOLEAN_TYPE");
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x1:
|
||||
String name = styleMap.get(attrValData);
|
||||
if (name != null) {
|
||||
writer.add("@*");
|
||||
if (attributeNS != -1) {
|
||||
writer.add(nsPrefix).add(':');
|
||||
}
|
||||
writer.add("style/").add(name.replaceAll("_", "."));
|
||||
} else {
|
||||
FieldNode field = localStyleMap.get(attrValData);
|
||||
if (field != null) {
|
||||
String cls = field.getParentClass().getShortName().toLowerCase();
|
||||
writer.add("@").add(cls).add("/").add(field.getName());
|
||||
} else {
|
||||
writer.add("0x").add(Integer.toHexString(attrValData));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
writer.add("UNKNOWN_DATA_TYPE_" + attrValDataType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
parseAttribute(i);
|
||||
writer.add('"');
|
||||
if ((i + 1) < attributeCount) {
|
||||
writer.add(" ");
|
||||
@@ -302,22 +246,95 @@ public class BinaryXMLParser {
|
||||
}
|
||||
}
|
||||
|
||||
private void parseElementEnd() {
|
||||
if (cInt16(bytes, count) != 0x0010) {
|
||||
die("ELEMENT END header is not 0x0010");
|
||||
private void parseAttribute(int i) throws IOException {
|
||||
int attributeNS = cInt32();
|
||||
int attributeName = cInt32();
|
||||
int attributeRawValue = cInt32();
|
||||
int attrValSize = cInt16();
|
||||
if (attrValSize != 0x08) {
|
||||
die("attrValSize != 0x08 not supported");
|
||||
}
|
||||
if (cInt32(bytes, count) != 0x18) {
|
||||
if (cInt8() != 0) {
|
||||
die("res0 is not 0");
|
||||
}
|
||||
int attrValDataType = cInt8();
|
||||
int attrValData = cInt32();
|
||||
if (attributeNS != -1) {
|
||||
writer.add(nsPrefix).add(':');
|
||||
}
|
||||
String attrName = strings[attributeName];
|
||||
writer.add(attrName).add("=\"");
|
||||
String decodedAttr = attributes.decode(attrName, attrValData);
|
||||
if (decodedAttr != null) {
|
||||
writer.add(decodedAttr);
|
||||
} else {
|
||||
decodeAttribute(attributeNS, attrValDataType, attrValData);
|
||||
}
|
||||
}
|
||||
|
||||
private void decodeAttribute(int attributeNS, int attrValDataType, int attrValData) {
|
||||
switch (attrValDataType) {
|
||||
case 0x3:
|
||||
writer.add(strings[attrValData]);
|
||||
break;
|
||||
|
||||
case 0x10:
|
||||
writer.add(String.valueOf(attrValData));
|
||||
break;
|
||||
|
||||
case 0x12:
|
||||
// FIXME: What to do, when data is always -1?
|
||||
if (attrValData == 0) {
|
||||
writer.add("false");
|
||||
} else if (attrValData == 1 || attrValData == -1) {
|
||||
writer.add("true");
|
||||
} else {
|
||||
writer.add("UNKNOWN_BOOLEAN_TYPE");
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x1:
|
||||
String name = styleMap.get(attrValData);
|
||||
if (name != null) {
|
||||
writer.add("@*");
|
||||
if (attributeNS != -1) {
|
||||
writer.add(nsPrefix).add(':');
|
||||
}
|
||||
writer.add("style/").add(name.replaceAll("_", "."));
|
||||
} else {
|
||||
FieldNode field = localStyleMap.get(attrValData);
|
||||
if (field != null) {
|
||||
String cls = field.getParentClass().getShortName().toLowerCase();
|
||||
writer.add("@").add(cls).add("/").add(field.getName());
|
||||
} else {
|
||||
writer.add("0x").add(Integer.toHexString(attrValData));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
writer.add("UNKNOWN_DATA_TYPE_" + attrValDataType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void parseElementEnd() throws IOException {
|
||||
if (cInt16() != 0x10) {
|
||||
die("ELEMENT END header is not 0x10");
|
||||
}
|
||||
if (cInt32() != 0x18) {
|
||||
die("ELEMENT END header chunk is not 0x18 big");
|
||||
}
|
||||
int endLineNumber = cInt32(bytes, count);
|
||||
int comment = cInt32(bytes, count);
|
||||
int elementNS = cInt32(bytes, count);
|
||||
int elementName = cInt32(bytes, count);
|
||||
int endLineNumber = cInt32();
|
||||
int comment = cInt32();
|
||||
int elementNS = cInt32();
|
||||
int elementName = cInt32();
|
||||
if (currentTag.equals(strings[elementName])) {
|
||||
writer.add(" />");
|
||||
wasOneLiner = true;
|
||||
} else {
|
||||
writer.startLine("</");
|
||||
writer.attachSourceLine(endLineNumber);
|
||||
if (elementNS != -1) {
|
||||
writer.add(strings[elementNS]).add(':');
|
||||
}
|
||||
@@ -328,26 +345,35 @@ public class BinaryXMLParser {
|
||||
}
|
||||
}
|
||||
|
||||
private int cInt8(byte[] bytes, int offset) {
|
||||
byte[] tmp = new byte[4];
|
||||
tmp[3] = bytes[count++];
|
||||
return ByteBuffer.wrap(tmp).getInt();
|
||||
private int cInt8() throws IOException {
|
||||
return input.read();
|
||||
}
|
||||
|
||||
private int cInt16(byte[] bytes, int offset) {
|
||||
byte[] tmp = new byte[4];
|
||||
tmp[3] = bytes[count++];
|
||||
tmp[2] = bytes[count++];
|
||||
return ByteBuffer.wrap(tmp).getInt();
|
||||
private int cInt16() throws IOException {
|
||||
int b1 = input.read();
|
||||
int b2 = input.read();
|
||||
return (b2 & 0xFF) << 8 | (b1 & 0xFF);
|
||||
}
|
||||
|
||||
private int cInt32(byte[] bytes, int offset) {
|
||||
byte[] tmp = new byte[4];
|
||||
for (int i = 0; i < 4; i++) {
|
||||
tmp[3 - i] = bytes[count + i];
|
||||
private int cInt32() throws IOException {
|
||||
InputStream in = input;
|
||||
int b1 = in.read();
|
||||
int b2 = in.read();
|
||||
int b3 = in.read();
|
||||
int b4 = in.read();
|
||||
return b4 << 24 | (b3 & 0xFF) << 16 | (b2 & 0xFF) << 8 | (b1 & 0xFF);
|
||||
}
|
||||
|
||||
private void readToArray(byte[] arr) throws IOException {
|
||||
int count = arr.length;
|
||||
int pos = input.read(arr, 0, count);
|
||||
while (pos < count) {
|
||||
int read = input.read(arr, pos, count - pos);
|
||||
if (read == -1) {
|
||||
throw new IOException("No data, can't read " + count + " bytes");
|
||||
}
|
||||
pos += read;
|
||||
}
|
||||
count += 4;
|
||||
return ByteBuffer.wrap(tmp).getInt();
|
||||
}
|
||||
|
||||
private void die(String message) {
|
||||
|
||||
@@ -128,6 +128,11 @@ public abstract class IntegrationTest extends TestUtils {
|
||||
public int getThreadsCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSkipResources() {
|
||||
return true;
|
||||
}
|
||||
}, new File(outDir));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user