Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 50 additions & 15 deletions resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ type resourceType struct {
entryCount uint32
entriesStart uint32
indexesStart uint32
flags uint8

// ResTable_config config;
}
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
Expand Down
99 changes: 99 additions & 0 deletions resources_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}