package jadx.core.xmlgen;
import jadx.core.codegen.CodeWriter;
import jadx.core.xmlgen.entry.EntryConfig;
import jadx.core.xmlgen.entry.RawNamedValue;
import jadx.core.xmlgen.entry.RawValue;
import jadx.core.xmlgen.entry.ResourceEntry;
import jadx.core.xmlgen.entry.ValuesParser;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ResTableParser extends CommonBinaryParser {
private static final Logger LOG = LoggerFactory.getLogger(ResTableParser.class);
private static final class PackageChunk {
private final int id;
private final String name;
private final String[] typeStrings;
private final String[] keyStrings;
private PackageChunk(int id, String name, String[] typeStrings, String[] keyStrings) {
this.id = id;
this.name = name;
this.typeStrings = typeStrings;
this.keyStrings = keyStrings;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public String[] getTypeStrings() {
return typeStrings;
}
public String[] getKeyStrings() {
return keyStrings;
}
}
private String[] strings;
private final ResourceStorage resStorage = new ResourceStorage();
public void decode(InputStream inputStream) throws IOException {
is = new ParserStream(inputStream);
decodeTableChunk();
resStorage.finish();
}
public ResContainer decodeFiles(InputStream inputStream) throws IOException {
decode(inputStream);
ValuesParser vp = new ValuesParser(strings, resStorage.getResourcesNames());
ResXmlGen resGen = new ResXmlGen(resStorage, vp);
ResContainer res = ResContainer.multiFile("res");
res.setContent(makeDump());
res.getSubFiles().addAll(resGen.makeResourcesXml());
return res;
}
public CodeWriter makeDump() throws IOException {
CodeWriter writer = new CodeWriter();
writer.add("app package: ").add(resStorage.getAppPackage());
writer.startLine();
ValuesParser vp = new ValuesParser(strings, resStorage.getResourcesNames());
for (ResourceEntry ri : resStorage.getResources()) {
writer.startLine(ri + ": " + vp.getValueString(ri));
}
writer.finish();
return writer;
}
public ResourceStorage getResStorage() {
return resStorage;
}
void decodeTableChunk() throws IOException {
is.checkInt16(RES_TABLE_TYPE, "Not a table chunk");
is.checkInt16(0x000c, "Unexpected table header size");
/*int size = */
is.readInt32();
int pkgCount = is.readInt32();
strings = parseStringPool();
for (int i = 0; i < pkgCount; i++) {
parsePackage();
}
}
private PackageChunk parsePackage() throws IOException {
long start = is.getPos();
is.checkInt16(RES_TABLE_PACKAGE_TYPE, "Not a table chunk");
int headerSize = is.readInt16();
if (headerSize != 0x011c && headerSize != 0x0120) {
die("Unexpected package header size");
}
long size = is.readUInt32();
long endPos = start + size;
int id = is.readInt32();
String name = is.readString16Fixed(128);
long typeStringsOffset = start + is.readInt32();
/* int lastPublicType = */
is.readInt32();
long keyStringsOffset = start + is.readInt32();
/* int lastPublicKey = */
is.readInt32();
if (headerSize == 0x0120) {
/* int typeIdOffset = */
is.readInt32();
}
String[] typeStrings = null;
if (typeStringsOffset != 0) {
is.skipToPos(typeStringsOffset, "Expected typeStrings string pool");
typeStrings = parseStringPool();
}
String[] keyStrings = null;
if (keyStringsOffset != 0) {
is.skipToPos(keyStringsOffset, "Expected keyStrings string pool");
keyStrings = parseStringPool();
}
PackageChunk pkg = new PackageChunk(id, name, typeStrings, keyStrings);
if (id == 0x7F) {
resStorage.setAppPackage(name);
}
while (is.getPos() < endPos) {
long chunkStart = is.getPos();
int type = is.readInt16();
if (type == RES_NULL_TYPE) {
continue;
}
if (type == RES_TABLE_TYPE_SPEC_TYPE) {
parseTypeSpecChunk();
} else if (type == RES_TABLE_TYPE_TYPE) {
parseTypeChunk(chunkStart, pkg);
}
}
return pkg;
}
private void parseTypeSpecChunk() throws IOException {
is.checkInt16(0x0010, "Unexpected type spec header size");
/*int size = */
is.readInt32();
int id = is.readInt8();
is.skip(3);
int entryCount = is.readInt32();
for (int i = 0; i < entryCount; i++) {
int entryFlag = is.readInt32();
}
}
private void parseTypeChunk(long start, PackageChunk pkg) throws IOException {
/*int headerSize = */ is.readInt16();
/*int size = */ is.readInt32();
int id = is.readInt8();
is.checkInt8(0, "type chunk, res0");
is.checkInt16(0, "type chunk, res1");
int entryCount = is.readInt32();
long entriesStart = start + is.readInt32();
EntryConfig config = parseConfig();
int[] entryIndexes = new int[entryCount];
for (int i = 0; i < entryCount; i++) {
entryIndexes[i] = is.readInt32();
}
is.checkPos(entriesStart, "Expected entry start");
for (int i = 0; i < entryCount; i++) {
if (entryIndexes[i] != NO_ENTRY) {
parseEntry(pkg, id, i, config);
}
}
}
private void parseEntry(PackageChunk pkg, int typeId, int entryId, EntryConfig config) throws IOException {
/* int size = */
is.readInt16();
int flags = is.readInt16();
int key = is.readInt32();
int resRef = pkg.getId() << 24 | typeId << 16 | entryId;
String typeName = pkg.getTypeStrings()[typeId - 1];
String keyName = pkg.getKeyStrings()[key];
ResourceEntry ri = new ResourceEntry(resRef, pkg.getName(), typeName, keyName);
ri.setConfig(config);
if ((flags & FLAG_COMPLEX) == 0) {
ri.setSimpleValue(parseValue());
} else {
int parentRef = is.readInt32();
ri.setParentRef(parentRef);
int count = is.readInt32();
List<RawNamedValue> values = new ArrayList<RawNamedValue>(count);
for (int i = 0; i < count; i++) {
values.add(parseValueMap());
}
ri.setNamedValues(values);
}
resStorage.add(ri);
}
private RawNamedValue parseValueMap() throws IOException {
int nameRef = is.readInt32();
return new RawNamedValue(nameRef, parseValue());
}
private RawValue parseValue() throws IOException {
is.checkInt16(8, "value size");
is.checkInt8(0, "value res0 not 0");
int dataType = is.readInt8();
int data = is.readInt32();
return new RawValue(dataType, data);
}
private EntryConfig parseConfig() throws IOException {
long start = is.getPos();
int size = is.readInt32();
EntryConfig config = new EntryConfig();
is.readInt16(); //mcc
is.readInt16(); //mnc
config.setLanguage(parseLocale());
config.setCountry(parseLocale());
int orientation = is.readInt8();
int touchscreen = is.readInt8();
int density = is.readInt16();
/*
is.readInt8(); // keyboard
is.readInt8(); // navigation
is.readInt8(); // inputFlags
is.readInt8(); // inputPad0
is.readInt16(); // screenWidth
is.readInt16(); // screenHeight
is.readInt16(); // sdkVersion
is.readInt16(); // minorVersion
is.readInt8(); // screenLayout
is.readInt8(); // uiMode
is.readInt16(); // smallestScreenWidthDp
is.readInt16(); // screenWidthDp
is.readInt16(); // screenHeightDp
*/
is.skipToPos(start + size, "Skip config parsing");
return config;
}
private String parseLocale() throws IOException {
int b1 = is.readInt8();
int b2 = is.readInt8();
String str = null;
if (b1 != 0 && b2 != 0) {
if ((b1 & 0x80) == 0) {
str = new String(new char[]{(char) b1, (char) b2});
} else {
LOG.warn("TODO: parse locale: 0x{}{}", Integer.toHexString(b1), Integer.toHexString(b2));
}
}
return str;
}
}