Commit 650cf315 authored by tRuNKator's avatar tRuNKator Committed by skylot

fix: resource qualifiers (PR #487)

parent 42b78437
package jadx.core.xmlgen; package jadx.core.xmlgen;
import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.charset.Charset; import java.nio.charset.Charset;
...@@ -143,6 +144,23 @@ public class ParserStream { ...@@ -143,6 +144,23 @@ public class ParserStream {
input.reset(); input.reset();
} }
public void readFully(byte[] b) throws IOException {
readFully(b, 0, b.length);
}
public void readFully(byte[] b, int off, int len) throws IOException {
readPos += len;
if (len < 0)
throw new IndexOutOfBoundsException();
int n = 0;
while (n < len) {
int count = input.read(b, off + n, len - n);
if (count < 0)
throw new EOFException();
n += count;
}
}
@Override @Override
public String toString() { public String toString() {
return "pos: 0x" + Long.toHexString(readPos); return "pos: 0x" + Long.toHexString(readPos);
......
...@@ -2,6 +2,7 @@ package jadx.core.xmlgen; ...@@ -2,6 +2,7 @@ package jadx.core.xmlgen;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.math.BigInteger;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
...@@ -21,6 +22,8 @@ public class ResTableParser extends CommonBinaryParser { ...@@ -21,6 +22,8 @@ public class ResTableParser extends CommonBinaryParser {
private static final Logger LOG = LoggerFactory.getLogger(ResTableParser.class); private static final Logger LOG = LoggerFactory.getLogger(ResTableParser.class);
private static final int KNOWN_CONFIG_BYTES = 56;
private static final class PackageChunk { private static final class PackageChunk {
private final int id; private final int id;
private final String name; private final String name;
...@@ -194,6 +197,11 @@ public class ResTableParser extends CommonBinaryParser { ...@@ -194,6 +197,11 @@ public class ResTableParser extends CommonBinaryParser {
EntryConfig config = parseConfig(); EntryConfig config = parseConfig();
if (config.isInvalid) {
String typeName = pkg.getTypeStrings()[id - 1];
LOG.warn("Invalid config flags detected: " + typeName + config.getQualifiers());
}
int[] entryIndexes = new int[entryCount]; int[] entryIndexes = new int[entryCount];
for (int i = 0; i < entryCount; i++) { for (int i = 0; i < entryCount; i++) {
entryIndexes[i] = is.readInt32(); entryIndexes[i] = is.readInt32();
...@@ -248,134 +256,134 @@ public class ResTableParser extends CommonBinaryParser { ...@@ -248,134 +256,134 @@ public class ResTableParser extends CommonBinaryParser {
} }
private EntryConfig parseConfig() throws IOException { private EntryConfig parseConfig() throws IOException {
long start = is.getPos();
int size = is.readInt32(); int size = is.readInt32();
int read = 28;
EntryConfig config = new EntryConfig(); if (size < 28) {
throw new IOException("Config size < 28");
}
is.readInt16(); //mcc boolean isInvalid = false;
is.readInt16(); //mnc
config.setLanguage(parseLocale()); short mcc = (short) is.readInt16();
config.setCountry(parseLocale()); short mnc = (short) is.readInt16();
int orientation = is.readInt8(); char[] language = unpackLocaleOrRegion((byte) is.readInt8(), (byte) is.readInt8(), 'a');
int touchscreen = is.readInt8(); char[] country = unpackLocaleOrRegion((byte) is.readInt8(), (byte) is.readInt8(), '0');
int density = is.readInt16();
if (density != 0) { byte orientation = (byte) is.readInt8();
config.setDensity(parseDensity(density)); byte touchscreen = (byte) is.readInt8();
}
is.readInt8(); // keyboard int density = is.readInt16();
is.readInt8(); // navigation
is.readInt8(); // inputFlags
is.readInt8(); // inputPad0
int screenWidth = is.readInt16(); byte keyboard = (byte) is.readInt8();
int screenHeight = is.readInt16(); byte navigation = (byte) is.readInt8();
byte inputFlags = (byte) is.readInt8();
/* inputPad0 */is.readInt8();
if (screenWidth != 0 && screenHeight != 0) { short screenWidth = (short) is.readInt16();
config.setScreenSize(screenWidth + "x" + screenHeight); short screenHeight = (short) is.readInt16();
}
int sdkVersion = is.readInt16(); short sdkVersion = (short) is.readInt16();
/* minorVersion, now must always be 0 */is.readInt16();
if (sdkVersion != 0) { byte screenLayout = 0;
config.setSdkVersion("v" + sdkVersion); byte uiMode = 0;
short smallestScreenWidthDp = 0;
if (size >= 32) {
screenLayout = (byte) is.readInt8();
uiMode = (byte) is.readInt8();
smallestScreenWidthDp = (short) is.readInt16();
read = 32;
} }
int minorVersion = is.readInt16(); short screenWidthDp = 0;
short screenHeightDp = 0;
int screenLayout = is.readInt8(); if (size >= 36) {
int uiMode = is.readInt8(); screenWidthDp = (short) is.readInt16();
int smallestScreenWidthDp = is.readInt16(); screenHeightDp = (short) is.readInt16();
read = 36;
int screenWidthDp = is.readInt16(); }
int screenHeightDp = is.readInt16();
if (screenLayout != 0) { char[] localeScript = null;
config.setScreenLayout(parseScreenLayout(screenLayout)); char[] localeVariant = null;
if (size >= 48) {
localeScript = readScriptOrVariantChar(4).toCharArray();
localeVariant = readScriptOrVariantChar(8).toCharArray();
read = 48;
} }
if (smallestScreenWidthDp != 0) { byte screenLayout2 = 0;
config.setSmallestScreenWidthDp("sw" + smallestScreenWidthDp + "dp"); byte colorMode = 0;
if (size >= 52) {
screenLayout2 = (byte) is.readInt8();
colorMode = (byte) is.readInt8();
is.readInt16(); // reserved padding
read = 52;
} }
if (orientation != 0) { if (size >= 56) {
config.setOrientation(parseOrientation(orientation)); is.readInt32();
read = 56;
} }
if (screenWidthDp != 0) { int exceedingSize = size - KNOWN_CONFIG_BYTES;
config.setScreenWidthDp("w" + screenWidthDp + "dp"); if (exceedingSize > 0) {
byte[] buf = new byte[exceedingSize];
read += exceedingSize;
is.readFully(buf);
BigInteger exceedingBI = new BigInteger(1, buf);
if (exceedingBI.equals(BigInteger.ZERO)) {
LOG.info(String
.format("Config flags size > %d, but exceeding bytes are all zero, so it should be ok.",
KNOWN_CONFIG_BYTES));
} else {
LOG.warn(String.format("Config flags size > %d. Size = %d. Exceeding bytes: 0x%X.",
KNOWN_CONFIG_BYTES, size, exceedingBI));
isInvalid = true;
}
} }
if (screenHeightDp != 0) { int remainingSize = size - read;
config.setScreenHeightDp("h" + screenHeightDp + "dp"); if (remainingSize > 0) {
is.skip(remainingSize);
} }
is.skipToPos(start + size, "Skip config parsing"); return new EntryConfig(mcc, mnc, language, country,
return config; orientation, touchscreen, density, keyboard, navigation,
inputFlags, screenWidth, screenHeight, sdkVersion,
screenLayout, uiMode, smallestScreenWidthDp, screenWidthDp,
screenHeightDp, localeScript, localeVariant, screenLayout2,
colorMode, isInvalid, size);
} }
private String parseOrientation(int orientation) { private char[] unpackLocaleOrRegion(byte in0, byte in1, char base) throws IOException {
if (orientation == 1) { // check high bit, if so we have a packed 3 letter code
return "port"; if (((in0 >> 7) & 1) == 1) {
} else if (orientation == 2) { int first = in1 & 0x1F;
return "land"; int second = ((in1 & 0xE0) >> 5) + ((in0 & 0x03) << 3);
} else { int third = (in0 & 0x7C) >> 2;
return "o" + orientation;
} // since this function handles languages & regions, we add the value(s) to the base char
} // which is usually 'a' or '0' depending on language or region.
return new char[] { (char) (first + base), (char) (second + base), (char) (third + base) };
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";
} }
return new char[] { (char) in0, (char) in1 };
} }
private String parseLocale() throws IOException { private String readScriptOrVariantChar(int length) throws IOException {
int b1 = is.readInt8(); StringBuilder string = new StringBuilder(16);
int b2 = is.readInt8();
String str = null; while(length-- != 0) {
if (b1 != 0 && b2 != 0) { short ch = (short) is.readInt8();
if ((b1 & 0x80) == 0) { if (ch == 0) {
str = new String(new char[]{(char) b1, (char) b2}); break;
} else {
LOG.warn("TODO: parse locale: 0x{}{}", Integer.toHexString(b1), Integer.toHexString(b2));
} }
string.append((char) ch);
} }
return str; is.skip(length);
return string.toString();
} }
} }
...@@ -202,10 +202,10 @@ public class ResXmlGen { ...@@ -202,10 +202,10 @@ public class ResXmlGen {
private String getFileName(ResourceEntry ri) { private String getFileName(ResourceEntry ri) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
String locale = ri.getConfig().getLocale(); String qualifiers = ri.getConfig().getQualifiers();
sb.append("res/values"); sb.append("res/values");
if (!locale.isEmpty()) { if (!qualifiers.isEmpty()) {
sb.append('-').append(locale); sb.append(qualifiers);
} }
sb.append('/'); sb.append('/');
sb.append(ri.getTypeName()); sb.append(ri.getTypeName());
......
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