fix some xml generate issues
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* Copyright 2014 Ryszard Wiśniewski <brut.alll@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package jadx.core.utils.android;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
|
||||
*/
|
||||
abstract public class DataInputDelegate implements DataInput {
|
||||
protected final DataInput mDelegate;
|
||||
|
||||
public DataInputDelegate(DataInput delegate) {
|
||||
this.mDelegate = delegate;
|
||||
}
|
||||
|
||||
public int skipBytes(int n) throws IOException {
|
||||
return mDelegate.skipBytes(n);
|
||||
}
|
||||
|
||||
public int readUnsignedShort() throws IOException {
|
||||
return mDelegate.readUnsignedShort();
|
||||
}
|
||||
|
||||
public int readUnsignedByte() throws IOException {
|
||||
return mDelegate.readUnsignedByte();
|
||||
}
|
||||
|
||||
public String readUTF() throws IOException {
|
||||
return mDelegate.readUTF();
|
||||
}
|
||||
|
||||
public short readShort() throws IOException {
|
||||
return mDelegate.readShort();
|
||||
}
|
||||
|
||||
public long readLong() throws IOException {
|
||||
return mDelegate.readLong();
|
||||
}
|
||||
|
||||
public String readLine() throws IOException {
|
||||
return mDelegate.readLine();
|
||||
}
|
||||
|
||||
public int readInt() throws IOException {
|
||||
return mDelegate.readInt();
|
||||
}
|
||||
|
||||
public void readFully(byte[] b, int off, int len) throws IOException {
|
||||
mDelegate.readFully(b, off, len);
|
||||
}
|
||||
|
||||
public void readFully(byte[] b) throws IOException {
|
||||
mDelegate.readFully(b);
|
||||
}
|
||||
|
||||
public float readFloat() throws IOException {
|
||||
return mDelegate.readFloat();
|
||||
}
|
||||
|
||||
public double readDouble() throws IOException {
|
||||
return mDelegate.readDouble();
|
||||
}
|
||||
|
||||
public char readChar() throws IOException {
|
||||
return mDelegate.readChar();
|
||||
}
|
||||
|
||||
public byte readByte() throws IOException {
|
||||
return mDelegate.readByte();
|
||||
}
|
||||
|
||||
public boolean readBoolean() throws IOException {
|
||||
return mDelegate.readBoolean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* Copyright 2014 Ryszard Wiśniewski <brut.alll@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package jadx.core.utils.android;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
|
||||
*/
|
||||
public class ExtDataInput extends DataInputDelegate {
|
||||
public ExtDataInput(InputStream in) {
|
||||
this((DataInput) new DataInputStream(in));
|
||||
}
|
||||
|
||||
public ExtDataInput(DataInput delegate) {
|
||||
super(delegate);
|
||||
}
|
||||
|
||||
public int[] readIntArray(int length) throws IOException {
|
||||
int[] array = new int[length];
|
||||
for(int i = 0; i < length; i++) {
|
||||
array[i] = readInt();
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
public void skipInt() throws IOException {
|
||||
skipBytes(4);
|
||||
}
|
||||
|
||||
public void skipCheckInt(int expected) throws IOException {
|
||||
int got = readInt();
|
||||
if (got != expected) {
|
||||
throw new IOException(String.format(
|
||||
"Expected: 0x%08x, got: 0x%08x", expected, got));
|
||||
}
|
||||
}
|
||||
|
||||
public void skipCheckShort(short expected) throws IOException {
|
||||
short got = readShort();
|
||||
if (got != expected) {
|
||||
throw new IOException(String.format(
|
||||
"Expected: 0x%08x, got: 0x%08x", expected, got));
|
||||
}
|
||||
}
|
||||
|
||||
public void skipCheckByte(byte expected) throws IOException {
|
||||
byte got = readByte();
|
||||
if (got != expected) {
|
||||
throw new IOException(String.format(
|
||||
"Expected: 0x%08x, got: 0x%08x", expected, got));
|
||||
}
|
||||
}
|
||||
|
||||
public void skipCheckChunkTypeInt(int expected, int possible) throws IOException {
|
||||
int got = readInt();
|
||||
|
||||
if (got == possible) {
|
||||
skipCheckChunkTypeInt(expected, -1);
|
||||
} else if (got != expected) {
|
||||
throw new IOException(String.format("Expected: 0x%08x, got: 0x%08x", expected, got));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The general contract of DataInput doesn't guarantee all the bytes requested will be skipped
|
||||
* and failure can occur for many reasons. We override this to try harder to skip all the bytes
|
||||
* requested (this is similar to DataInputStream's wrapper).
|
||||
*/
|
||||
public final int skipBytes(int n) throws IOException {
|
||||
int total = 0;
|
||||
int cur = 0;
|
||||
|
||||
while ((total < n) && ((cur = (int) super.skipBytes(n - total)) > 0)) {
|
||||
total += cur;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
public String readNullEndedString(int length, boolean fixed)
|
||||
throws IOException {
|
||||
StringBuilder string = new StringBuilder(16);
|
||||
while(length-- != 0) {
|
||||
short ch = readShort();
|
||||
if (ch == 0) {
|
||||
break;
|
||||
}
|
||||
string.append((char) ch);
|
||||
}
|
||||
if (fixed) {
|
||||
skipBytes(length * 2);
|
||||
}
|
||||
|
||||
return string.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* Copyright 2014 Ryszard Wiśniewski <brut.alll@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package jadx.core.utils.android;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInput;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
/**
|
||||
* @author Ryszard Wiśniewski <brut.alll@gmail.com>
|
||||
*/
|
||||
public class Res9patchStreamDecoder {
|
||||
|
||||
public void decode(InputStream in, OutputStream out) throws JadxException {
|
||||
try {
|
||||
byte[] data = IOUtils.toByteArray(in);
|
||||
|
||||
BufferedImage im = ImageIO.read(new ByteArrayInputStream(data));
|
||||
int w = im.getWidth(), h = im.getHeight();
|
||||
|
||||
BufferedImage im2 = new BufferedImage(w+2, h+2, BufferedImage.TYPE_INT_ARGB);
|
||||
im2.createGraphics().drawImage(im, 1, 1, w, h, null);
|
||||
|
||||
NinePatch np = getNinePatch(data);
|
||||
drawHLine(im2, h + 1, np.padLeft + 1, w - np.padRight);
|
||||
drawVLine(im2, w + 1, np.padTop + 1, h - np.padBottom);
|
||||
|
||||
int[] xDivs = np.xDivs;
|
||||
for (int i = 0; i < xDivs.length; i += 2) {
|
||||
drawHLine(im2, 0, xDivs[i] + 1, xDivs[i + 1]);
|
||||
}
|
||||
|
||||
int[] yDivs = np.yDivs;
|
||||
for (int i = 0; i < yDivs.length; i += 2) {
|
||||
drawVLine(im2, 0, yDivs[i] + 1, yDivs[i + 1]);
|
||||
}
|
||||
|
||||
ImageIO.write(im2, "png", out);
|
||||
} catch (IOException | NullPointerException ex) {
|
||||
throw new JadxException(ex.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private NinePatch getNinePatch(byte[] data) throws JadxException,
|
||||
IOException {
|
||||
ExtDataInput di = new ExtDataInput(new ByteArrayInputStream(data));
|
||||
find9patchChunk(di);
|
||||
return NinePatch.decode(di);
|
||||
}
|
||||
|
||||
private void find9patchChunk(DataInput di) throws JadxException,
|
||||
IOException {
|
||||
di.skipBytes(8);
|
||||
while (true) {
|
||||
int size;
|
||||
try {
|
||||
size = di.readInt();
|
||||
} catch (IOException ex) {
|
||||
throw new JadxException("Cant find nine patch chunk", ex);
|
||||
}
|
||||
if (di.readInt() == NP_CHUNK_TYPE) {
|
||||
return;
|
||||
}
|
||||
di.skipBytes(size + 4);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawHLine(BufferedImage im, int y, int x1, int x2) {
|
||||
for (int x = x1; x <= x2; x++) {
|
||||
im.setRGB(x, y, NP_COLOR);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawVLine(BufferedImage im, int x, int y1, int y2) {
|
||||
for (int y = y1; y <= y2; y++) {
|
||||
im.setRGB(x, y, NP_COLOR);
|
||||
}
|
||||
}
|
||||
|
||||
private static final int NP_CHUNK_TYPE = 0x6e705463; // npTc
|
||||
private static final int NP_COLOR = 0xff000000;
|
||||
|
||||
private static class NinePatch {
|
||||
public final int padLeft, padRight, padTop, padBottom;
|
||||
public final int[] xDivs, yDivs;
|
||||
|
||||
public NinePatch(int padLeft, int padRight, int padTop, int padBottom,
|
||||
int[] xDivs, int[] yDivs) {
|
||||
this.padLeft = padLeft;
|
||||
this.padRight = padRight;
|
||||
this.padTop = padTop;
|
||||
this.padBottom = padBottom;
|
||||
this.xDivs = xDivs;
|
||||
this.yDivs = yDivs;
|
||||
}
|
||||
|
||||
public static NinePatch decode(ExtDataInput di) throws IOException {
|
||||
di.skipBytes(1);
|
||||
byte numXDivs = di.readByte();
|
||||
byte numYDivs = di.readByte();
|
||||
di.skipBytes(1);
|
||||
di.skipBytes(8);
|
||||
int padLeft = di.readInt();
|
||||
int padRight = di.readInt();
|
||||
int padTop = di.readInt();
|
||||
int padBottom = di.readInt();
|
||||
di.skipBytes(4);
|
||||
int[] xDivs = di.readIntArray(numXDivs);
|
||||
int[] yDivs = di.readIntArray(numYDivs);
|
||||
|
||||
return new NinePatch(padLeft, padRight, padTop, padBottom, xDivs,
|
||||
yDivs);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import jadx.core.dex.info.ConstStorage;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.xmlgen.entry.ValuesParser;
|
||||
|
||||
@@ -35,27 +34,28 @@ public class BinaryXMLParser extends CommonBinaryParser {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(BinaryXMLParser.class);
|
||||
private static final String ANDROID_R_STYLE_CLS = "android.R$style";
|
||||
private static final boolean ATTR_NEW_LINE = false;
|
||||
|
||||
private final Map<Integer, String> styleMap = new HashMap<Integer, String>();
|
||||
private final Map<Integer, FieldNode> localStyleMap = new HashMap<Integer, FieldNode>();
|
||||
private final Map<Integer, String> resNames;
|
||||
private final Map<String, String> nsMap = new HashMap<>();
|
||||
private CodeWriter writer;
|
||||
private String[] strings;
|
||||
|
||||
private String nsPrefix = "ERROR";
|
||||
private String nsURI = "ERROR";
|
||||
private String currentTag = "ERROR";
|
||||
|
||||
private boolean firstElement;
|
||||
private boolean wasOneLiner = false;
|
||||
|
||||
private final Map<Integer, String> styleMap = new HashMap<>();
|
||||
private final Map<Integer, FieldNode> localStyleMap = new HashMap<>();
|
||||
private final Map<Integer, String> resNames;
|
||||
private ValuesParser valuesParser;
|
||||
|
||||
private final ManifestAttributes attributes;
|
||||
private boolean isLastEnd = true;
|
||||
private boolean isOneLine = true;
|
||||
|
||||
public BinaryXMLParser(RootNode root) {
|
||||
try {
|
||||
loadStyles();
|
||||
try {
|
||||
Class<?> rStyleCls = Class.forName(ANDROID_R_STYLE_CLS);
|
||||
for (Field f : rStyleCls.getFields()) {
|
||||
styleMap.put(f.getInt(f.getType()), f.getName());
|
||||
}
|
||||
} catch (Throwable th) {
|
||||
LOG.error("R class loading failed", th);
|
||||
}
|
||||
// add application constants
|
||||
ConstStorage constStorage = root.getConstValues();
|
||||
Map<Object, FieldNode> constFields = constStorage.getGlobalConstFields();
|
||||
@@ -67,25 +67,11 @@ public class BinaryXMLParser extends CommonBinaryParser {
|
||||
}
|
||||
}
|
||||
resNames = constStorage.getResourcesNames();
|
||||
|
||||
attributes = new ManifestAttributes();
|
||||
attributes.parseAll();
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("BinaryXMLParser init error", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadStyles() {
|
||||
try {
|
||||
Class<?> rStyleCls = Class.forName(ANDROID_R_STYLE_CLS);
|
||||
for (Field f : rStyleCls.getFields()) {
|
||||
styleMap.put(f.getInt(f.getType()), f.getName());
|
||||
}
|
||||
} catch (Exception th) {
|
||||
LOG.error("R class loading failed", th);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized CodeWriter parse(InputStream inputStream) throws IOException {
|
||||
is = new ParserStream(inputStream);
|
||||
if (!isBinaryXml()) {
|
||||
@@ -126,12 +112,14 @@ public class BinaryXMLParser extends CommonBinaryParser {
|
||||
parseResourceMap();
|
||||
break;
|
||||
case RES_XML_START_NAMESPACE_TYPE:
|
||||
case RES_XML_END_NAMESPACE_TYPE:
|
||||
parseNameSpace();
|
||||
break;
|
||||
case RES_XML_CDATA_TYPE:
|
||||
parseCData();
|
||||
break;
|
||||
case RES_XML_END_NAMESPACE_TYPE:
|
||||
parseNameSpaceEnd();
|
||||
break;
|
||||
case RES_XML_START_ELEMENT_TYPE:
|
||||
parseElement();
|
||||
break;
|
||||
@@ -164,12 +152,25 @@ public class BinaryXMLParser extends CommonBinaryParser {
|
||||
if (is.readInt32() != 0x18) {
|
||||
die("NAMESPACE header chunk is not 0x18 big");
|
||||
}
|
||||
int lineNumber = is.readInt32();
|
||||
int beginLineNumber = is.readInt32();
|
||||
int comment = is.readInt32();
|
||||
int idPrefix = is.readInt32();
|
||||
nsPrefix = strings[idPrefix];
|
||||
int idURI = is.readInt32();
|
||||
nsURI = strings[idURI];
|
||||
int beginPrefix = is.readInt32();
|
||||
int beginURI = is.readInt32();
|
||||
nsMap.computeIfAbsent(strings[beginURI], k -> strings[beginPrefix]);
|
||||
}
|
||||
|
||||
private void parseNameSpaceEnd() throws IOException {
|
||||
if (is.readInt16() != 0x10) {
|
||||
die("NAMESPACE header is not 0x0010");
|
||||
}
|
||||
if (is.readInt32() != 0x18) {
|
||||
die("NAMESPACE header chunk is not 0x18 big");
|
||||
}
|
||||
int endLineNumber = is.readInt32();
|
||||
int comment = is.readInt32();
|
||||
int endPrefix = is.readInt32();
|
||||
int endURI = is.readInt32();
|
||||
nsMap.computeIfAbsent(strings[endURI], k -> strings[endPrefix]);
|
||||
}
|
||||
|
||||
private void parseCData() throws IOException {
|
||||
@@ -185,11 +186,13 @@ public class BinaryXMLParser extends CommonBinaryParser {
|
||||
int strIndex = is.readInt32();
|
||||
String str = strings[strIndex];
|
||||
|
||||
writer.startLine().addIndent();
|
||||
writer.attachSourceLine(lineNumber);
|
||||
writer.add(StringUtils.escapeXML(str.trim())); // TODO: wrap into CDATA for easier reading
|
||||
|
||||
long size = is.readInt16();
|
||||
//TODO: what's this for?
|
||||
/*writer.startLine().addIndent();
|
||||
writer.attachSourceLine(lineNumber);
|
||||
writer.add(StringUtils.escapeXML(str.trim()));*/
|
||||
|
||||
int size = is.readInt16();
|
||||
is.skip(size - 2);
|
||||
}
|
||||
|
||||
@@ -208,11 +211,11 @@ public class BinaryXMLParser extends CommonBinaryParser {
|
||||
int comment = is.readInt32();
|
||||
int startNS = is.readInt32();
|
||||
int startNSName = is.readInt32(); // actually is elementName...
|
||||
if (!wasOneLiner && !"ERROR".equals(currentTag)
|
||||
&& !currentTag.equals(strings[startNSName])) {
|
||||
if (!isLastEnd && !"ERROR".equals(currentTag)) {
|
||||
writer.add(">");
|
||||
}
|
||||
wasOneLiner = false;
|
||||
isOneLine = true;
|
||||
isLastEnd = false;
|
||||
currentTag = strings[startNSName];
|
||||
writer.startLine("<").add(currentTag);
|
||||
writer.attachSourceLine(elementBegLineNumber);
|
||||
@@ -229,9 +232,11 @@ public class BinaryXMLParser extends CommonBinaryParser {
|
||||
int classIndex = is.readInt16();
|
||||
int styleIndex = is.readInt16();
|
||||
if ("manifest".equals(currentTag) || writer.getIndent() == 0) {
|
||||
writer.add(" xmlns:android=\"").add(nsURI).add("\"");
|
||||
for (Map.Entry<String, String> entry : nsMap.entrySet()) {
|
||||
writer.add(" xmlns:" + entry.getValue() + "=\"").add(entry.getKey()).add("\"");
|
||||
}
|
||||
}
|
||||
boolean attrNewLine = attributeCount != 1 && ATTR_NEW_LINE;
|
||||
boolean attrNewLine = attributeCount == 1 ? false : ATTR_NEW_LINE;
|
||||
for (int i = 0; i < attributeCount; i++) {
|
||||
parseAttribute(i, attrNewLine);
|
||||
}
|
||||
@@ -258,10 +263,10 @@ public class BinaryXMLParser extends CommonBinaryParser {
|
||||
writer.add(' ');
|
||||
}
|
||||
if (attributeNS != -1) {
|
||||
writer.add(nsPrefix).add(':');
|
||||
writer.add(nsMap.get(strings[attributeNS])).add(':');
|
||||
}
|
||||
writer.add(attrName).add("=\"");
|
||||
String decodedAttr = attributes.decode(attrName, attrValData);
|
||||
String decodedAttr = ManifestAttributes.getInstance().decode(attrName, attrValData);
|
||||
if (decodedAttr != null) {
|
||||
writer.add(decodedAttr);
|
||||
} else {
|
||||
@@ -275,10 +280,11 @@ public class BinaryXMLParser extends CommonBinaryParser {
|
||||
// reference custom processing
|
||||
String name = styleMap.get(attrValData);
|
||||
if (name != null) {
|
||||
writer.add("@*");
|
||||
writer.add("@");
|
||||
if (attributeNS != -1) {
|
||||
writer.add(nsPrefix).add(':');
|
||||
writer.add(nsMap.get(strings[attributeNS])).add(':');
|
||||
}
|
||||
LOG.debug("decodeAttribute: " + attributeNS + " " + name);
|
||||
writer.add("style/").add(name.replaceAll("_", "."));
|
||||
} else {
|
||||
FieldNode field = localStyleMap.get(attrValData);
|
||||
@@ -292,9 +298,20 @@ public class BinaryXMLParser extends CommonBinaryParser {
|
||||
} else {
|
||||
String resName = resNames.get(attrValData);
|
||||
if (resName != null) {
|
||||
writer.add("@").add(resName);
|
||||
writer.add("@");
|
||||
if (resName.startsWith("id/")) {
|
||||
writer.add("+");
|
||||
}
|
||||
writer.add(resName);
|
||||
} else {
|
||||
writer.add("0x").add(Integer.toHexString(attrValData));
|
||||
resName = ValuesParser.androidResMap.get(attrValData);
|
||||
if (resName != null) {
|
||||
writer.add("@android:").add(resName);
|
||||
} else if (attrValData == 0) {
|
||||
writer.add("@null");
|
||||
} else {
|
||||
writer.add("0x").add(Integer.toHexString(attrValData));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -315,9 +332,8 @@ public class BinaryXMLParser extends CommonBinaryParser {
|
||||
int comment = is.readInt32();
|
||||
int elementNS = is.readInt32();
|
||||
int elementName = is.readInt32();
|
||||
if (currentTag.equals(strings[elementName])) {
|
||||
if (currentTag.equals(strings[elementName]) && isOneLine && !isLastEnd) {
|
||||
writer.add(" />");
|
||||
wasOneLiner = true;
|
||||
} else {
|
||||
writer.startLine("</");
|
||||
writer.attachSourceLine(endLineNumber);
|
||||
@@ -326,6 +342,7 @@ public class BinaryXMLParser extends CommonBinaryParser {
|
||||
}
|
||||
writer.add(strings[elementName]).add(">");
|
||||
}
|
||||
isLastEnd = true;
|
||||
if (writer.getIndent() != 0) {
|
||||
writer.decIndent();
|
||||
}
|
||||
|
||||
@@ -49,7 +49,24 @@ public class ManifestAttributes {
|
||||
|
||||
private final Map<String, MAttr> attrMap = new HashMap<>();
|
||||
|
||||
public void parseAll() {
|
||||
private static ManifestAttributes instance;
|
||||
|
||||
public static ManifestAttributes getInstance() {
|
||||
if (instance == null) {
|
||||
try {
|
||||
instance = new ManifestAttributes();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
private ManifestAttributes() {
|
||||
parseAll();
|
||||
}
|
||||
|
||||
private void parseAll() {
|
||||
parse(loadXML(ATTR_XML));
|
||||
parse(loadXML(MANIFEST_ATTR_XML));
|
||||
LOG.debug("Loaded android attributes count: {}", attrMap.size());
|
||||
@@ -158,7 +175,10 @@ public class ManifestAttributes {
|
||||
} else if (attr.getType() == MAttrType.FLAG) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Map.Entry<Long, String> entry : attr.getValues().entrySet()) {
|
||||
if ((value & entry.getKey()) != 0) {
|
||||
if (value == entry.getKey()) {
|
||||
sb = new StringBuilder(entry.getValue() + "|");
|
||||
break;
|
||||
} else if ((value & entry.getKey()) == entry.getKey()) {
|
||||
sb.append(entry.getValue()).append('|');
|
||||
}
|
||||
}
|
||||
@@ -166,6 +186,6 @@ public class ManifestAttributes {
|
||||
return sb.deleteCharAt(sb.length() - 1).toString();
|
||||
}
|
||||
}
|
||||
return "UNKNOWN_DATA_0x" + Long.toHexString(value);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package jadx.core.xmlgen;
|
||||
|
||||
import jadx.core.codegen.CodeWriter;
|
||||
import jadx.core.utils.android.Res9patchStreamDecoder;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
@@ -37,8 +41,19 @@ public class ResContainer implements Comparable<ResContainer> {
|
||||
|
||||
public static ResContainer singleImageFile(String name, InputStream content) {
|
||||
ResContainer resContainer = new ResContainer(name, Collections.<ResContainer>emptyList());
|
||||
InputStream newContent = content;
|
||||
if (name.endsWith(".9.png")) {
|
||||
Res9patchStreamDecoder decoder = new Res9patchStreamDecoder();
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
try {
|
||||
decoder.decode(content, os);
|
||||
} catch (JadxException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
newContent = new ByteArrayInputStream(os.toByteArray());
|
||||
}
|
||||
try {
|
||||
resContainer.image = ImageIO.read(content);
|
||||
resContainer.image = ImageIO.read(newContent);
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Image load error", e);
|
||||
}
|
||||
|
||||
@@ -87,6 +87,10 @@ public class ResTableParser extends CommonBinaryParser {
|
||||
return resStorage;
|
||||
}
|
||||
|
||||
public String[] getStrings() {
|
||||
return strings;
|
||||
}
|
||||
|
||||
void decodeTableChunk() throws IOException {
|
||||
is.checkInt16(RES_TABLE_TYPE, "Not a table chunk");
|
||||
is.checkInt16(0x000c, "Unexpected table header size");
|
||||
@@ -250,29 +254,109 @@ public class ResTableParser extends CommonBinaryParser {
|
||||
int orientation = is.readInt8();
|
||||
int touchscreen = is.readInt8();
|
||||
int density = is.readInt16();
|
||||
/*
|
||||
|
||||
if (density != 0) {
|
||||
config.setDensity(parseDensity(density));
|
||||
}
|
||||
|
||||
is.readInt8(); // keyboard
|
||||
is.readInt8(); // navigation
|
||||
is.readInt8(); // inputFlags
|
||||
is.readInt8(); // inputPad0
|
||||
|
||||
is.readInt16(); // screenWidth
|
||||
is.readInt16(); // screenHeight
|
||||
int screenWidth = is.readInt16();
|
||||
int screenHeight = is.readInt16();
|
||||
|
||||
is.readInt16(); // sdkVersion
|
||||
is.readInt16(); // minorVersion
|
||||
if (screenWidth != 0 && screenHeight != 0) {
|
||||
config.setScreenSize(screenWidth + "x" + screenHeight);
|
||||
}
|
||||
|
||||
is.readInt8(); // screenLayout
|
||||
is.readInt8(); // uiMode
|
||||
is.readInt16(); // smallestScreenWidthDp
|
||||
int sdkVersion = is.readInt16();
|
||||
|
||||
if (sdkVersion != 0) {
|
||||
config.setSdkVersion("v" + sdkVersion);
|
||||
}
|
||||
|
||||
int minorVersion = is.readInt16();
|
||||
|
||||
int screenLayout = is.readInt8();
|
||||
int uiMode = is.readInt8();
|
||||
int smallestScreenWidthDp = is.readInt16();
|
||||
|
||||
int screenWidthDp = is.readInt16();
|
||||
int screenHeightDp = is.readInt16();
|
||||
|
||||
if (screenLayout != 0) {
|
||||
config.setScreenLayout(parseScreenLayout(screenLayout));
|
||||
}
|
||||
|
||||
if (smallestScreenWidthDp != 0) {
|
||||
config.setSmallestScreenWidthDp("sw" + smallestScreenWidthDp + "dp");
|
||||
}
|
||||
|
||||
if (orientation != 0) {
|
||||
config.setOrientation(parseOrientation(orientation));
|
||||
}
|
||||
|
||||
if (screenWidthDp != 0) {
|
||||
config.setScreenWidthDp("w" + screenWidthDp + "dp");
|
||||
}
|
||||
|
||||
if (screenHeightDp != 0) {
|
||||
config.setScreenHeightDp("h" + screenHeightDp + "dp");
|
||||
}
|
||||
|
||||
is.readInt16(); // screenWidthDp
|
||||
is.readInt16(); // screenHeightDp
|
||||
*/
|
||||
is.skipToPos(start + size, "Skip config parsing");
|
||||
return config;
|
||||
}
|
||||
|
||||
private String parseOrientation(int orientation) {
|
||||
if (orientation == 1) {
|
||||
return "port";
|
||||
} else if (orientation == 2) {
|
||||
return "land";
|
||||
} else {
|
||||
return "o" + orientation;
|
||||
}
|
||||
}
|
||||
|
||||
private String parseScreenLayout(int screenLayout) {
|
||||
switch (screenLayout) {
|
||||
case 1:
|
||||
return "small";
|
||||
case 2:
|
||||
return "normal";
|
||||
case 3:
|
||||
return "large";
|
||||
case 4:
|
||||
return "xlarge";
|
||||
case 64:
|
||||
return "ldltr";
|
||||
case 128:
|
||||
return "ldrtl";
|
||||
default:
|
||||
return "sl" + screenLayout;
|
||||
}
|
||||
}
|
||||
|
||||
private String parseDensity(int density) {
|
||||
if (density == 120) {
|
||||
return "ldpi";
|
||||
} else if (density == 160) {
|
||||
return "mdpi";
|
||||
} else if (density == 240) {
|
||||
return "hdpi";
|
||||
} else if (density == 320) {
|
||||
return "xhdpi";
|
||||
} else if (density == 480) {
|
||||
return "xxhdpi";
|
||||
} else if (density == 640) {
|
||||
return "xxxhdpi";
|
||||
} else {
|
||||
return density + "dpi";
|
||||
}
|
||||
}
|
||||
|
||||
private String parseLocale() throws IOException {
|
||||
int b1 = is.readInt8();
|
||||
int b2 = is.readInt8();
|
||||
|
||||
@@ -6,14 +6,7 @@ import jadx.core.xmlgen.entry.RawNamedValue;
|
||||
import jadx.core.xmlgen.entry.ResourceEntry;
|
||||
import jadx.core.xmlgen.entry.ValuesParser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
|
||||
public class ResXmlGen {
|
||||
|
||||
@@ -66,47 +59,127 @@ public class ResXmlGen {
|
||||
private void addValue(CodeWriter cw, ResourceEntry ri) {
|
||||
if (ri.getSimpleValue() != null) {
|
||||
String valueStr = vp.decodeValue(ri.getSimpleValue());
|
||||
addSimpleValue(cw, ri.getTypeName(), "name", ri.getKeyName(), valueStr);
|
||||
addSimpleValue(cw, ri.getTypeName(), ri.getTypeName(), "name", ri.getKeyName(), valueStr);
|
||||
} else {
|
||||
cw.startLine();
|
||||
cw.add('<').add(ri.getTypeName()).add(' ');
|
||||
cw.add("name=\"").add(ri.getKeyName()).add("\">");
|
||||
String itemTag = "item";
|
||||
if (ri.getTypeName().equals("attr") && ri.getNamedValues().size() > 0) {
|
||||
cw.add("name=\"").add(ri.getKeyName());
|
||||
int type = ri.getNamedValues().get(0).getRawValue().getData();
|
||||
if ((type & ValuesParser.ATTR_TYPE_ENUM) != 0) {
|
||||
itemTag = "enum";
|
||||
} else if ((type & ValuesParser.ATTR_TYPE_FLAGS) != 0) {
|
||||
itemTag = "flag";
|
||||
}
|
||||
String formatValue = getTypeAsString(type);
|
||||
if (formatValue != null) {
|
||||
cw.add("\" format=\"").add(formatValue);
|
||||
}
|
||||
cw.add("\">");
|
||||
} else {
|
||||
cw.add("name=\"").add(ri.getKeyName()).add("\">");
|
||||
}
|
||||
cw.incIndent();
|
||||
for (RawNamedValue value : ri.getNamedValues()) {
|
||||
addItem(cw, value);
|
||||
addItem(cw, itemTag, ri.getTypeName(), value);
|
||||
}
|
||||
cw.decIndent();
|
||||
cw.startLine().add("</").add(ri.getTypeName()).add('>');
|
||||
}
|
||||
}
|
||||
|
||||
private void addItem(CodeWriter cw, RawNamedValue value) {
|
||||
String keyName = null;
|
||||
String keyValue = null;
|
||||
int nameRef = value.getNameRef();
|
||||
if (ParserConstants.isResInternalId(nameRef)) {
|
||||
keyValue = ParserConstants.PLURALS_MAP.get(nameRef);
|
||||
if (keyValue != null) {
|
||||
keyName = "quantity";
|
||||
}
|
||||
private String getTypeAsString(int type) {
|
||||
String s = "";
|
||||
if ((type & ValuesParser.ATTR_TYPE_REFERENCE) != 0) {
|
||||
s += "|reference";
|
||||
}
|
||||
String valueStr = vp.decodeValue(value.getRawValue());
|
||||
addSimpleValue(cw, "item", keyName, keyValue, valueStr);
|
||||
if ((type & ValuesParser.ATTR_TYPE_STRING) != 0) {
|
||||
s += "|string";
|
||||
}
|
||||
if ((type & ValuesParser.ATTR_TYPE_INTEGER) != 0) {
|
||||
s += "|integer";
|
||||
}
|
||||
if ((type & ValuesParser.ATTR_TYPE_BOOLEAN) != 0) {
|
||||
s += "|boolean";
|
||||
}
|
||||
if ((type & ValuesParser.ATTR_TYPE_COLOR) != 0) {
|
||||
s += "|color";
|
||||
}
|
||||
if ((type & ValuesParser.ATTR_TYPE_FLOAT) != 0) {
|
||||
s += "|float";
|
||||
}
|
||||
if ((type & ValuesParser.ATTR_TYPE_DIMENSION) != 0) {
|
||||
s += "|dimension";
|
||||
}
|
||||
if ((type & ValuesParser.ATTR_TYPE_FRACTION) != 0) {
|
||||
s += "|fraction";
|
||||
}
|
||||
if (s.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return s.substring(1);
|
||||
}
|
||||
|
||||
private void addSimpleValue(CodeWriter cw, String typeName, String attrName, String attrValue, String valueStr) {
|
||||
cw.startLine();
|
||||
cw.add('<').add(typeName);
|
||||
if (attrName != null && attrValue != null) {
|
||||
cw.add(' ').add(attrName).add("=\"").add(attrValue).add('"');
|
||||
|
||||
private void addItem(CodeWriter cw, String itemTag, String typeName, RawNamedValue value) {
|
||||
String nameStr = vp.decodeNameRef(value.getNameRef());
|
||||
String valueStr = vp.decodeValue(value.getRawValue());
|
||||
if (!typeName.equals("attr")) {
|
||||
if (valueStr.equals("0")) {
|
||||
valueStr = "@null";
|
||||
}
|
||||
if (nameStr != null) {
|
||||
try {
|
||||
int intVal = Integer.parseInt(valueStr);
|
||||
String newVal = ManifestAttributes.getInstance().decode(nameStr.replace("android:attr.", ""), intVal);
|
||||
if (newVal != null) {
|
||||
valueStr = newVal;
|
||||
}
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
cw.add('>');
|
||||
if (typeName.equals("string")) {
|
||||
cw.add(StringUtils.escapeResStrValue(valueStr));
|
||||
if (typeName.equals("attr")) {
|
||||
if (nameStr != null) {
|
||||
addSimpleValue(cw, typeName, itemTag, nameStr, valueStr, "");
|
||||
}
|
||||
} else if (typeName.equals("style")) {
|
||||
if (nameStr != null) {
|
||||
addSimpleValue(cw, typeName, itemTag, nameStr, "", valueStr);
|
||||
}
|
||||
} else {
|
||||
cw.add(StringUtils.escapeResValue(valueStr));
|
||||
addSimpleValue(cw, typeName, itemTag, null, null, valueStr);
|
||||
}
|
||||
}
|
||||
|
||||
private void addSimpleValue(CodeWriter cw, String typeName, String itemTag, String attrName, String attrValue, String valueStr) {
|
||||
if (valueStr.startsWith("res/")) {
|
||||
// remove duplicated resources.
|
||||
return;
|
||||
}
|
||||
cw.startLine();
|
||||
cw.add('<').add(itemTag);
|
||||
if (attrName != null && attrValue != null) {
|
||||
if (typeName.equals("attr")) {
|
||||
cw.add(' ').add("name=\"").add(attrName.replace("id.", "")).add("\" value=\"").add(attrValue).add("\"");
|
||||
} else if (typeName.equals("style")) {
|
||||
cw.add(' ').add("name=\"").add(attrName.replace("attr.", "")).add("\"");
|
||||
} else {
|
||||
cw.add(' ').add(attrName).add("=\"").add(attrValue).add('"');
|
||||
}
|
||||
}
|
||||
if (valueStr.equals("")) {
|
||||
cw.add(" />");
|
||||
} else {
|
||||
cw.add('>');
|
||||
if (itemTag.equals("string")) {
|
||||
cw.add(StringUtils.escapeResStrValue(valueStr));
|
||||
} else {
|
||||
cw.add(StringUtils.escapeResValue(valueStr));
|
||||
}
|
||||
cw.add("</").add(itemTag).add('>');
|
||||
}
|
||||
cw.add("</").add(typeName).add('>');
|
||||
}
|
||||
|
||||
private String getFileName(ResourceEntry ri) {
|
||||
|
||||
@@ -3,34 +3,93 @@ package jadx.core.xmlgen.entry;
|
||||
public class EntryConfig {
|
||||
private String language;
|
||||
private String country;
|
||||
|
||||
public void setLanguage(String language) {
|
||||
this.language = language;
|
||||
}
|
||||
private String density;
|
||||
private String screenSize;
|
||||
private String sdkVersion;
|
||||
private String screenLayout;
|
||||
private String smallestScreenWidthDp;
|
||||
private String orientation;
|
||||
private String screenWidthDp;
|
||||
private String screenHeightDp;
|
||||
|
||||
public String getLanguage() {
|
||||
return language;
|
||||
}
|
||||
|
||||
public void setCountry(String country) {
|
||||
this.country = country;
|
||||
public void setLanguage(String language) {
|
||||
this.language = language;
|
||||
}
|
||||
|
||||
public String getCountry() {
|
||||
return country;
|
||||
}
|
||||
|
||||
public void setCountry(String country) {
|
||||
this.country = country;
|
||||
}
|
||||
|
||||
public String getLocale() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (screenSize != null) {
|
||||
if (sb.length() != 0) {
|
||||
sb.append("-");
|
||||
}
|
||||
sb.append(screenSize);
|
||||
} else if (screenHeightDp != null) {
|
||||
if (sb.length() != 0) {
|
||||
sb.append("-");
|
||||
}
|
||||
sb.append(screenHeightDp);
|
||||
} else if (screenWidthDp != null) {
|
||||
if (sb.length() != 0) {
|
||||
sb.append("-");
|
||||
}
|
||||
sb.append(screenWidthDp);
|
||||
} else if (screenLayout != null) {
|
||||
if (sb.length() != 0) {
|
||||
sb.append("-");
|
||||
}
|
||||
sb.append(screenLayout);
|
||||
} else if (smallestScreenWidthDp != null) {
|
||||
if (sb.length() != 0) {
|
||||
sb.append("-");
|
||||
}
|
||||
sb.append(smallestScreenWidthDp);
|
||||
} else if (density != null) {
|
||||
sb.append(density);
|
||||
}
|
||||
if (language != null) {
|
||||
if (sb.length() != 0) {
|
||||
sb.append("-");
|
||||
}
|
||||
sb.append(language);
|
||||
}
|
||||
if (country != null) {
|
||||
sb.append("-r").append(country);
|
||||
}
|
||||
if (orientation != null) {
|
||||
if (sb.length() != 0) {
|
||||
sb.append("-");
|
||||
}
|
||||
sb.append(orientation);
|
||||
}
|
||||
if (sdkVersion != null) {
|
||||
if (sb.length() != 0) {
|
||||
sb.append("-");
|
||||
}
|
||||
sb.append(sdkVersion);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String getDensity() {
|
||||
return density;
|
||||
}
|
||||
|
||||
public void setDensity(String density) {
|
||||
this.density = density;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
@@ -41,4 +100,60 @@ public class EntryConfig {
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public void setScreenSize(String screenSize) {
|
||||
this.screenSize = screenSize;
|
||||
}
|
||||
|
||||
public String getScreenSize() {
|
||||
return screenSize;
|
||||
}
|
||||
|
||||
public void setSdkVersion(String sdkVersion) {
|
||||
this.sdkVersion = sdkVersion;
|
||||
}
|
||||
|
||||
public String getSdkVersion() {
|
||||
return sdkVersion;
|
||||
}
|
||||
|
||||
public void setScreenLayout(String screenLayout) {
|
||||
this.screenLayout = screenLayout;
|
||||
}
|
||||
|
||||
public String getScreenLayout() {
|
||||
return screenLayout;
|
||||
}
|
||||
|
||||
public void setSmallestScreenWidthDp(String smallestScreenWidthDp) {
|
||||
this.smallestScreenWidthDp = smallestScreenWidthDp;
|
||||
}
|
||||
|
||||
public String getSmallestScreenWidthDp() {
|
||||
return smallestScreenWidthDp;
|
||||
}
|
||||
|
||||
public void setOrientation(String orientation) {
|
||||
this.orientation = orientation;
|
||||
}
|
||||
|
||||
public String getOrientation() {
|
||||
return orientation;
|
||||
}
|
||||
|
||||
public void setScreenWidthDp(String screenWidthDp) {
|
||||
this.screenWidthDp = screenWidthDp;
|
||||
}
|
||||
|
||||
public String getScreenWidthDp() {
|
||||
return screenWidthDp;
|
||||
}
|
||||
|
||||
public void setScreenHeightDp(String screenHeightDp) {
|
||||
this.screenHeightDp = screenHeightDp;
|
||||
}
|
||||
|
||||
public String getScreenHeightDp() {
|
||||
return screenHeightDp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
package jadx.core.xmlgen.entry;
|
||||
|
||||
import jadx.core.xmlgen.ParserConstants;
|
||||
import jadx.core.xmlgen.ResTableParser;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -16,9 +20,28 @@ public class ValuesParser extends ParserConstants {
|
||||
private final String[] strings;
|
||||
private final Map<Integer, String> resMap;
|
||||
|
||||
public static String[] androidStrings;
|
||||
public static Map<Integer, String> androidResMap;
|
||||
|
||||
public ValuesParser(String[] strings, Map<Integer, String> resMap) {
|
||||
this.strings = strings;
|
||||
this.resMap = resMap;
|
||||
|
||||
if (androidStrings == null && androidResMap == null) {
|
||||
try {
|
||||
decodeAndroid();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void decodeAndroid() throws IOException {
|
||||
InputStream inputStream = new BufferedInputStream(getClass().getResourceAsStream("/resources.arsc"));
|
||||
ResTableParser androidParser = new ResTableParser();
|
||||
androidParser.decode(inputStream);
|
||||
androidStrings = androidParser.getStrings();
|
||||
androidResMap = androidParser.getResStorage().getResourcesNames();
|
||||
}
|
||||
|
||||
public String getValueString(ResourceEntry ri) {
|
||||
@@ -73,6 +96,11 @@ public class ValuesParser extends ParserConstants {
|
||||
case TYPE_REFERENCE: {
|
||||
String ri = resMap.get(data);
|
||||
if (ri == null) {
|
||||
String androidRi = androidResMap.get(data);
|
||||
if (androidRi != null) {
|
||||
return "@android:" + androidRi;
|
||||
}
|
||||
if (data == 0) return "0";
|
||||
return "?unknown_ref: " + Integer.toHexString(data);
|
||||
}
|
||||
return "@" + ri;
|
||||
@@ -81,6 +109,10 @@ public class ValuesParser extends ParserConstants {
|
||||
case TYPE_ATTRIBUTE: {
|
||||
String ri = resMap.get(data);
|
||||
if (ri == null) {
|
||||
String androidRi = androidResMap.get(data);
|
||||
if (androidRi != null) {
|
||||
return "?android:" + androidRi;
|
||||
}
|
||||
return "?unknown_attr_ref: " + Integer.toHexString(data);
|
||||
}
|
||||
return "?" + ri;
|
||||
@@ -97,7 +129,7 @@ public class ValuesParser extends ParserConstants {
|
||||
}
|
||||
}
|
||||
|
||||
private String decodeNameRef(int nameRef) {
|
||||
public String decodeNameRef(int nameRef) {
|
||||
int ref = nameRef;
|
||||
if (isResInternalId(nameRef)) {
|
||||
ref = nameRef & ATTR_TYPE_ANY;
|
||||
@@ -108,6 +140,11 @@ public class ValuesParser extends ParserConstants {
|
||||
String ri = resMap.get(ref);
|
||||
if (ri != null) {
|
||||
return ri.replace('/', '.');
|
||||
} else {
|
||||
String androidRi = androidResMap.get(ref);
|
||||
if (androidRi != null) {
|
||||
return "android:" + androidRi.replace('/', '.');
|
||||
}
|
||||
}
|
||||
return "?0x" + Integer.toHexString(nameRef);
|
||||
}
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user