From bc7394080d0a5d827f03a95d7c6fcd3bf1bfb5b0 Mon Sep 17 00:00:00 2001 From: Connor Tumbleson Date: Wed, 4 Oct 2023 20:13:58 -0400 Subject: [PATCH] Android 14 Support (Partial) (#3206) * fix: prevent over-reading config flags * feat: add grammatical inflection * fix: add natural requirement for U * fix: UpsideDownCake is now 34 * test: test for grammatical inflection * fix: add detection for compact resources * fix: add detection for offset16 table types * Revert "test: test for grammatical inflection" This reverts commit fa08cef9fe6d436176f74152d85a652a771971ad. * refactor: use enum instead of magic numbers --- .../main/java/brut/androlib/apk/ApkInfo.java | 1 + .../androlib/res/data/ResConfigFlags.java | 26 ++++++++++++++++- .../androlib/res/decoder/ARSCDecoder.java | 29 +++++++++++++++---- 3 files changed, 50 insertions(+), 6 deletions(-) diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/ApkInfo.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/ApkInfo.java index 99faee09..e7866360 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/ApkInfo.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/apk/ApkInfo.java @@ -191,6 +191,7 @@ public class ApkInfo implements YamlSerializable { return ResConfigFlags.SDK_TIRAMISU; case "UPSIDEDOWNCAKE": case "UPSIDE_DOWN_CAKE": + return ResConfigFlags.SDK_UPSIDEDOWN_CAKE; case "VANILLAICECREAM": case "VANILLA_ICE_CREAM": return ResConfigFlags.SDK_DEVELOPMENT; diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResConfigFlags.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResConfigFlags.java index 2c4ae0f8..1d27b77e 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResConfigFlags.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/data/ResConfigFlags.java @@ -32,6 +32,7 @@ public class ResConfigFlags { public final byte keyboard; public final byte navigation; public final byte inputFlags; + public final byte grammaticalInflection; public final short screenWidth; public final short screenHeight; @@ -70,6 +71,7 @@ public class ResConfigFlags { keyboard = KEYBOARD_ANY; navigation = NAVIGATION_ANY; inputFlags = KEYSHIDDEN_ANY | NAVHIDDEN_ANY; + grammaticalInflection = GRAMMATICAL_GENDER_ANY; screenWidth = 0; screenHeight = 0; sdkVersion = 0; @@ -91,7 +93,7 @@ public class ResConfigFlags { public ResConfigFlags(short mcc, short mnc, char[] language, char[] region, byte orientation, byte touchscreen, int density, byte keyboard, byte navigation, - byte inputFlags, short screenWidth, short screenHeight, + byte inputFlags, byte grammaticalInflection, short screenWidth, short screenHeight, short sdkVersion, byte screenLayout, byte uiMode, short smallestScreenWidthDp, short screenWidthDp, short screenHeightDp, char[] localeScript, char[] localeVariant, @@ -149,6 +151,7 @@ public class ResConfigFlags { this.keyboard = keyboard; this.navigation = navigation; this.inputFlags = inputFlags; + this.grammaticalInflection = grammaticalInflection; this.screenWidth = screenWidth; this.screenHeight = screenHeight; this.sdkVersion = sdkVersion; @@ -198,6 +201,18 @@ public class ResConfigFlags { } ret.append(getLocaleString()); + switch (grammaticalInflection) { + case GRAMMATICAL_GENDER_NEUTER: + ret.append("-neuter"); + break; + case GRAMMATICAL_GENDER_FEMININE: + ret.append("-feminine"); + break; + case GRAMMATICAL_GENDER_MASCULINE: + ret.append("-masculine"); + break; + } + switch (screenLayout & MASK_LAYOUTDIR) { case SCREENLAYOUT_LAYOUTDIR_RTL: ret.append("-ldrtl"); @@ -421,6 +436,9 @@ public class ResConfigFlags { } private short getNaturalSdkVersionRequirement() { + if (grammaticalInflection != 0) { + return SDK_UPSIDEDOWN_CAKE; + } if ((uiMode & MASK_UI_MODE_TYPE) == UI_MODE_TYPE_VR_HEADSET || (colorMode & COLOR_WIDE_MASK) != 0 || ((colorMode & COLOR_HDR_MASK) != 0)) { return SDK_OREO; } @@ -550,6 +568,7 @@ public class ResConfigFlags { public final static byte SDK_S = 31; public final static byte SDK_S_V2 = 32; public final static byte SDK_TIRAMISU = 33; + public final static byte SDK_UPSIDEDOWN_CAKE = 34; // AOSP has this as 10,000 for dev purposes. // platform_frameworks_base/commit/c7a1109a1fe0771d4c9b572dcf178e2779fc4f2d @@ -590,6 +609,11 @@ public class ResConfigFlags { public final static short SCREENLAYOUT_ROUND_NO = 0x1; public final static short SCREENLAYOUT_ROUND_YES = 0x2; + public final static byte GRAMMATICAL_GENDER_ANY = 0; + public final static byte GRAMMATICAL_GENDER_NEUTER = 1; + public final static byte GRAMMATICAL_GENDER_FEMININE = 2; + public final static byte GRAMMATICAL_GENDER_MASCULINE = 3; + public final static byte KEYBOARD_ANY = 0; public final static byte KEYBOARD_NOKEYS = 1; public final static byte KEYBOARD_QWERTY = 2; diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ARSCDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ARSCDecoder.java index 371aedd5..a92de989 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ARSCDecoder.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ARSCDecoder.java @@ -282,13 +282,18 @@ public class ARSCDecoder { // Be sure we don't poison mResTable by marking the application as sparse // Only flag the ResTable as sparse if the main package is not loaded. - if ((typeFlags & 0x01) != 0 && !mResTable.isMainPkgLoaded()) { + if ((typeFlags & TABLE_TYPE_FLAG_SPARSE) != 0 && !mResTable.isMainPkgLoaded()) { mResTable.setSparseResources(true); } + if ((typeFlags & TABLE_TYPE_FLAG_OFFSET16) != 0) { + LOGGER.warning("Please report this application to Apktool for a fix: https://github.com/iBotPeaches/Apktool/issues/3367"); + throw new AndrolibException("Unexpected TYPE_FLAG_OFFSET16"); + } + HashMap entryOffsetMap = new LinkedHashMap<>(); for (int i = 0; i < entryCount; i++) { - if ((typeFlags & 0x01) != 0) { + if ((typeFlags & TABLE_TYPE_FLAG_SPARSE) != 0) { entryOffsetMap.put(mIn.readUnsignedShort(), mIn.readUnsignedShort()); } else { entryOffsetMap.put(i, mIn.readInt()); @@ -352,7 +357,15 @@ public class ARSCDecoder { return null; } - ResValue value = (flags & ENTRY_FLAG_COMPLEX) == 0 ? readValue() : readComplexEntry(); + boolean isComplex = (flags & ENTRY_FLAG_COMPLEX) != 0; + boolean isCompact = (flags & ENTRY_FLAG_COMPACT) != 0; + + if (isCompact) { + LOGGER.warning("Please report this application to Apktool for a fix: https://github.com/iBotPeaches/Apktool/issues/3366"); + throw new AndrolibException("Unexpected entry type: compact"); + } + + ResValue value = isComplex ? readComplexEntry() : readValue(); // #2824 - In some applications the res entries are duplicated with the 2nd being malformed. // AOSP skips this, so we will do the same. if (value == null) { @@ -483,11 +496,12 @@ public class ARSCDecoder { byte keyboard = 0; byte navigation = 0; byte inputFlags = 0; + byte grammaticalInflection = 0; if (size >= 20) { keyboard = mIn.readByte(); navigation = mIn.readByte(); inputFlags = mIn.readByte(); - mIn.skipBytes(1); // inputPad0 + grammaticalInflection = mIn.readByte(); read = 20; } @@ -545,6 +559,7 @@ public class ARSCDecoder { } int exceedingKnownSize = size - KNOWN_CONFIG_BYTES; + if (exceedingKnownSize > 0) { byte[] buf = new byte[exceedingKnownSize]; read += exceedingKnownSize; @@ -569,7 +584,7 @@ public class ARSCDecoder { return new ResConfigFlags(mcc, mnc, language, country, orientation, touchscreen, density, keyboard, navigation, - inputFlags, screenWidth, screenHeight, sdkVersion, + inputFlags, grammaticalInflection, screenWidth, screenHeight, sdkVersion, screenLayout, uiMode, smallestScreenWidthDp, screenWidthDp, screenHeightDp, localeScript, localeVariant, screenLayout2, colorMode, localeNumberingSystem, isInvalid, size); @@ -663,6 +678,10 @@ public class ARSCDecoder { private final static short ENTRY_FLAG_COMPLEX = 0x0001; private final static short ENTRY_FLAG_PUBLIC = 0x0002; private final static short ENTRY_FLAG_WEAK = 0x0004; + private final static short ENTRY_FLAG_COMPACT = 0x0008; + + private final static short TABLE_TYPE_FLAG_SPARSE = 0x01; + private final static short TABLE_TYPE_FLAG_OFFSET16 = 0x02; private static final int KNOWN_CONFIG_BYTES = 64;