Commit 3700ecb7 authored by Skylot's avatar Skylot

core: add resources methods to jadx API

parent 811b0e7f
...@@ -32,6 +32,12 @@ public final class JadxCLIArgs implements IJadxArgs { ...@@ -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)") @Parameter(names = {"-f", "--fallback"}, description = "make simple dump (using goto instead of 'if', 'for', etc)")
protected boolean fallbackMode = false; 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)") @Parameter(names = {"--show-bad-code"}, description = "show inconsistent code (incorrectly decompiled)")
protected boolean showInconsistentCode = false; protected boolean showInconsistentCode = false;
...@@ -47,9 +53,6 @@ public final class JadxCLIArgs implements IJadxArgs { ...@@ -47,9 +53,6 @@ public final class JadxCLIArgs implements IJadxArgs {
@Parameter(names = {"-h", "--help"}, description = "print this help", help = true) @Parameter(names = {"-h", "--help"}, description = "print this help", help = true)
protected boolean printHelp = false; 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 final List<File> input = new ArrayList<File>(1);
private File outputDir; private File outputDir;
...@@ -168,8 +171,13 @@ public final class JadxCLIArgs implements IJadxArgs { ...@@ -168,8 +171,13 @@ public final class JadxCLIArgs implements IJadxArgs {
} }
@Override @Override
public boolean isXMLTest() { public boolean isSkipResources() {
return xmlTest; return skipResources;
}
@Override
public boolean isSkipSources() {
return skipSources;
} }
@Override @Override
......
...@@ -40,7 +40,12 @@ public class DefaultJadxArgs implements IJadxArgs { ...@@ -40,7 +40,12 @@ public class DefaultJadxArgs implements IJadxArgs {
} }
@Override @Override
public boolean isXMLTest() { public boolean isSkipResources() {
return false;
}
@Override
public boolean isSkipSources() {
return false; return false;
} }
} }
...@@ -17,5 +17,7 @@ public interface IJadxArgs { ...@@ -17,5 +17,7 @@ public interface IJadxArgs {
boolean isVerbose(); boolean isVerbose();
boolean isXMLTest(); boolean isSkipResources();
boolean isSkipSources();
} }
...@@ -2,6 +2,7 @@ package jadx.api; ...@@ -2,6 +2,7 @@ package jadx.api;
import jadx.core.Jadx; import jadx.core.Jadx;
import jadx.core.ProcessClass; import jadx.core.ProcessClass;
import jadx.core.codegen.CodeWriter;
import jadx.core.dex.info.ClassInfo; import jadx.core.dex.info.ClassInfo;
import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.RootNode; import jadx.core.dex.nodes.RootNode;
...@@ -55,6 +56,9 @@ public final class JadxDecompiler { ...@@ -55,6 +56,9 @@ public final class JadxDecompiler {
private RootNode root; private RootNode root;
private List<IDexTreeVisitor> passes; private List<IDexTreeVisitor> passes;
private List<JavaClass> classes; private List<JavaClass> classes;
private List<ResourceFile> resources;
private BinaryXMLParser xmlParser;
public JadxDecompiler() { public JadxDecompiler() {
this(new DefaultJadxArgs()); this(new DefaultJadxArgs());
...@@ -82,6 +86,8 @@ public final class JadxDecompiler { ...@@ -82,6 +86,8 @@ public final class JadxDecompiler {
void reset() { void reset() {
ClassInfo.clearCache(); ClassInfo.clearCache();
classes = null; classes = null;
resources = null;
xmlParser = null;
root = null; root = null;
} }
...@@ -108,27 +114,21 @@ public final class JadxDecompiler { ...@@ -108,27 +114,21 @@ public final class JadxDecompiler {
parse(); parse();
} }
public void parseAndSaveXML() { public void save() {
if (this.args.isXMLTest()) { save(!args.isSkipSources(), !args.isSkipResources());
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() { public void saveSources() {
parseAndSaveXML(); save(true, false);
}
public void saveResources() {
save(false, true);
}
private void save(boolean saveSources, boolean saveResources) {
try { try {
ExecutorService ex = getSaveExecutor(); ExecutorService ex = getSaveExecutor(saveSources, saveResources);
ex.shutdown(); ex.shutdown();
ex.awaitTermination(1, TimeUnit.DAYS); ex.awaitTermination(1, TimeUnit.DAYS);
} catch (InterruptedException e) { } catch (InterruptedException e) {
...@@ -137,6 +137,10 @@ public final class JadxDecompiler { ...@@ -137,6 +137,10 @@ public final class JadxDecompiler {
} }
public ExecutorService getSaveExecutor() { public ExecutorService getSaveExecutor() {
return getSaveExecutor(!args.isSkipSources(), !args.isSkipResources());
}
private ExecutorService getSaveExecutor(boolean saveSources, boolean saveResources) {
if (root == null) { if (root == null) {
throw new JadxRuntimeException("No loaded files"); throw new JadxRuntimeException("No loaded files");
} }
...@@ -145,14 +149,31 @@ public final class JadxDecompiler { ...@@ -145,14 +149,31 @@ public final class JadxDecompiler {
LOG.info("processing ..."); LOG.info("processing ...");
ExecutorService executor = Executors.newFixedThreadPool(threadsCount); ExecutorService executor = Executors.newFixedThreadPool(threadsCount);
for (final JavaClass cls : getClasses()) { if (saveSources) {
executor.execute(new Runnable() { for (final JavaClass cls : getClasses()) {
@Override executor.execute(new Runnable() {
public void run() { @Override
cls.decompile(); public void run() {
SaveCode.save(outDir, args, cls.getClassNode()); 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; return executor;
} }
...@@ -172,6 +193,16 @@ public final class JadxDecompiler { ...@@ -172,6 +193,16 @@ public final class JadxDecompiler {
return classes; 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() { public List<JavaPackage> getPackages() {
List<JavaClass> classList = getClasses(); List<JavaClass> classList = getClasses();
if (classList.isEmpty()) { if (classList.isEmpty()) {
...@@ -232,6 +263,13 @@ public final class JadxDecompiler { ...@@ -232,6 +263,13 @@ public final class JadxDecompiler {
return root; return root;
} }
BinaryXMLParser getXmlParser() {
if (xmlParser == null) {
xmlParser = new BinaryXMLParser(root);
}
return xmlParser;
}
JavaClass findJavaClass(ClassNode cls) { JavaClass findJavaClass(ClassNode cls) {
if (cls == null) { if (cls == null) {
return null; return null;
......
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 + "}";
}
}
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;
}
}
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; ...@@ -5,11 +5,12 @@ import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.RootNode; import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException; 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.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
...@@ -32,9 +33,11 @@ public class BinaryXMLParser { ...@@ -32,9 +33,11 @@ public class BinaryXMLParser {
private static final Logger LOG = LoggerFactory.getLogger(BinaryXMLParser.class); 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 String[] strings;
private int count;
private String nsPrefix = "ERROR"; private String nsPrefix = "ERROR";
private String nsURI = "ERROR"; private String nsURI = "ERROR";
...@@ -43,9 +46,9 @@ public class BinaryXMLParser { ...@@ -43,9 +46,9 @@ public class BinaryXMLParser {
private boolean firstElement; private boolean firstElement;
private boolean wasOneLiner = false; private boolean wasOneLiner = false;
private CodeWriter writer;
private Map<Integer, String> styleMap = new HashMap<Integer, String>(); private Map<Integer, String> styleMap = new HashMap<Integer, String>();
private Map<Integer, FieldNode> localStyleMap = new HashMap<Integer, FieldNode>(); private Map<Integer, FieldNode> localStyleMap = new HashMap<Integer, FieldNode>();
private final ManifestAttributes attributes; private final ManifestAttributes attributes;
public BinaryXMLParser(RootNode root) { public BinaryXMLParser(RootNode root) {
...@@ -70,30 +73,34 @@ public class BinaryXMLParser { ...@@ -70,30 +73,34 @@ public class BinaryXMLParser {
} }
} }
public void parse(byte[] xmlBytes, File out) { public synchronized CodeWriter parse(InputStream inputStream) {
LOG.debug("Decoding AndroidManifest.xml, output: {}", out);
writer = new CodeWriter(); writer = new CodeWriter();
writer.add("<?xml version=\"1.0\" encoding=\"utf-8\"?>"); writer.add("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
bytes = xmlBytes; input = inputStream;
count = 0;
firstElement = true; firstElement = true;
decode(); try {
writer.save(out); 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() { void decode() throws IOException {
if (cInt16(bytes, count) != 0x0003) { if (cInt16() != 0x0003) {
die("Version is not 3"); die("Version is not 3");
} }
if (cInt16(bytes, count) != 0x0008) { if (cInt16() != 0x0008) {
die("Size of header is not 8"); die("Size of header is not 8");
} }
if (cInt32(bytes, count) != bytes.length) { cInt32();
die("Size of manifest doesn't match"); while (input.available() != 0) {
} int type = cInt16();
while ((count + 2) <= bytes.length) {
int type = cInt16(bytes, count);
switch (type) { switch (type) {
case 0x0001: case 0x0001:
parseStringPool(); parseStringPool();
...@@ -124,105 +131,106 @@ public class BinaryXMLParser { ...@@ -124,105 +131,106 @@ public class BinaryXMLParser {
} }
} }
private void parseStringPool() { private void parseStringPool() throws IOException {
if (cInt16(bytes, count) != 0x001c) { if (cInt16() != 0x001c) {
die("Header header size not 28"); die("Header header size not 28");
} }
int hsize = cInt32(bytes, count); int hsize = cInt32();
int stringCount = cInt32(bytes, count); int stringCount = cInt32();
int styleCount = cInt32(bytes, count); int styleCount = cInt32();
int flags = cInt32(bytes, count); int flags = cInt32();
int stringsStart = cInt32(bytes, count); int stringsStart = cInt32();
int stylesStart = cInt32(bytes, count); int stylesStart = cInt32();
int[] stringsOffsets = new int[stringCount]; int[] stringsOffsets = new int[stringCount];
for (int i = 0; i < stringCount; i++) { for (int i = 0; i < stringCount; i++) {
stringsOffsets[i] = cInt32(bytes, count); stringsOffsets[i] = cInt32();
} }
strings = new String[stringCount]; strings = new String[stringCount];
for (int i = 0; i < stringCount; i++) { for (int i = 0; i < stringCount; i++) {
int off = 8 + stringsStart + stringsOffsets[i]; int off = 8 + stringsStart + stringsOffsets[i];
int strlen = cInt16(bytes, off); int strlen = cInt16();
byte[] str = new byte[strlen * 2]; byte[] str = new byte[strlen * 2];
System.arraycopy(bytes, count, str, 0, strlen * 2); readToArray(str);
count += strlen * 2; strings[i] = new String(str, STRING_CHARSET);
strings[i] = new String(str, Charset.forName("UTF-16LE")); cInt16();
count += 2;
} }
} }
private void parseResourceMap() { private void parseResourceMap() throws IOException {
if (cInt16(bytes, count) != 0x8) { if (cInt16() != 0x8) {
die("Header size of resmap is not 8!"); die("Header size of resmap is not 8!");
} }
int rhsize = cInt32(bytes, count); int rhsize = cInt32();
int[] ids = new int[(rhsize - 8) / 4]; int[] ids = new int[(rhsize - 8) / 4];
for (int i = 0; i < ids.length; i++) { for (int i = 0; i < ids.length; i++) {
ids[i] = cInt32(bytes, count); ids[i] = cInt32();
} }
} }
private void parseNameSpace() { private void parseNameSpace() throws IOException {
if (cInt16(bytes, count) != 0x0010) { if (cInt16() != 0x10) {
die("NAMESPACE header is not 0x0010"); die("NAMESPACE header is not 0x0010");
} }
if (cInt32(bytes, count) != 0x18) { if (cInt32() != 0x18) {
die("NAMESPACE header chunk is not 0x18 big"); die("NAMESPACE header chunk is not 0x18 big");
} }
int beginLineNumber = cInt32(bytes, count); int beginLineNumber = cInt32();
int comment = cInt32(bytes, count); int comment = cInt32();
int beginPrefix = cInt32(bytes, count); int beginPrefix = cInt32();
nsPrefix = strings[beginPrefix]; nsPrefix = strings[beginPrefix];
int beginURI = cInt32(bytes, count); int beginURI = cInt32();
nsURI = strings[beginURI]; nsURI = strings[beginURI];
} }
private void parseNameSpaceEnd() { private void parseNameSpaceEnd() throws IOException {
if (cInt16(bytes, count) != 0x0010) { if (cInt16() != 0x10) {
die("NAMESPACE header is not 0x0010"); die("NAMESPACE header is not 0x0010");
} }
if (cInt32(bytes, count) != 0x18) { if (cInt32() != 0x18) {
die("NAMESPACE header chunk is not 0x18 big"); die("NAMESPACE header chunk is not 0x18 big");
} }
int endLineNumber = cInt32(bytes, count); int endLineNumber = cInt32();
int comment = cInt32(bytes, count); int comment = cInt32();
int endPrefix = cInt32(bytes, count); int endPrefix = cInt32();
nsPrefix = strings[endPrefix]; nsPrefix = strings[endPrefix];
int endURI = cInt32(bytes, count); int endURI = cInt32();
nsURI = strings[endURI]; nsURI = strings[endURI];
} }
private void parseElement() { private void parseElement() throws IOException {
if (firstElement) { if (firstElement) {
firstElement = false; firstElement = false;
} else { } else {
writer.incIndent(); writer.incIndent();
} }
if (cInt16(bytes, count) != 0x0010) { if (cInt16() != 0x10) {
die("ELEMENT HEADER SIZE is not 0x10"); die("ELEMENT HEADER SIZE is not 0x10");
} }
count += 4; // TODO: Check element chunk size // TODO: Check element chunk size
int elementBegLineNumber = cInt32(bytes, count); cInt32();
int comment = cInt32(bytes, count); int elementBegLineNumber = cInt32();
int startNS = cInt32(bytes, count); int comment = cInt32();
int startNSName = cInt32(bytes, count); // actually is elementName... int startNS = cInt32();
int startNSName = cInt32(); // actually is elementName...
if (!wasOneLiner && !"ERROR".equals(currentTag) && !currentTag.equals(strings[startNSName])) { if (!wasOneLiner && !"ERROR".equals(currentTag) && !currentTag.equals(strings[startNSName])) {
writer.add(">"); writer.add(">");
} }
wasOneLiner = false; wasOneLiner = false;
currentTag = strings[startNSName]; currentTag = strings[startNSName];
writer.startLine("<").add(strings[startNSName]); writer.startLine("<").add(strings[startNSName]);
int attributeStart = cInt16(bytes, count); writer.attachSourceLine(elementBegLineNumber);
int attributeStart = cInt16();
if (attributeStart != 0x14) { if (attributeStart != 0x14) {
die("startNS's attributeStart is not 0x14"); die("startNS's attributeStart is not 0x14");
} }
int attributeSize = cInt16(bytes, count); int attributeSize = cInt16();
if (attributeSize != 0x14) { if (attributeSize != 0x14) {
die("startNS's attributeSize is not 0x14"); die("startNS's attributeSize is not 0x14");
} }
int attributeCount = cInt16(bytes, count); int attributeCount = cInt16();
int idIndex = cInt16(bytes, count); int idIndex = cInt16();
int classIndex = cInt16(bytes, count); int classIndex = cInt16();
int styleIndex = cInt16(bytes, count); int styleIndex = cInt16();
if ("manifest".equals(strings[startNSName])) { if ("manifest".equals(strings[startNSName])) {
writer.add(" xmlns:\"").add(nsURI).add("\""); writer.add(" xmlns:\"").add(nsURI).add("\"");
} }
...@@ -230,71 +238,7 @@ public class BinaryXMLParser { ...@@ -230,71 +238,7 @@ public class BinaryXMLParser {
writer.add(" "); writer.add(" ");
} }
for (int i = 0; i < attributeCount; i++) { for (int i = 0; i < attributeCount; i++) {
int attributeNS = cInt32(bytes, count); parseAttribute(i);
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;
}
}
writer.add('"'); writer.add('"');
if ((i + 1) < attributeCount) { if ((i + 1) < attributeCount) {
writer.add(" "); writer.add(" ");
...@@ -302,22 +246,95 @@ public class BinaryXMLParser { ...@@ -302,22 +246,95 @@ public class BinaryXMLParser {
} }
} }
private void parseElementEnd() { private void parseAttribute(int i) throws IOException {
if (cInt16(bytes, count) != 0x0010) { int attributeNS = cInt32();
die("ELEMENT END header is not 0x0010"); int attributeName = cInt32();
int attributeRawValue = cInt32();
int attrValSize = cInt16();
if (attrValSize != 0x08) {
die("attrValSize != 0x08 not supported");
}
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(bytes, count) != 0x18) { if (cInt32() != 0x18) {
die("ELEMENT END header chunk is not 0x18 big"); die("ELEMENT END header chunk is not 0x18 big");
} }
int endLineNumber = cInt32(bytes, count); int endLineNumber = cInt32();
int comment = cInt32(bytes, count); int comment = cInt32();
int elementNS = cInt32(bytes, count); int elementNS = cInt32();
int elementName = cInt32(bytes, count); int elementName = cInt32();
if (currentTag.equals(strings[elementName])) { if (currentTag.equals(strings[elementName])) {
writer.add(" />"); writer.add(" />");
wasOneLiner = true; wasOneLiner = true;
} else { } else {
writer.startLine("</"); writer.startLine("</");
writer.attachSourceLine(endLineNumber);
if (elementNS != -1) { if (elementNS != -1) {
writer.add(strings[elementNS]).add(':'); writer.add(strings[elementNS]).add(':');
} }
...@@ -328,26 +345,35 @@ public class BinaryXMLParser { ...@@ -328,26 +345,35 @@ public class BinaryXMLParser {
} }
} }
private int cInt8(byte[] bytes, int offset) { private int cInt8() throws IOException {
byte[] tmp = new byte[4]; return input.read();
tmp[3] = 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 cInt16(byte[] bytes, int offset) { private int cInt32() throws IOException {
byte[] tmp = new byte[4]; InputStream in = input;
tmp[3] = bytes[count++]; int b1 = in.read();
tmp[2] = bytes[count++]; int b2 = in.read();
return ByteBuffer.wrap(tmp).getInt(); int b3 = in.read();
int b4 = in.read();
return b4 << 24 | (b3 & 0xFF) << 16 | (b2 & 0xFF) << 8 | (b1 & 0xFF);
} }
private int cInt32(byte[] bytes, int offset) { private void readToArray(byte[] arr) throws IOException {
byte[] tmp = new byte[4]; int count = arr.length;
for (int i = 0; i < 4; i++) { int pos = input.read(arr, 0, count);
tmp[3 - i] = bytes[count + i]; 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) { private void die(String message) {
......
...@@ -128,6 +128,11 @@ public abstract class IntegrationTest extends TestUtils { ...@@ -128,6 +128,11 @@ public abstract class IntegrationTest extends TestUtils {
public int getThreadsCount() { public int getThreadsCount() {
return 1; return 1;
} }
@Override
public boolean isSkipResources() {
return true;
}
}, new File(outDir)); }, new File(outDir));
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment