Skip to content

Commit

Permalink
fix: resource qualifiers (PR #487)
Browse files Browse the repository at this point in the history
  • Loading branch information
tRuNKator authored and skylot committed Mar 22, 2019
1 parent 42b7843 commit 650cf31
Show file tree
Hide file tree
Showing 4 changed files with 772 additions and 256 deletions.
18 changes: 18 additions & 0 deletions jadx-core/src/main/java/jadx/core/xmlgen/ParserStream.java
@@ -1,5 +1,6 @@
package jadx.core.xmlgen;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
Expand Down Expand Up @@ -143,6 +144,23 @@ public void reset() throws IOException {
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
public String toString() {
return "pos: 0x" + Long.toHexString(readPos);
Expand Down
204 changes: 106 additions & 98 deletions jadx-core/src/main/java/jadx/core/xmlgen/ResTableParser.java
Expand Up @@ -2,6 +2,7 @@

import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
Expand All @@ -21,6 +22,8 @@ public class ResTableParser extends CommonBinaryParser {

private static final Logger LOG = LoggerFactory.getLogger(ResTableParser.class);

private static final int KNOWN_CONFIG_BYTES = 56;

private static final class PackageChunk {
private final int id;
private final String name;
Expand Down Expand Up @@ -194,6 +197,11 @@ private void parseTypeChunk(long start, PackageChunk pkg) throws IOException {

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];
for (int i = 0; i < entryCount; i++) {
entryIndexes[i] = is.readInt32();
Expand Down Expand Up @@ -248,134 +256,134 @@ private RawValue parseValue() throws IOException {
}

private EntryConfig parseConfig() throws IOException {
long start = is.getPos();
int size = is.readInt32();
int read = 28;

EntryConfig config = new EntryConfig();

is.readInt16(); //mcc
is.readInt16(); //mnc
if (size < 28) {
throw new IOException("Config size < 28");
}

config.setLanguage(parseLocale());
config.setCountry(parseLocale());
boolean isInvalid = false;

int orientation = is.readInt8();
int touchscreen = is.readInt8();
int density = is.readInt16();
short mcc = (short) is.readInt16();
short mnc = (short) is.readInt16();

if (density != 0) {
config.setDensity(parseDensity(density));
}
char[] language = unpackLocaleOrRegion((byte) is.readInt8(), (byte) is.readInt8(), 'a');
char[] country = unpackLocaleOrRegion((byte) is.readInt8(), (byte) is.readInt8(), '0');

is.readInt8(); // keyboard
is.readInt8(); // navigation
is.readInt8(); // inputFlags
is.readInt8(); // inputPad0
byte orientation = (byte) is.readInt8();
byte touchscreen = (byte) is.readInt8();

int screenWidth = is.readInt16();
int screenHeight = is.readInt16();
int density = is.readInt16();

if (screenWidth != 0 && screenHeight != 0) {
config.setScreenSize(screenWidth + "x" + screenHeight);
byte keyboard = (byte) is.readInt8();
byte navigation = (byte) is.readInt8();
byte inputFlags = (byte) is.readInt8();
/* inputPad0 */is.readInt8();

short screenWidth = (short) is.readInt16();
short screenHeight = (short) is.readInt16();

short sdkVersion = (short) is.readInt16();
/* minorVersion, now must always be 0 */is.readInt16();

byte screenLayout = 0;
byte uiMode = 0;
short smallestScreenWidthDp = 0;
if (size >= 32) {
screenLayout = (byte) is.readInt8();
uiMode = (byte) is.readInt8();
smallestScreenWidthDp = (short) is.readInt16();
read = 32;
}

int sdkVersion = is.readInt16();

if (sdkVersion != 0) {
config.setSdkVersion("v" + sdkVersion);
short screenWidthDp = 0;
short screenHeightDp = 0;
if (size >= 36) {
screenWidthDp = (short) is.readInt16();
screenHeightDp = (short) is.readInt16();
read = 36;
}

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));
char[] localeScript = null;
char[] localeVariant = null;
if (size >= 48) {
localeScript = readScriptOrVariantChar(4).toCharArray();
localeVariant = readScriptOrVariantChar(8).toCharArray();
read = 48;
}

if (smallestScreenWidthDp != 0) {
config.setSmallestScreenWidthDp("sw" + smallestScreenWidthDp + "dp");
byte screenLayout2 = 0;
byte colorMode = 0;
if (size >= 52) {
screenLayout2 = (byte) is.readInt8();
colorMode = (byte) is.readInt8();
is.readInt16(); // reserved padding
read = 52;
}

if (orientation != 0) {
config.setOrientation(parseOrientation(orientation));
if (size >= 56) {
is.readInt32();
read = 56;
}

if (screenWidthDp != 0) {
config.setScreenWidthDp("w" + screenWidthDp + "dp");
int exceedingSize = size - KNOWN_CONFIG_BYTES;
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) {
config.setScreenHeightDp("h" + screenHeightDp + "dp");
int remainingSize = size - read;
if (remainingSize > 0) {
is.skip(remainingSize);
}

is.skipToPos(start + size, "Skip config parsing");
return config;
return new EntryConfig(mcc, mnc, language, country,
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) {
if (orientation == 1) {
return "port";
} else if (orientation == 2) {
return "land";
} else {
return "o" + orientation;
}
}
private char[] unpackLocaleOrRegion(byte in0, byte in1, char base) throws IOException {
// check high bit, if so we have a packed 3 letter code
if (((in0 >> 7) & 1) == 1) {
int first = in1 & 0x1F;
int second = ((in1 & 0xE0) >> 5) + ((in0 & 0x03) << 3);
int third = (in0 & 0x7C) >> 2;

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;
// 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) };
}
return new char[] { (char) in0, (char) in1 };
}

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 readScriptOrVariantChar(int length) throws IOException {
StringBuilder string = new StringBuilder(16);

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));
while(length-- != 0) {
short ch = (short) is.readInt8();
if (ch == 0) {
break;
}
string.append((char) ch);
}
return str;
is.skip(length);

return string.toString();
}
}
6 changes: 3 additions & 3 deletions jadx-core/src/main/java/jadx/core/xmlgen/ResXmlGen.java
Expand Up @@ -202,10 +202,10 @@ private void addSimpleValue(CodeWriter cw, String typeName, String itemTag, Stri

private String getFileName(ResourceEntry ri) {
StringBuilder sb = new StringBuilder();
String locale = ri.getConfig().getLocale();
String qualifiers = ri.getConfig().getQualifiers();
sb.append("res/values");
if (!locale.isEmpty()) {
sb.append('-').append(locale);
if (!qualifiers.isEmpty()) {
sb.append(qualifiers);
}
sb.append('/');
sb.append(ri.getTypeName());
Expand Down

0 comments on commit 650cf31

Please sign in to comment.