manifest: restore application references and Android values (enums, flags)
This commit is contained in:
@@ -203,20 +203,21 @@ public final class JadxDecompiler {
|
||||
reset();
|
||||
root = new RootNode();
|
||||
LOG.info("loading ...");
|
||||
root.load(inputFiles);
|
||||
|
||||
if (this.args.isXMLTest()) {
|
||||
InputFile inf = inputFiles.get(0);
|
||||
try {
|
||||
byte[] buffer = InputFile.loadXMLBuffer(inf.getFile());
|
||||
if (buffer != null) {
|
||||
File out = new File(args.getOutDir(), "AndroidManifest.xml");
|
||||
BinaryXMLParser bxp = new BinaryXMLParser();
|
||||
BinaryXMLParser bxp = new BinaryXMLParser(root);
|
||||
bxp.parse(buffer, out);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.info("Decompiling AndroidManifest.xml failed!", e);
|
||||
}
|
||||
}
|
||||
root.load(inputFiles);
|
||||
}
|
||||
|
||||
void processClass(ClassNode cls) {
|
||||
|
||||
@@ -174,6 +174,10 @@ public class CodeWriter {
|
||||
updateIndent();
|
||||
}
|
||||
|
||||
public int getIndent() {
|
||||
return indent;
|
||||
}
|
||||
|
||||
public int getLine() {
|
||||
return line;
|
||||
}
|
||||
|
||||
@@ -104,6 +104,10 @@ public class RootNode {
|
||||
return searchClassByName(fullName);
|
||||
}
|
||||
|
||||
public List<DexNode> getDexNodes() {
|
||||
return dexNodes;
|
||||
}
|
||||
|
||||
public ErrorsCounter getErrorsCounter() {
|
||||
return errorsCounter;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package jadx.core.xmlgen;
|
||||
|
||||
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 java.io.File;
|
||||
@@ -40,16 +44,28 @@ public class BinaryXMLParser {
|
||||
private boolean wasOneLiner = false;
|
||||
|
||||
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() {
|
||||
styleMap = new HashMap<Integer, String>();
|
||||
public BinaryXMLParser(RootNode root) {
|
||||
try {
|
||||
for (Field f : AndroidR.style.class.getFields()) {
|
||||
styleMap.put(f.getInt(f.getType()), f.getName());
|
||||
}
|
||||
//TODO: also add application constant fields
|
||||
} catch (IllegalAccessException e) {
|
||||
// add application constants
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -194,7 +210,7 @@ public class BinaryXMLParser {
|
||||
}
|
||||
wasOneLiner = false;
|
||||
currentTag = strings[startNSName];
|
||||
writer.startLine("<" + strings[startNSName]);
|
||||
writer.startLine("<").add(strings[startNSName]);
|
||||
int attributeStart = cInt16(bytes, count);
|
||||
if (attributeStart != 0x14) {
|
||||
die("startNS's attributeStart is not 0x14");
|
||||
@@ -208,7 +224,7 @@ public class BinaryXMLParser {
|
||||
int classIndex = cInt16(bytes, count);
|
||||
int styleIndex = cInt16(bytes, count);
|
||||
if ("manifest".equals(strings[startNSName])) {
|
||||
writer.add(" xmlns:\"" + nsURI + "\"");
|
||||
writer.add(" xmlns:\"").add(nsURI).add("\"");
|
||||
}
|
||||
if (attributeCount > 0) {
|
||||
writer.add(" ");
|
||||
@@ -227,53 +243,59 @@ public class BinaryXMLParser {
|
||||
int attrValDataType = cInt8(bytes, count);
|
||||
int attrValData = cInt32(bytes, count);
|
||||
if (attributeNS != -1) {
|
||||
writer.add(nsPrefix + ":");
|
||||
writer.add(nsPrefix).add(':');
|
||||
}
|
||||
writer.add(strings[attributeName] + "=\"");
|
||||
if (attrValDataType == 0x3) {
|
||||
writer.add(strings[attrValData]);
|
||||
} else if (attrValDataType == 0x10) {
|
||||
writer.add(String.valueOf(attrValData));
|
||||
} 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));
|
||||
}
|
||||
String attrName = strings[attributeName];
|
||||
writer.add(attrName).add("=\"");
|
||||
String decodedAttr = attributes.decode(attrName, attrValData);
|
||||
if (decodedAttr != null) {
|
||||
writer.add(decodedAttr);
|
||||
} else {
|
||||
// TODO: extract values names from here:
|
||||
// https://github.com/android/platform_frameworks_base/blob/master/core/res/res/values/attrs_manifest.xml
|
||||
if ("configChanges".equals(strings[attributeName])) {
|
||||
if (attrValData == 1152) {
|
||||
writer.add("orientation");
|
||||
} else if (attrValData == 4016) {
|
||||
writer.add("keyboard|keyboardHidden|orientation|screenLayout|uiMode");
|
||||
} else if (attrValData == 176) {
|
||||
writer.add("keyboard|keyboardHidden|orientation");
|
||||
} else if (attrValData == 160) {
|
||||
writer.add("keyboardHidden|orientation");
|
||||
} else {
|
||||
writer.add("UNKNOWN_DATA_" + Integer.toHexString(attrValData));
|
||||
}
|
||||
} else {
|
||||
writer.add("UNKNOWN_DATA_TYPE_" + attrValDataType);
|
||||
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) {
|
||||
writer.add(" ");
|
||||
}
|
||||
@@ -292,16 +314,18 @@ public class BinaryXMLParser {
|
||||
int elementNS = cInt32(bytes, count);
|
||||
int elementName = cInt32(bytes, count);
|
||||
if (currentTag.equals(strings[elementName])) {
|
||||
writer.add("/>");
|
||||
writer.add(" />");
|
||||
wasOneLiner = true;
|
||||
} else {
|
||||
writer.startLine("</");
|
||||
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) {
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user