* Parse strings only when they are referenced * Fix style
This commit is contained in:
@@ -45,7 +45,7 @@ public class BinaryXMLParser extends CommonBinaryParser {
|
||||
private final Map<String, String> tagAttrDeobfNames = new HashMap<>();
|
||||
|
||||
private ICodeWriter writer;
|
||||
private String[] strings;
|
||||
private BinaryXMLStrings strings;
|
||||
private String currentTag = "ERROR";
|
||||
private boolean firstElement;
|
||||
private ValuesParser valuesParser;
|
||||
@@ -387,8 +387,8 @@ public class BinaryXMLParser extends CommonBinaryParser {
|
||||
}
|
||||
|
||||
private String getString(int strId) {
|
||||
if (0 <= strId && strId < strings.length) {
|
||||
return strings[strId];
|
||||
if (0 <= strId && strId < strings.size()) {
|
||||
return strings.get(strId);
|
||||
}
|
||||
return "NOT_FOUND_STR_0x" + Integer.toHexString(strId);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
package jadx.core.xmlgen;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class BinaryXMLStrings {
|
||||
private final int stringCount;
|
||||
|
||||
private final long stringsStart;
|
||||
|
||||
private final ByteBuffer buffer;
|
||||
|
||||
private final boolean isUtf8;
|
||||
|
||||
// This cache include strings that have been overridden by the deobfuscator.
|
||||
private final Map<Integer, String> cache = new HashMap<>();
|
||||
|
||||
public BinaryXMLStrings() {
|
||||
stringCount = 0;
|
||||
stringsStart = 0;
|
||||
buffer = ByteBuffer.allocate(0);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
isUtf8 = false;
|
||||
}
|
||||
|
||||
public BinaryXMLStrings(int stringCount, long stringsStart, byte[] buffer, boolean isUtf8) {
|
||||
this.stringCount = stringCount;
|
||||
this.stringsStart = stringsStart;
|
||||
this.buffer = ByteBuffer.wrap(buffer);
|
||||
this.buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
this.isUtf8 = isUtf8;
|
||||
}
|
||||
|
||||
public String get(int id) {
|
||||
String cached = cache.get(id);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
long offset = stringsStart + buffer.getInt(id * 4);
|
||||
String extracted;
|
||||
if (isUtf8) {
|
||||
extracted = extractString8(this.buffer.array(), (int) offset);
|
||||
} else {
|
||||
// don't trust specified string length, read until \0
|
||||
// stringsOffset can be same for different indexes
|
||||
extracted = extractString16(this.buffer.array(), (int) offset);
|
||||
}
|
||||
cache.put(id, extracted);
|
||||
return extracted;
|
||||
}
|
||||
|
||||
public void put(int id, String content) {
|
||||
cache.put(id, content);
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return this.stringCount;
|
||||
}
|
||||
|
||||
private static String extractString8(byte[] strArray, int offset) {
|
||||
if (offset >= strArray.length) {
|
||||
return "STRING_DECODE_ERROR";
|
||||
}
|
||||
int start = offset + skipStrLen8(strArray, offset);
|
||||
int len = strArray[start++];
|
||||
if (len == 0) {
|
||||
return "";
|
||||
}
|
||||
if ((len & 0x80) != 0) {
|
||||
len = (len & 0x7F) << 8 | strArray[start++] & 0xFF;
|
||||
}
|
||||
byte[] arr = Arrays.copyOfRange(strArray, start, start + len);
|
||||
return new String(arr, ParserStream.STRING_CHARSET_UTF8);
|
||||
}
|
||||
|
||||
private static String extractString16(byte[] strArray, int offset) {
|
||||
int len = strArray.length;
|
||||
int start = offset + skipStrLen16(strArray, offset);
|
||||
int end = start;
|
||||
while (true) {
|
||||
if (end + 1 >= len) {
|
||||
break;
|
||||
}
|
||||
if (strArray[end] == 0 && strArray[end + 1] == 0) {
|
||||
break;
|
||||
}
|
||||
end += 2;
|
||||
}
|
||||
byte[] arr = Arrays.copyOfRange(strArray, start, end);
|
||||
return new String(arr, ParserStream.STRING_CHARSET_UTF16);
|
||||
}
|
||||
|
||||
private static int skipStrLen8(byte[] strArray, int offset) {
|
||||
return (strArray[offset] & 0x80) == 0 ? 1 : 2;
|
||||
}
|
||||
|
||||
private static int skipStrLen16(byte[] strArray, int offset) {
|
||||
return (strArray[offset + 1] & 0x80) == 0 ? 2 : 4;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,16 @@
|
||||
package jadx.core.xmlgen;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class CommonBinaryParser extends ParserConstants {
|
||||
protected ParserStream is;
|
||||
|
||||
protected String[] parseStringPool() throws IOException {
|
||||
protected BinaryXMLStrings parseStringPool() throws IOException {
|
||||
is.checkInt16(RES_STRING_POOL_TYPE, "String pool expected");
|
||||
return parseStringPoolNoType();
|
||||
}
|
||||
|
||||
protected String[] parseStringPoolNoType() throws IOException {
|
||||
protected BinaryXMLStrings parseStringPoolNoType() throws IOException {
|
||||
long start = is.getPos() - 2;
|
||||
is.checkInt16(0x001c, "String pool header size not 0x001c");
|
||||
long size = is.readUInt32();
|
||||
@@ -23,68 +22,16 @@ public class CommonBinaryParser extends ParserConstants {
|
||||
long stringsStart = is.readInt32();
|
||||
long stylesStart = is.readInt32();
|
||||
|
||||
int[] stringsOffset = is.readInt32Array(stringCount);
|
||||
int[] stylesOffset = is.readInt32Array(styleCount);
|
||||
|
||||
is.skipToPos(start + stringsStart, "Expected strings start");
|
||||
String[] strings = new String[stringCount];
|
||||
byte[] strData = is.readInt8Array((int) (chunkEnd - is.getPos()));
|
||||
if ((flags & UTF8_FLAG) != 0) {
|
||||
// UTF-8
|
||||
for (int i = 0; i < stringCount; i++) {
|
||||
strings[i] = extractString8(strData, stringsOffset[i]);
|
||||
}
|
||||
} else {
|
||||
// UTF-16
|
||||
for (int i = 0; i < stringCount; i++) {
|
||||
// don't trust specified string length, read until \0
|
||||
// stringsOffset can be same for different indexes
|
||||
strings[i] = extractString16(strData, stringsOffset[i]);
|
||||
}
|
||||
}
|
||||
// Correct the offset of actual strings, as the header is already read.
|
||||
stringsStart = stringsStart - (is.getPos() - start);
|
||||
byte[] buffer = is.readInt8Array((int) (chunkEnd - is.getPos()));
|
||||
is.checkPos(chunkEnd, "Expected strings pool end");
|
||||
return strings;
|
||||
}
|
||||
|
||||
private static String extractString8(byte[] strArray, int offset) {
|
||||
if (offset >= strArray.length) {
|
||||
return "STRING_DECODE_ERROR";
|
||||
}
|
||||
int start = offset + skipStrLen8(strArray, offset);
|
||||
int len = strArray[start++];
|
||||
if (len == 0) {
|
||||
return "";
|
||||
}
|
||||
if ((len & 0x80) != 0) {
|
||||
len = (len & 0x7F) << 8 | strArray[start++] & 0xFF;
|
||||
}
|
||||
byte[] arr = Arrays.copyOfRange(strArray, start, start + len);
|
||||
return new String(arr, ParserStream.STRING_CHARSET_UTF8);
|
||||
}
|
||||
|
||||
private static String extractString16(byte[] strArray, int offset) {
|
||||
int len = strArray.length;
|
||||
int start = offset + skipStrLen16(strArray, offset);
|
||||
int end = start;
|
||||
while (true) {
|
||||
if (end + 1 >= len) {
|
||||
break;
|
||||
}
|
||||
if (strArray[end] == 0 && strArray[end + 1] == 0) {
|
||||
break;
|
||||
}
|
||||
end += 2;
|
||||
}
|
||||
byte[] arr = Arrays.copyOfRange(strArray, start, end);
|
||||
return new String(arr, ParserStream.STRING_CHARSET_UTF16);
|
||||
}
|
||||
|
||||
private static int skipStrLen8(byte[] strArray, int offset) {
|
||||
return (strArray[offset] & 0x80) == 0 ? 1 : 2;
|
||||
}
|
||||
|
||||
private static int skipStrLen16(byte[] strArray, int offset) {
|
||||
return (strArray[offset + 1] & 0x80) == 0 ? 2 : 4;
|
||||
return new BinaryXMLStrings(
|
||||
stringCount,
|
||||
stringsStart,
|
||||
buffer,
|
||||
(flags & UTF8_FLAG) != 0);
|
||||
}
|
||||
|
||||
protected void die(String message) throws IOException {
|
||||
|
||||
@@ -9,5 +9,5 @@ public interface IResParser {
|
||||
|
||||
ResourceStorage getResStorage();
|
||||
|
||||
String[] getStrings();
|
||||
BinaryXMLStrings getStrings();
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ public class ResProtoParser implements IResParser {
|
||||
|
||||
public ResContainer decodeFiles(InputStream inputStream) throws IOException {
|
||||
decode(inputStream);
|
||||
ValuesParser vp = new ValuesParser(new String[0], resStorage.getResourcesNames());
|
||||
ValuesParser vp = new ValuesParser(new BinaryXMLStrings(), resStorage.getResourcesNames());
|
||||
ResXmlGen resGen = new ResXmlGen(resStorage, vp);
|
||||
ICodeInfo content = XmlGenUtils.makeXmlDump(root.makeCodeWriter(), resStorage);
|
||||
List<ResContainer> xmlFiles = resGen.makeResourcesXml();
|
||||
@@ -252,7 +252,7 @@ public class ResProtoParser implements IResParser {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getStrings() {
|
||||
return new String[0];
|
||||
public BinaryXMLStrings getStrings() {
|
||||
return new BinaryXMLStrings();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,10 +38,10 @@ public class ResTableParser extends CommonBinaryParser implements IResParser {
|
||||
private static final class PackageChunk {
|
||||
private final int id;
|
||||
private final String name;
|
||||
private final String[] typeStrings;
|
||||
private final String[] keyStrings;
|
||||
private final BinaryXMLStrings typeStrings;
|
||||
private final BinaryXMLStrings keyStrings;
|
||||
|
||||
private PackageChunk(int id, String name, String[] typeStrings, String[] keyStrings) {
|
||||
private PackageChunk(int id, String name, BinaryXMLStrings typeStrings, BinaryXMLStrings keyStrings) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.typeStrings = typeStrings;
|
||||
@@ -56,11 +56,11 @@ public class ResTableParser extends CommonBinaryParser implements IResParser {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String[] getTypeStrings() {
|
||||
public BinaryXMLStrings getTypeStrings() {
|
||||
return typeStrings;
|
||||
}
|
||||
|
||||
public String[] getKeyStrings() {
|
||||
public BinaryXMLStrings getKeyStrings() {
|
||||
return keyStrings;
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ public class ResTableParser extends CommonBinaryParser implements IResParser {
|
||||
private final boolean useRawResName;
|
||||
private final RootNode root;
|
||||
private final ResourceStorage resStorage = new ResourceStorage();
|
||||
private String[] strings;
|
||||
private BinaryXMLStrings strings;
|
||||
|
||||
public ResTableParser(RootNode root) {
|
||||
this(root, false);
|
||||
@@ -137,12 +137,12 @@ public class ResTableParser extends CommonBinaryParser implements IResParser {
|
||||
is.readInt32();
|
||||
}
|
||||
|
||||
String[] typeStrings = null;
|
||||
BinaryXMLStrings typeStrings = null;
|
||||
if (typeStringsOffset != 0) {
|
||||
is.skipToPos(typeStringsOffset, "Expected typeStrings string pool");
|
||||
typeStrings = parseStringPool();
|
||||
}
|
||||
String[] keyStrings = null;
|
||||
BinaryXMLStrings keyStrings = null;
|
||||
if (keyStringsOffset != 0) {
|
||||
is.skipToPos(keyStringsOffset, "Expected keyStrings string pool");
|
||||
keyStrings = parseStringPool();
|
||||
@@ -185,23 +185,23 @@ public class ResTableParser extends CommonBinaryParser implements IResParser {
|
||||
return pkg;
|
||||
}
|
||||
|
||||
private void deobfKeyStrings(String[] keyStrings) {
|
||||
int keysCount = keyStrings.length;
|
||||
private void deobfKeyStrings(BinaryXMLStrings keyStrings) {
|
||||
int keysCount = keyStrings.size();
|
||||
if (root.getArgs().isRenamePrintable()) {
|
||||
for (int i = 0; i < keysCount; i++) {
|
||||
String keyString = keyStrings[i];
|
||||
String keyString = keyStrings.get(i);
|
||||
if (!NameMapper.isAllCharsPrintable(keyString)) {
|
||||
keyStrings[i] = makeNewKeyName(i);
|
||||
keyStrings.put(i, makeNewKeyName(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (root.getArgs().isRenameValid()) {
|
||||
Set<String> keySet = new HashSet<>(keysCount);
|
||||
for (int i = 0; i < keysCount; i++) {
|
||||
String keyString = keyStrings[i];
|
||||
String keyString = keyStrings.get(i);
|
||||
boolean isNew = keySet.add(keyString);
|
||||
if (!isNew) {
|
||||
keyStrings[i] = makeNewKeyName(i);
|
||||
keyStrings.put(i, makeNewKeyName(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -274,7 +274,7 @@ public class ResTableParser extends CommonBinaryParser implements IResParser {
|
||||
EntryConfig config = parseConfig();
|
||||
|
||||
if (config.isInvalid) {
|
||||
String typeName = pkg.getTypeStrings()[id - 1];
|
||||
String typeName = pkg.getTypeStrings().get(id - 1);
|
||||
LOG.warn("Invalid config flags detected: {}{}", typeName, config.getQualifiers());
|
||||
}
|
||||
|
||||
@@ -351,8 +351,8 @@ public class ResTableParser extends CommonBinaryParser implements IResParser {
|
||||
}
|
||||
|
||||
int resRef = pkg.getId() << 24 | typeId << 16 | entryId;
|
||||
String typeName = pkg.getTypeStrings()[typeId - 1];
|
||||
String origKeyName = pkg.getKeyStrings()[key];
|
||||
String typeName = pkg.getTypeStrings().get(typeId - 1);
|
||||
String origKeyName = pkg.getKeyStrings().get(key);
|
||||
ResourceEntry newResEntry = new ResourceEntry(resRef, pkg.getName(), typeName, getResName(typeName, resRef, origKeyName), config);
|
||||
ResourceEntry prevResEntry = resStorage.searchEntryWithSameName(newResEntry);
|
||||
if (prevResEntry != null) {
|
||||
@@ -568,7 +568,7 @@ public class ResTableParser extends CommonBinaryParser implements IResParser {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getStrings() {
|
||||
public BinaryXMLStrings getStrings() {
|
||||
return strings;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.utils.android.TextResMapFile;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.xmlgen.BinaryXMLStrings;
|
||||
import jadx.core.xmlgen.ParserConstants;
|
||||
import jadx.core.xmlgen.XmlGenUtils;
|
||||
|
||||
@@ -19,10 +20,10 @@ public class ValuesParser extends ParserConstants {
|
||||
|
||||
private static Map<Integer, String> androidResMap;
|
||||
|
||||
private final String[] strings;
|
||||
private final BinaryXMLStrings strings;
|
||||
private final Map<Integer, String> resMap;
|
||||
|
||||
public ValuesParser(String[] strings, Map<Integer, String> resMap) {
|
||||
public ValuesParser(BinaryXMLStrings strings, Map<Integer, String> resMap) {
|
||||
this.strings = strings;
|
||||
this.resMap = resMap;
|
||||
getAndroidResMap();
|
||||
@@ -105,7 +106,7 @@ public class ValuesParser extends ParserConstants {
|
||||
case TYPE_NULL:
|
||||
return null;
|
||||
case TYPE_STRING:
|
||||
return strings[data];
|
||||
return strings.get(data);
|
||||
case TYPE_INT_DEC:
|
||||
return Integer.toString(data);
|
||||
case TYPE_INT_HEX:
|
||||
|
||||
@@ -135,7 +135,9 @@ class ResXmlGenTest {
|
||||
re.setNamedValues(Lists.list());
|
||||
resStorage.add(re);
|
||||
|
||||
ValuesParser vp = new ValuesParser(new String[] { "Jadx Decompiler App" }, resStorage.getResourcesNames());
|
||||
BinaryXMLStrings strings = new BinaryXMLStrings();
|
||||
strings.put(0, "Jadx Decompiler App");
|
||||
ValuesParser vp = new ValuesParser(strings, resStorage.getResourcesNames());
|
||||
ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp);
|
||||
List<ResContainer> files = resXmlGen.makeResourcesXml();
|
||||
|
||||
@@ -155,7 +157,9 @@ class ResXmlGenTest {
|
||||
Lists.list(new RawNamedValue(16777216, new RawValue(3, 0))));
|
||||
resStorage.add(re);
|
||||
|
||||
ValuesParser vp = new ValuesParser(new String[] { "Let's go" }, resStorage.getResourcesNames());
|
||||
BinaryXMLStrings strings = new BinaryXMLStrings();
|
||||
strings.put(0, "Let's go");
|
||||
ValuesParser vp = new ValuesParser(strings, resStorage.getResourcesNames());
|
||||
ResXmlGen resXmlGen = new ResXmlGen(resStorage, vp);
|
||||
List<ResContainer> files = resXmlGen.makeResourcesXml();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user