Commit 716db8b9 authored by Skylot's avatar Skylot

manifest: restore application references and Android values (enums, flags)

parent b55975a3
...@@ -203,20 +203,21 @@ public final class JadxDecompiler { ...@@ -203,20 +203,21 @@ public final class JadxDecompiler {
reset(); reset();
root = new RootNode(); root = new RootNode();
LOG.info("loading ..."); LOG.info("loading ...");
root.load(inputFiles);
if (this.args.isXMLTest()) { if (this.args.isXMLTest()) {
InputFile inf = inputFiles.get(0); InputFile inf = inputFiles.get(0);
try { try {
byte[] buffer = InputFile.loadXMLBuffer(inf.getFile()); byte[] buffer = InputFile.loadXMLBuffer(inf.getFile());
if (buffer != null) { if (buffer != null) {
File out = new File(args.getOutDir(), "AndroidManifest.xml"); File out = new File(args.getOutDir(), "AndroidManifest.xml");
BinaryXMLParser bxp = new BinaryXMLParser(); BinaryXMLParser bxp = new BinaryXMLParser(root);
bxp.parse(buffer, out); bxp.parse(buffer, out);
} }
} catch (Exception e) { } catch (Exception e) {
LOG.info("Decompiling AndroidManifest.xml failed!", e); LOG.info("Decompiling AndroidManifest.xml failed!", e);
} }
} }
root.load(inputFiles);
} }
void processClass(ClassNode cls) { void processClass(ClassNode cls) {
......
...@@ -174,6 +174,10 @@ public class CodeWriter { ...@@ -174,6 +174,10 @@ public class CodeWriter {
updateIndent(); updateIndent();
} }
public int getIndent() {
return indent;
}
public int getLine() { public int getLine() {
return line; return line;
} }
......
...@@ -104,6 +104,10 @@ public class RootNode { ...@@ -104,6 +104,10 @@ public class RootNode {
return searchClassByName(fullName); return searchClassByName(fullName);
} }
public List<DexNode> getDexNodes() {
return dexNodes;
}
public ErrorsCounter getErrorsCounter() { public ErrorsCounter getErrorsCounter() {
return errorsCounter; return errorsCounter;
} }
......
package jadx.core.xmlgen; package jadx.core.xmlgen;
import jadx.core.codegen.CodeWriter; import jadx.core.codegen.CodeWriter;
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.exceptions.JadxRuntimeException; import jadx.core.utils.exceptions.JadxRuntimeException;
import java.io.File; import java.io.File;
...@@ -40,16 +44,28 @@ public class BinaryXMLParser { ...@@ -40,16 +44,28 @@ public class BinaryXMLParser {
private boolean wasOneLiner = false; private boolean wasOneLiner = false;
private CodeWriter writer; private CodeWriter writer;
private Map<Integer, String> styleMap = null; private Map<Integer, String> styleMap = new HashMap<Integer, String>();
private Map<Integer, FieldNode> localStyleMap = new HashMap<Integer, FieldNode>();
private final ManifestAttributes attributes;
public BinaryXMLParser() { public BinaryXMLParser(RootNode root) {
styleMap = new HashMap<Integer, String>();
try { try {
for (Field f : AndroidR.style.class.getFields()) { for (Field f : AndroidR.style.class.getFields()) {
styleMap.put(f.getInt(f.getType()), f.getName()); styleMap.put(f.getInt(f.getType()), f.getName());
} }
//TODO: also add application constant fields // add application constants
} catch (IllegalAccessException e) { for (DexNode dexNode : root.getDexNodes()) {
for (Map.Entry<Object, FieldNode> entry : dexNode.getConstFields().entrySet()) {
Object key = entry.getKey();
FieldNode field = entry.getValue();
if (field.getType().equals(ArgType.INT) && key instanceof Integer) {
localStyleMap.put((Integer) key, field);
}
}
}
attributes = new ManifestAttributes();
attributes.parse();
} catch (Exception e) {
throw new JadxRuntimeException("BinaryXMLParser init error", e); throw new JadxRuntimeException("BinaryXMLParser init error", e);
} }
} }
...@@ -194,7 +210,7 @@ public class BinaryXMLParser { ...@@ -194,7 +210,7 @@ public class BinaryXMLParser {
} }
wasOneLiner = false; wasOneLiner = false;
currentTag = strings[startNSName]; currentTag = strings[startNSName];
writer.startLine("<" + strings[startNSName]); writer.startLine("<").add(strings[startNSName]);
int attributeStart = cInt16(bytes, count); int attributeStart = cInt16(bytes, count);
if (attributeStart != 0x14) { if (attributeStart != 0x14) {
die("startNS's attributeStart is not 0x14"); die("startNS's attributeStart is not 0x14");
...@@ -208,7 +224,7 @@ public class BinaryXMLParser { ...@@ -208,7 +224,7 @@ public class BinaryXMLParser {
int classIndex = cInt16(bytes, count); int classIndex = cInt16(bytes, count);
int styleIndex = cInt16(bytes, count); int styleIndex = cInt16(bytes, count);
if ("manifest".equals(strings[startNSName])) { if ("manifest".equals(strings[startNSName])) {
writer.add(" xmlns:\"" + nsURI + "\""); writer.add(" xmlns:\"").add(nsURI).add("\"");
} }
if (attributeCount > 0) { if (attributeCount > 0) {
writer.add(" "); writer.add(" ");
...@@ -227,53 +243,59 @@ public class BinaryXMLParser { ...@@ -227,53 +243,59 @@ public class BinaryXMLParser {
int attrValDataType = cInt8(bytes, count); int attrValDataType = cInt8(bytes, count);
int attrValData = cInt32(bytes, count); int attrValData = cInt32(bytes, count);
if (attributeNS != -1) { if (attributeNS != -1) {
writer.add(nsPrefix + ":"); writer.add(nsPrefix).add(':');
} }
writer.add(strings[attributeName] + "=\""); String attrName = strings[attributeName];
if (attrValDataType == 0x3) { writer.add(attrName).add("=\"");
writer.add(strings[attrValData]); String decodedAttr = attributes.decode(attrName, attrValData);
} else if (attrValDataType == 0x10) { if (decodedAttr != null) {
writer.add(String.valueOf(attrValData)); writer.add(decodedAttr);
} else if (attrValDataType == 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");
}
} else if (attrValDataType == 0x1) {
String name = styleMap.get(attrValData);
if (name != null) {
writer.add("@*");
if (attributeNS != -1) {
writer.add(nsPrefix + ":");
}
writer.add("style/" + name.replaceAll("_", "."));
} else {
writer.add("0x" + Integer.toHexString(attrValData));
}
} else { } else {
// TODO: extract values names from here: switch (attrValDataType) {
// https://github.com/android/platform_frameworks_base/blob/master/core/res/res/values/attrs_manifest.xml case 0x3:
if ("configChanges".equals(strings[attributeName])) { writer.add(strings[attrValData]);
if (attrValData == 1152) { break;
writer.add("orientation");
} else if (attrValData == 4016) { case 0x10:
writer.add("keyboard|keyboardHidden|orientation|screenLayout|uiMode"); writer.add(String.valueOf(attrValData));
} else if (attrValData == 176) { break;
writer.add("keyboard|keyboardHidden|orientation");
} else if (attrValData == 160) { case 0x12:
writer.add("keyboardHidden|orientation"); // FIXME: What to do, when data is always -1?
} else { if (attrValData == 0) {
writer.add("UNKNOWN_DATA_" + Integer.toHexString(attrValData)); writer.add("false");
} } else if (attrValData == 1 || attrValData == -1) {
} else { writer.add("true");
writer.add("UNKNOWN_DATA_TYPE_" + attrValDataType); } 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(" ");
} }
...@@ -292,16 +314,18 @@ public class BinaryXMLParser { ...@@ -292,16 +314,18 @@ public class BinaryXMLParser {
int elementNS = cInt32(bytes, count); int elementNS = cInt32(bytes, count);
int elementName = cInt32(bytes, count); int elementName = cInt32(bytes, count);
if (currentTag.equals(strings[elementName])) { if (currentTag.equals(strings[elementName])) {
writer.add("/>"); writer.add(" />");
wasOneLiner = true; wasOneLiner = true;
} else { } else {
writer.startLine("</"); writer.startLine("</");
if (elementNS != -1) { if (elementNS != -1) {
writer.add(strings[elementNS] + ":"); writer.add(strings[elementNS]).add(':');
} }
writer.add(strings[elementName] + ">"); writer.add(strings[elementName]).add(">");
}
if (writer.getIndent() != 0) {
writer.decIndent();
} }
writer.decIndent();
} }
private int cInt8(byte[] bytes, int offset) { private int cInt8(byte[] bytes, int offset) {
......
package jadx.core.xmlgen;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.InputStream;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class ManifestAttributes {
private static final Logger LOG = LoggerFactory.getLogger(ManifestAttributes.class);
private static final String MANIFEST_ATTR_XML = "/attrs_manifest.xml";
private enum MAttrType {
ENUM, FLAG
}
private static class MAttr {
private final MAttrType type;
private final Map<Integer, String> values = new LinkedHashMap<Integer, String>();
public MAttr(MAttrType type) {
this.type = type;
}
public MAttrType getType() {
return type;
}
public Map<Integer, String> getValues() {
return values;
}
@Override
public String toString() {
return "[" + type + ", " + values + "]";
}
}
private final Document doc;
private final Map<String, MAttr> attrMap = new HashMap<String, MAttr>();
public ManifestAttributes() throws Exception {
InputStream xmlStream = ManifestAttributes.class.getResourceAsStream(MANIFEST_ATTR_XML);
DocumentBuilder dBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
doc = dBuilder.parse(xmlStream);
}
public void parse() {
NodeList nodeList = doc.getChildNodes();
for (int count = 0; count < nodeList.getLength(); count++) {
Node node = nodeList.item(count);
if (node.getNodeType() == Node.ELEMENT_NODE) {
if (node.hasChildNodes()) {
parseAttrList(node.getChildNodes());
}
}
}
}
private void parseAttrList(NodeList nodeList) {
for (int count = 0; count < nodeList.getLength(); count++) {
Node tempNode = nodeList.item(count);
if (tempNode.getNodeType() == Node.ELEMENT_NODE
&& tempNode.hasAttributes()
&& tempNode.hasChildNodes()) {
String name = null;
NamedNodeMap nodeMap = tempNode.getAttributes();
for (int i = 0; i < nodeMap.getLength(); i++) {
Node node = nodeMap.item(i);
if (node.getNodeName().equals("name")) {
name = node.getNodeValue();
break;
}
}
if (name != null && tempNode.getNodeName().equals("attr")) {
parseValues(name, tempNode.getChildNodes());
} else {
parseAttrList(tempNode.getChildNodes());
}
}
}
}
private void parseValues(String name, NodeList nodeList) {
MAttr attr = null;
for (int count = 0; count < nodeList.getLength(); count++) {
Node tempNode = nodeList.item(count);
if (tempNode.getNodeType() == Node.ELEMENT_NODE
&& tempNode.hasAttributes()) {
if (attr == null) {
if (tempNode.getNodeName().equals("enum")) {
attr = new MAttr(MAttrType.ENUM);
} else if (tempNode.getNodeName().equals("flag")) {
attr = new MAttr(MAttrType.FLAG);
}
if (attr == null) {
return;
}
attrMap.put(name, attr);
}
NamedNodeMap attributes = tempNode.getAttributes();
Node nameNode = attributes.getNamedItem("name");
if (nameNode != null) {
Node valueNode = attributes.getNamedItem("value");
if (valueNode != null) {
try {
int key;
String nodeValue = valueNode.getNodeValue();
if (attr.getType() == MAttrType.ENUM) {
key = Integer.parseInt(nodeValue);
} else {
nodeValue = nodeValue.replace("0x", "");
key = Integer.parseInt(nodeValue, 16);
}
attr.getValues().put(key, nameNode.getNodeValue());
} catch (NumberFormatException e) {
LOG.debug("Failed parse manifest number", e);
}
}
}
}
}
}
public String decode(String attrName, int value) {
MAttr attr = attrMap.get(attrName);
if (attr == null) {
return null;
}
if (attr.getType() == MAttrType.ENUM) {
String name = attr.getValues().get(value);
if (name != null) {
return name;
}
} else if (attr.getType() == MAttrType.FLAG) {
StringBuilder sb = new StringBuilder();
for (Map.Entry<Integer, String> entry : attr.getValues().entrySet()) {
if ((value & entry.getKey()) != 0) {
sb.append(entry.getValue()).append('|');
}
}
if (sb.length() != 0) {
return sb.deleteCharAt(sb.length() - 1).toString();
}
}
return "UNKNOWN_DATA_" + Integer.toHexString(value);
}
}
This diff is collapsed.
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