Skip to content

Commit bcda752

Browse files
committed
fix: handle unknown res configs gracefully
1 parent 0aa2a38 commit bcda752

File tree

4 files changed

+89
-77
lines changed

4 files changed

+89
-77
lines changed

brut.apktool/apktool-cli/src/main/java/brut/apktool/Main.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,8 @@ public static void main(String[] args) throws AndrolibException {
336336
break;
337337
case "h":
338338
case "help":
339+
case "-help":
340+
case "--help":
339341
loadOptions(null, true);
340342
printUsage();
341343
break;

brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/BinaryResourceParser.java

Lines changed: 49 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@
2727
import brut.androlib.res.table.*;
2828
import brut.androlib.res.table.value.*;
2929
import brut.util.BinaryDataInputStream;
30+
import com.google.common.io.BaseEncoding;
3031
import org.apache.commons.lang3.tuple.Pair;
3132

3233
import java.io.*;
33-
import java.math.BigInteger;
3434
import java.nio.charset.StandardCharsets;
3535
import java.util.*;
3636
import java.util.logging.Logger;
@@ -63,8 +63,6 @@ public class BinaryResourceParser {
6363
// This should not be encountered in most cases (#3993)
6464
private static final int ENTRY_FLAG_FEATUREFLAG = 0x0010;
6565

66-
private static final int CONFIG_KNOWN_MAX_SIZE = 64;
67-
6866
private final ResTable mTable;
6967
private final boolean mKeepBroken;
7068
private final boolean mRecordFlagsOffsets;
@@ -319,12 +317,14 @@ private void parseType(ResChunkPullParser parser) throws AndrolibException, IOEx
319317
String typeName = typeSpec.getName();
320318
ResType type;
321319
if (mInvalidConfigs.contains(config)) {
322-
String dirName = typeName + config.getQualifiers();
323320
if (mKeepBroken) {
324-
LOGGER.warning("Invalid resource config detected: " + dirName);
321+
LOGGER.warning(String.format(
322+
"Invalid resource config detected: %s %s", typeName, config));
325323
type = mPackage.addType(id, config);
326324
} else {
327-
LOGGER.warning("Invalid resource config detected. Dropping resources: " + dirName);
325+
LOGGER.warning(String.format(
326+
"Invalid resource config detected. Dropping resources: %s %s",
327+
typeName, config));
328328
type = null;
329329
}
330330
} else {
@@ -425,16 +425,18 @@ private void parseType(ResChunkPullParser parser) throws AndrolibException, IOEx
425425
continue;
426426
}
427427

428-
boolean overwrite;
429-
if (mPackage.hasEntrySpec(entryId)) {
430-
overwrite = mKeepBroken && mPackage.hasEntry(entryId, config);
431-
} else {
428+
// The same entry can never be added more than once.
429+
if (mPackage.hasEntry(entryId, config)) {
430+
LOGGER.warning(String.format(
431+
"Ignoring repeated entry: id=%s, config=%s", id, config));
432+
continue;
433+
}
434+
435+
if (!mPackage.hasEntrySpec(entryId)) {
432436
mPackage.addEntrySpec(entryId, mKeyStrings.getString(key));
433437
mMissingEntrySpecs.remove(entryId);
434-
overwrite = false;
435438
}
436-
437-
mPackage.addEntry(entryId, config, value, overwrite);
439+
mPackage.addEntry(entryId, config, value);
438440
}
439441
}
440442

@@ -526,45 +528,29 @@ private ResConfig parseConfig() throws AndrolibException, IOException {
526528
mIn.skipShort(); // screenConfigPad2
527529
}
528530

531+
// Locale extensions are currently not supported by aapt2.
532+
// Data beyond this point may be repurposed by certain vendors.
533+
/*boolean localeScriptWasComputed = false;
529534
String localeNumberingSystem = "";
530-
if (size >= 60) {
535+
if (size >= 64) {
536+
localeScriptWasComputed = mIn.readUnsignedByte() != 0;
531537
localeNumberingSystem = mIn.readAscii(8);
532-
}
538+
mIn.skipBytes(3); // endPadding
539+
}*/
533540

534-
boolean isInvalid = false;
535541
int bytesRead = (int) (mIn.position() - startPosition);
536-
int exceedingKnownSize = size - CONFIG_KNOWN_MAX_SIZE;
537-
if (exceedingKnownSize > 0) {
538-
byte[] buf = mIn.readBytes(exceedingKnownSize);
539-
bytesRead += exceedingKnownSize;
540-
541-
BigInteger exceedingBI = new BigInteger(1, buf);
542-
if (exceedingBI.equals(BigInteger.ZERO)) {
543-
LOGGER.fine(String.format(
544-
"Config size of %d exceeds %d, but exceeding bytes are all zero.",
545-
size, CONFIG_KNOWN_MAX_SIZE));
546-
} else {
547-
LOGGER.warning(String.format(
548-
"Config size of %d exceeds %d. Exceeding bytes: %X",
549-
size, CONFIG_KNOWN_MAX_SIZE, exceedingBI));
550-
isInvalid = true;
551-
}
552-
}
553-
554-
int remainingSize = size - bytesRead;
555-
if (remainingSize > 0) {
556-
mIn.skipBytes(remainingSize);
557-
}
542+
byte[] unknown = readExceedingBytes("Config", size, bytesRead);
558543

559544
ResConfig config = new ResConfig(
560545
mcc, mnc, language, region, orientation,
561546
touchscreen, density, keyboard, navigation, inputFlags,
562547
grammaticalInflection, screenWidth, screenHeight, sdkVersion,
563548
minorVersion, screenLayout, uiMode, smallestScreenWidthDp,
564549
screenWidthDp, screenHeightDp, localeScript, localeVariant,
565-
screenLayout2, colorMode, localeNumberingSystem);
550+
screenLayout2, colorMode, /*localeScriptWasComputed,
551+
localeNumberingSystem,*/ unknown);
566552

567-
if (isInvalid || config.isInvalid()) {
553+
if (config.isInvalid()) {
568554
mInvalidConfigs.add(config);
569555
}
570556

@@ -725,7 +711,13 @@ private void parseOverlayable(ResChunkPullParser parser) throws AndrolibExceptio
725711

726712
skipUnreadHeader(parser);
727713

728-
ResOverlayable overlayable = mPackage.addOverlayable(name, actor);
714+
// Avoid conflicts by reusing overlayables.
715+
ResOverlayable overlayable;
716+
try {
717+
overlayable = mPackage.getOverlayable(name);
718+
} catch (UndefinedResObjectException ignored) {
719+
overlayable = mPackage.addOverlayable(name, actor);
720+
}
729721

730722
parser = new ResChunkPullParser(mIn, parser.dataSize());
731723
while (nextChunk(parser)) {
@@ -787,23 +779,24 @@ private void skipUnreadHeader(ResChunkPullParser parser) throws IOException {
787779
// Trusting the header size is misleading, so compare to what we actually read in the
788780
// header vs reported and skip the rest. However, this runs after each chunk and not
789781
// every chunk reading has a specific distinction between the header and the body.
790-
int readHeaderSize = (int) (mIn.position() - parser.chunkStart());
791-
int exceedingSize = parser.headerSize() - readHeaderSize;
792-
if (exceedingSize <= 0) {
793-
return;
794-
}
782+
int bytesRead = (int) (mIn.position() - parser.chunkStart());
783+
readExceedingBytes("Chunk header", parser.headerSize(), bytesRead);
784+
}
795785

796-
byte[] buf = mIn.readBytes(exceedingSize);
797-
BigInteger exceedingBI = new BigInteger(1, buf);
798-
if (exceedingBI.equals(BigInteger.ZERO)) {
799-
LOGGER.fine(String.format(
800-
"Chunk header size: %d bytes, read: %d bytes, but exceeding bytes are all zero.",
801-
parser.headerSize(), readHeaderSize));
802-
} else {
803-
LOGGER.warning(String.format(
804-
"Chunk header size: %d bytes, read: %d bytes. Exceeding bytes: %X",
805-
parser.headerSize(), readHeaderSize, exceedingBI));
786+
private byte[] readExceedingBytes(String name, int size, int bytesRead) throws IOException {
787+
int bytesExceeding = size - bytesRead;
788+
if (bytesExceeding > 0) {
789+
byte[] buf = mIn.readBytes(bytesExceeding);
790+
for (int i = 0; i < buf.length; i++) {
791+
if (buf[i] != 0) {
792+
LOGGER.warning(String.format(
793+
"%s size: %d bytes, read: %d bytes. Exceeding bytes: %s",
794+
name, size, bytesRead, BaseEncoding.base16().encode(buf)));
795+
return buf;
796+
}
797+
}
806798
}
799+
return null;
807800
}
808801

809802
private void injectDummyEntrySpecs() throws AndrolibException {

brut.apktool/apktool-lib/src/main/java/brut/androlib/res/table/ResConfig.java

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
*/
1717
package brut.androlib.res.table;
1818

19+
import java.util.Arrays;
20+
1921
public class ResConfig {
2022
public static final int SDK_BASE = 1;
2123
public static final int SDK_BASE_1_1 = 2;
@@ -191,7 +193,9 @@ public class ResConfig {
191193
private final String mLocaleVariant;
192194
private final int mScreenLayout2;
193195
private final int mColorMode;
194-
private final String mLocaleNumberingSystem;
196+
//private final boolean mLocaleScriptWasComputed;
197+
//private final String mLocaleNumberingSystem;
198+
private final byte[] mUnknown;
195199

196200
private final String mQualifiers;
197201
private boolean mIsInvalid;
@@ -221,7 +225,9 @@ private ResConfig() {
221225
mLocaleVariant = "";
222226
mScreenLayout2 = 0;
223227
mColorMode = COLOR_MODE_WIDECG_ANY | COLOR_MODE_HDR_ANY;
224-
mLocaleNumberingSystem = "";
228+
//mLocaleScriptWasComputed = false;
229+
//mLocaleNumberingSystem = "";
230+
mUnknown = null;
225231
mQualifiers = "";
226232
}
227233

@@ -230,7 +236,8 @@ public ResConfig(int mcc, int mnc, String language, String region, int orientati
230236
int grammaticalInflection, int screenWidth, int screenHeight, int sdkVersion,
231237
int minorVersion, int screenLayout, int uiMode, int smallestScreenWidthDp,
232238
int screenWidthDp, int screenHeightDp, String localeScript, String localeVariant,
233-
int screenLayout2, int colorMode, String localeNumberingSystem) {
239+
int screenLayout2, int colorMode, /*boolean localeScriptWasComputed,
240+
String localeNumberingSystem,*/ byte[] unknown) {
234241
mMcc = mcc;
235242
mMnc = mnc;
236243
mLanguage = language;
@@ -255,7 +262,9 @@ public ResConfig(int mcc, int mnc, String language, String region, int orientati
255262
mLocaleVariant = localeVariant;
256263
mScreenLayout2 = screenLayout2;
257264
mColorMode = colorMode;
258-
mLocaleNumberingSystem = localeNumberingSystem;
265+
//mLocaleScriptWasComputed = localeScriptWasComputed;
266+
//mLocaleNumberingSystem = localeNumberingSystem;
267+
mUnknown = unknown;
259268
mQualifiers = generateQualifiers();
260269
}
261270

@@ -612,7 +621,11 @@ private String generateQualifiers() {
612621
// sb.append('.').append(mMinorVersion);
613622
//}
614623
}
615-
624+
if (mUnknown != null) {
625+
// We have to separate unknown resources to avoid conflicts.
626+
sb.append("-unk").append(String.format("%08X", Arrays.hashCode(mUnknown)));
627+
mIsInvalid = true;
628+
}
616629
return sb.toString();
617630
}
618631

brut.apktool/apktool-lib/src/main/java/brut/androlib/res/table/ResPackage.java

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -204,21 +204,11 @@ public ResEntry getEntry(ResId id, ResConfig config) throws UndefinedResObjectEx
204204
}
205205

206206
public ResEntry addEntry(ResId id, ResConfig config, ResValue value) throws AndrolibException {
207-
return addEntry(id, config, value, false);
208-
}
209-
210-
public ResEntry addEntry(ResId id, ResConfig config, ResValue value, boolean overwrite)
211-
throws AndrolibException {
212207
Pair<ResId, ResConfig> entryKey = Pair.of(id, config);
213208
ResEntry entry = mEntries.get(entryKey);
214209
if (entry != null) {
215-
if (overwrite) {
216-
LOGGER.warning(String.format(
217-
"Overwriting repeated entry: id=%s, config=%s", id, config));
218-
} else {
219-
throw new AndrolibException(String.format(
220-
"Repeated entry: id=%s, config=%s", id, config));
221-
}
210+
throw new AndrolibException(String.format(
211+
"Repeated entry: id=%s, config=%s", id, config));
222212
}
223213

224214
ResEntrySpec entrySpec = getEntrySpec(id);
@@ -245,19 +235,33 @@ public Collection<ResEntry> listEntries() {
245235
return mEntries.values();
246236
}
247237

248-
public ResOverlayable addOverlayable(String name, String actor) {
238+
public boolean hasOverlayable(String name) {
239+
return mOverlayables.containsKey(name);
240+
}
241+
242+
public ResOverlayable getOverlayable(String name) throws UndefinedResObjectException {
243+
ResOverlayable overlayable = mOverlayables.get(name);
244+
if (overlayable == null) {
245+
throw new UndefinedResObjectException(String.format("overlayable: name=%s", name));
246+
}
247+
return overlayable;
248+
}
249+
250+
public ResOverlayable addOverlayable(String name, String actor) throws AndrolibException {
249251
ResOverlayable overlayable = mOverlayables.get(name);
250252
if (overlayable != null) {
251-
LOGGER.warning(String.format(
252-
"Repeated overlayable: name=%s, actor=%s", name, actor));
253-
return overlayable;
253+
throw new AndrolibException(String.format("Repeated overlayable: name=%s", name));
254254
}
255255

256256
overlayable = new ResOverlayable(this, name, actor);
257257
mOverlayables.put(name, overlayable);
258258
return overlayable;
259259
}
260260

261+
public int getOverlayableCount() {
262+
return mOverlayables.size();
263+
}
264+
261265
public Collection<ResOverlayable> listOverlayables() {
262266
return mOverlayables.values();
263267
}

0 commit comments

Comments
 (0)