|
27 | 27 | import brut.androlib.res.table.*; |
28 | 28 | import brut.androlib.res.table.value.*; |
29 | 29 | import brut.util.BinaryDataInputStream; |
| 30 | +import com.google.common.io.BaseEncoding; |
30 | 31 | import org.apache.commons.lang3.tuple.Pair; |
31 | 32 |
|
32 | 33 | import java.io.*; |
33 | | -import java.math.BigInteger; |
34 | 34 | import java.nio.charset.StandardCharsets; |
35 | 35 | import java.util.*; |
36 | 36 | import java.util.logging.Logger; |
@@ -63,8 +63,6 @@ public class BinaryResourceParser { |
63 | 63 | // This should not be encountered in most cases (#3993) |
64 | 64 | private static final int ENTRY_FLAG_FEATUREFLAG = 0x0010; |
65 | 65 |
|
66 | | - private static final int CONFIG_KNOWN_MAX_SIZE = 64; |
67 | | - |
68 | 66 | private final ResTable mTable; |
69 | 67 | private final boolean mKeepBroken; |
70 | 68 | private final boolean mRecordFlagsOffsets; |
@@ -319,12 +317,14 @@ private void parseType(ResChunkPullParser parser) throws AndrolibException, IOEx |
319 | 317 | String typeName = typeSpec.getName(); |
320 | 318 | ResType type; |
321 | 319 | if (mInvalidConfigs.contains(config)) { |
322 | | - String dirName = typeName + config.getQualifiers(); |
323 | 320 | if (mKeepBroken) { |
324 | | - LOGGER.warning("Invalid resource config detected: " + dirName); |
| 321 | + LOGGER.warning(String.format( |
| 322 | + "Invalid resource config detected: %s %s", typeName, config)); |
325 | 323 | type = mPackage.addType(id, config); |
326 | 324 | } 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)); |
328 | 328 | type = null; |
329 | 329 | } |
330 | 330 | } else { |
@@ -425,16 +425,18 @@ private void parseType(ResChunkPullParser parser) throws AndrolibException, IOEx |
425 | 425 | continue; |
426 | 426 | } |
427 | 427 |
|
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)) { |
432 | 436 | mPackage.addEntrySpec(entryId, mKeyStrings.getString(key)); |
433 | 437 | mMissingEntrySpecs.remove(entryId); |
434 | | - overwrite = false; |
435 | 438 | } |
436 | | - |
437 | | - mPackage.addEntry(entryId, config, value, overwrite); |
| 439 | + mPackage.addEntry(entryId, config, value); |
438 | 440 | } |
439 | 441 | } |
440 | 442 |
|
@@ -526,45 +528,29 @@ private ResConfig parseConfig() throws AndrolibException, IOException { |
526 | 528 | mIn.skipShort(); // screenConfigPad2 |
527 | 529 | } |
528 | 530 |
|
| 531 | + // Locale extensions are currently not supported by aapt2. |
| 532 | + // Data beyond this point may be repurposed by certain vendors. |
| 533 | + /*boolean localeScriptWasComputed = false; |
529 | 534 | String localeNumberingSystem = ""; |
530 | | - if (size >= 60) { |
| 535 | + if (size >= 64) { |
| 536 | + localeScriptWasComputed = mIn.readUnsignedByte() != 0; |
531 | 537 | localeNumberingSystem = mIn.readAscii(8); |
532 | | - } |
| 538 | + mIn.skipBytes(3); // endPadding |
| 539 | + }*/ |
533 | 540 |
|
534 | | - boolean isInvalid = false; |
535 | 541 | 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); |
558 | 543 |
|
559 | 544 | ResConfig config = new ResConfig( |
560 | 545 | mcc, mnc, language, region, orientation, |
561 | 546 | touchscreen, density, keyboard, navigation, inputFlags, |
562 | 547 | grammaticalInflection, screenWidth, screenHeight, sdkVersion, |
563 | 548 | minorVersion, screenLayout, uiMode, smallestScreenWidthDp, |
564 | 549 | screenWidthDp, screenHeightDp, localeScript, localeVariant, |
565 | | - screenLayout2, colorMode, localeNumberingSystem); |
| 550 | + screenLayout2, colorMode, /*localeScriptWasComputed, |
| 551 | + localeNumberingSystem,*/ unknown); |
566 | 552 |
|
567 | | - if (isInvalid || config.isInvalid()) { |
| 553 | + if (config.isInvalid()) { |
568 | 554 | mInvalidConfigs.add(config); |
569 | 555 | } |
570 | 556 |
|
@@ -725,7 +711,13 @@ private void parseOverlayable(ResChunkPullParser parser) throws AndrolibExceptio |
725 | 711 |
|
726 | 712 | skipUnreadHeader(parser); |
727 | 713 |
|
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 | + } |
729 | 721 |
|
730 | 722 | parser = new ResChunkPullParser(mIn, parser.dataSize()); |
731 | 723 | while (nextChunk(parser)) { |
@@ -787,23 +779,24 @@ private void skipUnreadHeader(ResChunkPullParser parser) throws IOException { |
787 | 779 | // Trusting the header size is misleading, so compare to what we actually read in the |
788 | 780 | // header vs reported and skip the rest. However, this runs after each chunk and not |
789 | 781 | // 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 | + } |
795 | 785 |
|
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 | + } |
806 | 798 | } |
| 799 | + return null; |
807 | 800 | } |
808 | 801 |
|
809 | 802 | private void injectDummyEntrySpecs() throws AndrolibException { |
|
0 commit comments