Commit 9cd46e74 authored by binjia.zhou's avatar binjia.zhou Committed by skylot

fix some xml generate issues

parent 57812204
/**
* 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();
}
}
\ No newline at end of file
/**
* 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();
}
}
\ No newline at end of file
/**
* 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);
}
}
}
\ No newline at end of file
......@@ -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;
}
}
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();
if (screenWidth != 0 && screenHeight != 0) {
config.setScreenSize(screenWidth + "x" + screenHeight);
}
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));
}
is.readInt16(); // sdkVersion
is.readInt16(); // minorVersion
if (screenWidthDp != 0) {
config.setScreenWidthDp("w" + screenWidthDp + "dp");
}
is.readInt8(); // screenLayout
is.readInt8(); // uiMode
is.readInt16(); // smallestScreenWidthDp
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";
}
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 addItem(CodeWriter cw, String itemTag, String typeName, RawNamedValue value) {
String nameStr = vp.decodeNameRef(value.getNameRef());
String valueStr = vp.decodeValue(value.getRawValue());
addSimpleValue(cw, "item", keyName, keyValue, valueStr);
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) {
}
}
}
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 {
addSimpleValue(cw, typeName, itemTag, null, null, valueStr);
}
}
private void addSimpleValue(CodeWriter cw, String typeName, String attrName, String attrValue, String 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(typeName);
cw.add('<').add(itemTag);
if (attrName != null && attrValue != null) {
cw.add(' ').add(attrName).add("=\"").add(attrValue).add('"');
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('"');
}
}
cw.add('>');
if (typeName.equals("string")) {
cw.add(StringUtils.escapeResStrValue(valueStr));
if (valueStr.equals("")) {
cw.add(" />");
} else {
cw.add(StringUtils.escapeResValue(valueStr));
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;
}
}
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);
}
......
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