Skip to content

Commit

Permalink
fix(res): fixed XML proto parsing for removed debug data (PR #2111)
Browse files Browse the repository at this point in the history
* Fixed XML proto parsing for removed debug data

* Fixed codestyle check

---------

Co-authored-by: bagipro <bugi@bugi>
  • Loading branch information
bagipro and bagipro authored Feb 27, 2024
1 parent a73c9e9 commit 881a716
Show file tree
Hide file tree
Showing 3 changed files with 221 additions and 184 deletions.
179 changes: 179 additions & 0 deletions jadx-core/src/main/java/jadx/core/xmlgen/CommonProtoParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package jadx.core.xmlgen;

import java.util.ArrayList;
import java.util.List;

import com.android.aapt.ConfigurationOuterClass;
import com.android.aapt.Resources;

import jadx.core.xmlgen.entry.EntryConfig;
import jadx.core.xmlgen.entry.ProtoValue;

public class CommonProtoParser {
protected ProtoValue parse(Resources.Style s) {
List<ProtoValue> namedValues = new ArrayList<>(s.getEntryCount());
String parent = s.getParent().getName();
if (parent.isEmpty()) {
parent = null;
} else {
parent = '@' + parent;
}
for (int i = 0; i < s.getEntryCount(); i++) {
Resources.Style.Entry entry = s.getEntry(i);
String name = entry.getKey().getName();
String value = parse(entry.getItem());
namedValues.add(new ProtoValue(value).setName(name));
}
return new ProtoValue().setNamedValues(namedValues).setParent(parent);
}

protected ProtoValue parse(Resources.Styleable s) {
List<ProtoValue> namedValues = new ArrayList<>(s.getEntryCount());
for (int i = 0; i < s.getEntryCount(); i++) {
Resources.Styleable.Entry e = s.getEntry(i);
namedValues.add(new ProtoValue('@' + e.getAttr().getName()));
}
return new ProtoValue().setNamedValues(namedValues);
}

protected ProtoValue parse(Resources.Array a) {
List<ProtoValue> namedValues = new ArrayList<>(a.getElementCount());
for (int i = 0; i < a.getElementCount(); i++) {
Resources.Array.Element e = a.getElement(i);
String value = parse(e.getItem());
namedValues.add(new ProtoValue(value));
}
return new ProtoValue().setNamedValues(namedValues);
}

protected ProtoValue parse(Resources.Attribute a) {
String format = XmlGenUtils.getAttrTypeAsString(a.getFormatFlags());
List<ProtoValue> namedValues = new ArrayList<>(a.getSymbolCount());
for (int i = 0; i < a.getSymbolCount(); i++) {
Resources.Attribute.Symbol s = a.getSymbol(i);
int type = s.getType();
String name = s.getName().getName();
String value = String.valueOf(s.getValue());
namedValues.add(new ProtoValue(value).setName(name).setType(type));
}
return new ProtoValue(format).setNamedValues(namedValues);
}

protected ProtoValue parse(Resources.Plural p) {
List<ProtoValue> namedValues = new ArrayList<>(p.getEntryCount());
for (int i = 0; i < p.getEntryCount(); i++) {
Resources.Plural.Entry e = p.getEntry(i);
String name = e.getArity().name();
String value = parse(e.getItem());
namedValues.add(new ProtoValue(value).setName(name));
}
return new ProtoValue().setNamedValues(namedValues);
}

protected ProtoValue parse(Resources.CompoundValue c) {
switch (c.getValueCase()) {
case STYLE:
return parse(c.getStyle());
case STYLEABLE:
return parse(c.getStyleable());
case ARRAY:
return parse(c.getArray());
case ATTR:
return parse(c.getAttr());
case PLURAL:
return parse(c.getPlural());
default:
return new ProtoValue("Unresolved value");
}
}

protected String parse(ConfigurationOuterClass.Configuration c) {
char[] language = c.getLocale().toCharArray();
if (language.length == 0) {
language = new char[] { '\00' };
}
short mcc = (short) c.getMcc();
short mnc = (short) c.getMnc();
byte orientation = (byte) c.getOrientationValue();
short screenWidth = (short) c.getScreenWidth();
short screenHeight = (short) c.getScreenHeight();
short screenWidthDp = (short) c.getScreenWidthDp();
short screenHeightDp = (short) c.getScreenHeightDp();
short smallestScreenWidthDp = (short) c.getSmallestScreenWidthDp();
short sdkVersion = (short) c.getSdkVersion();
byte keyboard = (byte) c.getKeyboardValue();
byte touchscreen = (byte) c.getTouchscreenValue();
int density = c.getDensity();
byte screenLayout = (byte) c.getScreenLayoutLongValue();
byte colorMode = (byte) (c.getHdrValue() | c.getWideColorGamutValue());
byte screenLayout2 = (byte) (c.getLayoutDirectionValue() | c.getScreenRoundValue());
byte navigation = (byte) c.getNavigationValue();
byte inputFlags = (byte) (c.getKeysHiddenValue() | c.getNavHiddenValue());
int size = c.getSerializedSize();
byte uiMode = (byte) (c.getUiModeNightValue() | c.getUiModeTypeValue());

c.getScreenLayoutSize(); // unknown field
c.getProduct(); // unknown field

return new EntryConfig(mcc, mnc, language, new char[] { '\00' },
orientation, touchscreen, density, keyboard, navigation,
inputFlags, screenWidth, screenHeight, sdkVersion,
screenLayout, uiMode, smallestScreenWidthDp, screenWidthDp,
screenHeightDp, new char[] { '\00' }, new char[] { '\00' }, screenLayout2,
colorMode, false, size).getQualifiers();
}

protected String parse(Resources.Item i) {
if (i.hasRawStr()) {
return i.getRawStr().getValue();
}
if (i.hasStr()) {
return i.getStr().getValue();
}
if (i.hasStyledStr()) {
return i.getStyledStr().getValue();
}
if (i.hasPrim()) {
Resources.Primitive prim = i.getPrim();
switch (prim.getOneofValueCase()) {
case NULL_VALUE:
return null;
case INT_DECIMAL_VALUE:
return String.valueOf(prim.getIntDecimalValue());
case INT_HEXADECIMAL_VALUE:
return Integer.toHexString(prim.getIntHexadecimalValue());
case BOOLEAN_VALUE:
return String.valueOf(prim.getBooleanValue());
case FLOAT_VALUE:
return String.valueOf(prim.getFloatValue());
case COLOR_ARGB4_VALUE:
return String.format("#%04x", prim.getColorArgb4Value());
case COLOR_ARGB8_VALUE:
return String.format("#%08x", prim.getColorArgb8Value());
case COLOR_RGB4_VALUE:
return String.format("#%03x", prim.getColorRgb4Value());
case COLOR_RGB8_VALUE:
return String.format("#%06x", prim.getColorRgb8Value());
case DIMENSION_VALUE:
return XmlGenUtils.decodeComplex(prim.getDimensionValue(), false);
case FRACTION_VALUE:
return XmlGenUtils.decodeComplex(prim.getDimensionValue(), true);
case EMPTY_VALUE:
default:
return "";
}
}
if (i.hasRef()) {
Resources.Reference ref = i.getRef();
String value = ref.getName();
if (value.isEmpty()) {
value = "id/" + ref.getId();
}
return '@' + value;
}
if (i.hasFile()) {
return i.getFile().getPath();
}
return "";
}
}
51 changes: 41 additions & 10 deletions jadx-core/src/main/java/jadx/core/xmlgen/ProtoXMLParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@
import com.android.aapt.Resources.XmlElement;
import com.android.aapt.Resources.XmlNamespace;
import com.android.aapt.Resources.XmlNode;
import com.google.protobuf.InvalidProtocolBufferException;

import jadx.api.ICodeInfo;
import jadx.api.ICodeWriter;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.StringUtils;
import jadx.core.xmlgen.entry.ValuesParser;

public class ProtoXMLParser {
public class ProtoXMLParser extends CommonProtoParser {
private Map<String, String> nsMap;
private final Map<String, String> tagAttrDeobfNames = new HashMap<>();

Expand Down Expand Up @@ -78,16 +78,48 @@ private void decode(XmlElement e) throws IOException {

private void decode(XmlAttribute a) {
writer.add(' ');
String namespace = a.getNamespaceUri();
if (!namespace.isEmpty()) {
writer.add(nsMap.get(namespace)).add(':');
}
String name = a.getName();
String value = deobfClassName(a.getValue());
String name = getAttributeFullName(a);
String value = deobfClassName(getAttributeValue(a));
writer.add(name).add("=\"").add(StringUtils.escapeXML(value)).add('\"');
memorizePackageName(name, value);
}

private String getAttributeFullName(XmlAttribute a) {
String namespaceUri = a.getNamespaceUri();
String namespace = null;
if (!namespaceUri.isEmpty()) {
namespace = nsMap.get(namespaceUri);
}

String attrName = a.getName();
if (attrName.isEmpty()) {
// some optimization tools clear the name because the Android platform doesn't need it
int resId = a.getResourceId();
String str = ValuesParser.getAndroidResMap().get(resId);
if (str != null) {
namespace = nsMap.get(ParserConstants.ANDROID_NS_URL);
// cut type before /
int typeEnd = str.indexOf('/');
if (typeEnd != -1) {
attrName = str.substring(typeEnd + 1);
} else {
attrName = str;
}
} else {
attrName = "_unknown_";
}
}

return namespace != null ? namespace + ":" + attrName : attrName;
}

private String getAttributeValue(XmlAttribute a) {
if (!a.getValue().isEmpty()) {
return a.getValue();
}
return parse(a.getCompiledItem());
}

private void decode(XmlNamespace n) {
String prefix = n.getPrefix();
String uri = n.getUri();
Expand Down Expand Up @@ -134,8 +166,7 @@ private static String generateTagAttrName() {
return sb.toString();
}

private XmlNode decodeProto(InputStream inputStream)
throws InvalidProtocolBufferException, IOException {
private XmlNode decodeProto(InputStream inputStream) throws IOException {
return XmlNode.parseFrom(XmlGenUtils.readData(inputStream));
}
}
Loading

0 comments on commit 881a716

Please sign in to comment.