diff --git a/resources.go b/resources.go index 53e5d9b..7d20f03 100644 --- a/resources.go +++ b/resources.go @@ -54,6 +54,7 @@ type resourceType struct { entryCount uint32 entriesStart uint32 indexesStart uint32 + flags uint8 // ResTable_config config; } @@ -62,6 +63,8 @@ const ( tableEntryComplex = 0x0001 tableEntryPublic = 0x0002 tableEntryWeak = 0x0004 + + tableTypeFlagSparse = 0x01 ) // Describes one resource entry, for example @drawable/icon in the original XML, in one particular config option. @@ -369,9 +372,9 @@ func (x *ResourceTable) parseTypeSpec(r *io.LimitedReader, pkg *resourcePackage, func (x *ResourceTable) parseType(r io.Reader, pkg *resourcePackage, group *packageGroup, chunkData []byte, hdrLen uint16) error { vals := struct { - Id uint8 - Res0 uint8 - Res1 uint16 + Id uint8 + Flags uint8 + Res1 uint16 EntryCount uint32 EntriesStart uint32 @@ -399,6 +402,7 @@ func (x *ResourceTable) parseType(r io.Reader, pkg *resourcePackage, group *pack entryCount: vals.EntryCount, entriesStart: vals.EntriesStart, indexesStart: uint32(hdrLen), + flags: vals.Flags, }) } return nil @@ -508,21 +512,12 @@ func (x *ResourceTable) getEntryConfigs(group *packageGroup, typeId, entry uint3 var entries []*ResourceEntry for _, typ := range typeList { for _, thisType := range typ.Configs { - if entry >= thisType.entryCount { - continue - } - r := bytes.NewReader(thisType.chunkData) - if _, err := r.Seek(int64(thisType.indexesStart+entry*4), io.SeekStart); err != nil { + thisOffset, ok, err := resourceEntryOffset(r, thisType, entry) + if err != nil { return nil, err } - - var thisOffset uint32 - if err := binary.Read(r, binary.LittleEndian, &thisOffset); err != nil { - return nil, fmt.Errorf("Failed to read this type offset: %s", err.Error()) - } - - if thisOffset == math.MaxUint32 { + if !ok { continue } @@ -556,6 +551,46 @@ exit: return entries, lastErr } +func resourceEntryOffset(r io.ReadSeeker, typ *resourceType, entry uint32) (uint32, bool, error) { + if typ.flags&tableTypeFlagSparse == 0 { + if entry >= typ.entryCount { + return 0, false, nil + } + if _, err := r.Seek(int64(typ.indexesStart+entry*4), io.SeekStart); err != nil { + return 0, false, err + } + + var offset uint32 + if err := binary.Read(r, binary.LittleEndian, &offset); err != nil { + return 0, false, fmt.Errorf("Failed to read this type offset: %s", err.Error()) + } + if offset == math.MaxUint32 { + return 0, false, nil + } + return offset, true, nil + } + + if _, err := r.Seek(int64(typ.indexesStart), io.SeekStart); err != nil { + return 0, false, err + } + for i := uint32(0); i < typ.entryCount; i++ { + var index, offset uint16 + if err := binary.Read(r, binary.LittleEndian, &index); err != nil { + return 0, false, fmt.Errorf("Failed to read sparse type index: %s", err.Error()) + } + if err := binary.Read(r, binary.LittleEndian, &offset); err != nil { + return 0, false, fmt.Errorf("Failed to read sparse type offset: %s", err.Error()) + } + if uint32(index) == entry { + return uint32(offset) * 4, true, nil + } + if uint32(index) > entry { + break + } + } + return 0, false, nil +} + func (x *ResourceTable) parseEntry(r io.Reader, pkg *resourcePackage, typeId uint32) (*ResourceEntry, error) { var err error var res ResourceEntry diff --git a/resources_test.go b/resources_test.go new file mode 100644 index 0000000..a4ed4ef --- /dev/null +++ b/resources_test.go @@ -0,0 +1,99 @@ +package apkparser + +import ( + "bytes" + "encoding/binary" + "testing" +) + +func TestGetEntryConfigsSparseType(t *testing.T) { + table := &ResourceTable{ + mainStrings: stringTable{strings: []string{ + "res/mipmap-anydpi-v26/ic_launcher.xml", + "res/mipmap-anydpi-v26/ic_launcher_round.xml", + }}, + } + pkg := &resourcePackage{ + Name: "com.example", + typeStrings: stringTable{strings: []string{"mipmap"}}, + keyStrings: stringTable{strings: []string{"ic_launcher", "ic_launcher_round"}}, + } + group := &packageGroup{ + types: map[uint8][]resourceTypeSpec{ + 1: {{ + Id: 1, + Package: pkg, + Configs: []*resourceType{{ + chunkData: sparseTypeChunk(t), + entryCount: 2, + entriesStart: 92, + indexesStart: 84, + flags: tableTypeFlagSparse, + }}, + }}, + }, + } + + entries, err := table.getEntryConfigs(group, 0, 1, 1) + if err != nil { + t.Fatalf("get sparse entry 1: %v", err) + } + if len(entries) != 1 { + t.Fatalf("got %d entries, want 1", len(entries)) + } + got, err := entries[0].GetValue().String() + if err != nil { + t.Fatalf("string value: %v", err) + } + if want := "res/mipmap-anydpi-v26/ic_launcher.xml"; got != want { + t.Fatalf("got %q, want %q", got, want) + } + + entries, err = table.getEntryConfigs(group, 0, 2, 1) + if err != nil { + t.Fatalf("get sparse entry 2: %v", err) + } + got, err = entries[0].GetValue().String() + if err != nil { + t.Fatalf("string value: %v", err) + } + if want := "res/mipmap-anydpi-v26/ic_launcher_round.xml"; got != want { + t.Fatalf("got %q, want %q", got, want) + } +} + +func sparseTypeChunk(t *testing.T) []byte { + t.Helper() + + var b bytes.Buffer + b.Write(make([]byte, 84)) + writeSparseTypeEntry(t, &b, 1, 0) + writeSparseTypeEntry(t, &b, 2, 4) + writeTestResourceEntry(t, &b, 0, 0) + writeTestResourceEntry(t, &b, 1, 1) + return b.Bytes() +} + +func writeSparseTypeEntry(t *testing.T, b *bytes.Buffer, index, offset uint16) { + t.Helper() + writeValue(t, b, index) + writeValue(t, b, offset) +} + +func writeTestResourceEntry(t *testing.T, b *bytes.Buffer, keyIndex, stringIndex uint32) { + t.Helper() + writeValue(t, b, uint16(8)) + writeValue(t, b, uint16(0)) + writeValue(t, b, keyIndex) + writeValue(t, b, uint16(8)) + writeValue(t, b, uint8(0)) + writeValue(t, b, uint8(AttrTypeString)) + writeValue(t, b, stringIndex) +} + +func writeValue(t *testing.T, b *bytes.Buffer, v interface{}) { + t.Helper() + if err := binary.Write(b, binary.LittleEndian, v); err != nil { + t.Fatal(err) + } +}