internal/binres: provide decoding of resources.arsc in platform sdk
This decodes enough information to allow validation of resource
names but not their values, though a simple method for querying
does not yet exist.
Change-Id: I32023f3de0eda390b12d5c1df6c06202ee4d0778
Reviewed-on: https://go-review.googlesource.com/18055
Reviewed-by: David Crawshaw <crawshaw@golang.org>
diff --git a/internal/binres/binres.go b/internal/binres/binres.go
index d9e792f..2c745aa 100644
--- a/internal/binres/binres.go
+++ b/internal/binres/binres.go
@@ -50,6 +50,19 @@
"fmt"
)
+type ErrWrongType struct {
+ have ResType
+ want []ResType
+}
+
+func errWrongType(have ResType, want ...ResType) ErrWrongType {
+ return ErrWrongType{have, want}
+}
+
+func (err ErrWrongType) Error() string {
+ return fmt.Sprintf("wrong resource type %s, want one of %v", err.have, err.want)
+}
+
type ResType uint16
func (t ResType) IsSupported() bool {
@@ -59,7 +72,8 @@
t == ResXMLStartElement || t == ResXMLEndElement ||
t == ResXMLCharData ||
t == ResXMLResourceMap ||
- t == ResTable || t == ResTablePackage
+ t == ResTable || t == ResTablePackage ||
+ t == ResTableTypeSpec || t == ResTableType
}
// explicitly defined for clarity and resolvability with apt source
@@ -80,6 +94,7 @@
ResTablePackage ResType = 0x0200
ResTableType ResType = 0x0201
ResTableTypeSpec ResType = 0x0202
+ ResTableLibrary ResType = 0x0203
)
var (
@@ -133,6 +148,7 @@
if !hdr.typ.IsSupported() {
return nil, fmt.Errorf("%s not supported", hdr.typ)
}
+
bin := make([]byte, 8)
putu16(bin, uint16(hdr.typ))
putu16(bin[2:], hdr.headerByteSize)
diff --git a/internal/binres/binres_string.go b/internal/binres/binres_string.go
index d1d08a8..3e8d891 100644
--- a/internal/binres/binres_string.go
+++ b/internal/binres/binres_string.go
@@ -8,14 +8,14 @@
_ResType_name_0 = "ResNullResStringPoolResTableResXML"
_ResType_name_1 = "ResXMLStartNamespaceResXMLEndNamespaceResXMLStartElementResXMLEndElementResXMLCharData"
_ResType_name_2 = "ResXMLResourceMap"
- _ResType_name_3 = "ResTablePackageResTableTypeResTableTypeSpec"
+ _ResType_name_3 = "ResTablePackageResTableTypeResTableTypeSpecResTableLibrary"
)
var (
_ResType_index_0 = [...]uint8{7, 20, 28, 34}
_ResType_index_1 = [...]uint8{20, 38, 56, 72, 86}
_ResType_index_2 = [...]uint8{17}
- _ResType_index_3 = [...]uint8{15, 27, 43}
+ _ResType_index_3 = [...]uint8{15, 27, 43, 58}
)
func (i ResType) String() string {
@@ -35,7 +35,7 @@
return _ResType_name_1[lo:_ResType_index_1[i]]
case i == 384:
return _ResType_name_2
- case 512 <= i && i <= 514:
+ case 512 <= i && i <= 515:
i -= 512
lo := uint8(0)
if i > 0 {
diff --git a/internal/binres/binres_test.go b/internal/binres/binres_test.go
index 739e1e0..73571bc 100644
--- a/internal/binres/binres_test.go
+++ b/internal/binres/binres_test.go
@@ -5,7 +5,6 @@
package binres
import (
- "archive/zip"
"bytes"
"encoding"
"encoding/xml"
@@ -15,6 +14,7 @@
"log"
"math"
"os"
+ "path"
"sort"
"strings"
"testing"
@@ -146,11 +146,11 @@
return fo
}
-type ByNamespace []xml.Attr
+type byNamespace []xml.Attr
-func (a ByNamespace) Len() int { return len(a) }
-func (a ByNamespace) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
-func (a ByNamespace) Less(i, j int) bool {
+func (a byNamespace) Len() int { return len(a) }
+func (a byNamespace) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+func (a byNamespace) Less(i, j int) bool {
if a[j].Name.Space == "" {
return a[i].Name.Space != ""
}
@@ -191,18 +191,18 @@
Space: "",
Local: "platformBuildVersionCode",
},
- Value: "10",
+ Value: "15",
}
bvn := xml.Attr{
Name: xml.Name{
Space: "",
Local: "platformBuildVersionName",
},
- Value: "2.3.3",
+ Value: "4.0.3",
}
attrs = append(attrs, bvc, bvn)
- sort.Sort(ByNamespace(attrs))
+ sort.Sort(byNamespace(attrs))
var names, vals []string
for _, attr := range attrs {
if strings.HasSuffix(attr.Name.Space, "tools") {
@@ -228,44 +228,41 @@
}
}
-func TestAndroidJar(t *testing.T) {
- zr, err := zip.OpenReader("/home/daniel/local/android-sdk/platforms/android-10/android.jar")
+func TestOpenTable(t *testing.T) {
+ sdkdir := os.Getenv("ANDROID_HOME")
+ if sdkdir == "" {
+ t.Fatal("ANDROID_HOME env var not set")
+ }
+ tbl, err := OpenTable(path.Join(sdkdir, "platforms/android-15/android.jar"))
if err != nil {
t.Fatal(err)
}
- defer zr.Close()
+ if len(tbl.pkgs) == 0 {
+ t.Fatal("failed to decode any resource packages")
+ }
- buf := new(bytes.Buffer)
- for _, f := range zr.File {
- if f.Name == "resources.arsc" {
- rc, err := f.Open()
- if err != nil {
- t.Fatal(err)
+ pkg := tbl.pkgs[0]
+
+ t.Log("package name:", pkg.name)
+
+ for i, x := range pkg.typePool.strings {
+ t.Logf("typePool[i=%v]: %s\n", i, x)
+ }
+
+ for i, spec := range pkg.specs {
+ t.Logf("spec[i=%v]: %v %q\n", i, spec.id, pkg.typePool.strings[spec.id-1])
+ for j, typ := range spec.types {
+ t.Logf("\ttype[i=%v]: %v\n", j, typ.id)
+ for k, nt := range typ.entries {
+ if nt == nil { // NoEntry
+ continue
+ }
+ t.Logf("\t\tentry[i=%v]: %v %q\n", k, nt.key, pkg.keyPool.strings[nt.key])
+ if k > 5 {
+ t.Logf("\t\t... truncating output")
+ break
+ }
}
- _, err = io.Copy(buf, rc)
- if err != nil {
- t.Fatal(err)
- }
- rc.Close()
- break
}
}
- if buf.Len() == 0 {
- t.Fatal("failed to read resources.arsc")
- }
-
- bin := buf.Bytes()
-
- tbl := &Table{}
- if err := tbl.UnmarshalBinary(bin); err != nil {
- t.Fatal(err)
- }
-
- t.Logf("%+v\n", tbl.TableHeader)
- t.Logf("%+v\n", tbl.pool.chunkHeader)
- for i, x := range tbl.pool.strings[:10] {
- t.Logf("pool(%v) %s\n", i, x)
- }
-
- t.Logf("%+v\n", tbl.pkg)
}
diff --git a/internal/binres/package.go b/internal/binres/package.go
deleted file mode 100644
index 8b967dd..0000000
--- a/internal/binres/package.go
+++ /dev/null
@@ -1,101 +0,0 @@
-// Copyright 2015 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package binres
-
-type TableHeader struct {
- chunkHeader
- packageCount uint32
-}
-
-func (hdr *TableHeader) UnmarshalBinary(bin []byte) error {
- if err := (&hdr.chunkHeader).UnmarshalBinary(bin); err != nil {
- return err
- }
- hdr.packageCount = btou32(bin[8:])
- return nil
-}
-
-func (hdr TableHeader) MarshalBinary() ([]byte, error) {
- bin := make([]byte, 12)
- b, err := hdr.chunkHeader.MarshalBinary()
- if err != nil {
- return nil, err
- }
- copy(bin, b)
- putu32(b[8:], hdr.packageCount)
- return bin, nil
-}
-
-// TODO next up: package chunk
-// https://justanapplication.wordpress.com/2011/09/16/
-type Table struct {
- TableHeader
- pool *Pool
- pkg *Package
-}
-
-func (tbl *Table) UnmarshalBinary(bin []byte) error {
- buf := bin
- if err := (&tbl.TableHeader).UnmarshalBinary(buf); err != nil {
- return err
- }
- buf = buf[tbl.headerByteSize:]
- tbl.pool = new(Pool)
- if err := tbl.pool.UnmarshalBinary(buf); err != nil {
- return err
- }
- buf = buf[tbl.pool.size():]
- tbl.pkg = new(Package)
- if err := tbl.pkg.UnmarshalBinary(buf); err != nil {
- return err
- }
- return nil
-}
-
-type Package struct {
- chunkHeader
-
- // If this is a base package, its ID. Package IDs start
- // at 1 (corresponding to the value of the package bits in a
- // resource identifier). 0 means this is not a base package.
- id uint32
-
- // name of package, zero terminated
- name [128]uint16
-
- // Offset to a ResStringPool_header defining the resource
- // type symbol table. If zero, this package is inheriting from
- // another base package (overriding specific values in it).
- typeStrings uint32
-
- // Last index into typeStrings that is for public use by others.
- lastPublicType uint32
-
- // Offset to a ResStringPool_header defining the resource
- // key symbol table. If zero, this package is inheriting from
- // another base package (overriding specific values in it).
- keyStrings uint32
-
- // Last index into keyStrings that is for public use by others.
- lastPublicKey uint32
-
- typeIdOffset uint32
-}
-
-func (pkg *Package) UnmarshalBinary(bin []byte) error {
- if err := (&pkg.chunkHeader).UnmarshalBinary(bin); err != nil {
- return err
- }
- pkg.id = btou32(bin[8:])
- for i := range pkg.name {
- pkg.name[i] = btou16(bin[12+i*2:])
- }
- pkg.typeStrings = btou32(bin[140:])
- pkg.lastPublicType = btou32(bin[144:])
- pkg.keyStrings = btou32(bin[148:])
- pkg.lastPublicKey = btou32(bin[152:])
- pkg.typeIdOffset = btou32(bin[156:])
- return nil
-}
diff --git a/internal/binres/table.go b/internal/binres/table.go
new file mode 100644
index 0000000..25015bd
--- /dev/null
+++ b/internal/binres/table.go
@@ -0,0 +1,287 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package binres
+
+import (
+ "archive/zip"
+ "bytes"
+ "fmt"
+ "io"
+ "unicode/utf16"
+)
+
+const NoEntry = 0xFFFFFFFF
+
+// TableRef uniquely identifies entries within a resource table.
+type TableRef uint32
+
+// Table is a container for packaged resources. Resource values within a package
+// are obtained through pool while resource names and identifiers are obtained
+// through each package's type and key pools respectively.
+//
+// TODO provide method for querying resource types.
+type Table struct {
+ chunkHeader
+ pool *Pool
+ pkgs []*Package
+}
+
+// OpenTable decodes resources.arsc from an sdk platform jar.
+func OpenTable(path string) (*Table, error) {
+ zr, err := zip.OpenReader(path)
+ if err != nil {
+ return nil, err
+ }
+ defer zr.Close()
+
+ buf := new(bytes.Buffer)
+ for _, f := range zr.File {
+ if f.Name == "resources.arsc" {
+ rc, err := f.Open()
+ if err != nil {
+ return nil, err
+ }
+ _, err = io.Copy(buf, rc)
+ if err != nil {
+ return nil, err
+ }
+ rc.Close()
+ break
+ }
+ }
+ if buf.Len() == 0 {
+ return nil, fmt.Errorf("failed to read resources.arsc")
+ }
+
+ tbl := new(Table)
+ if err := tbl.UnmarshalBinary(buf.Bytes()); err != nil {
+ return nil, err
+ }
+ return tbl, nil
+}
+
+func (tbl *Table) UnmarshalBinary(bin []byte) error {
+ if err := (&tbl.chunkHeader).UnmarshalBinary(bin); err != nil {
+ return err
+ }
+ if tbl.typ != ResTable {
+ return fmt.Errorf("unexpected resource type %s, want %s", tbl.typ, ResTable)
+ }
+
+ npkgs := btou32(bin[8:])
+ tbl.pkgs = make([]*Package, npkgs)
+
+ buf := bin[tbl.headerByteSize:]
+
+ tbl.pool = new(Pool)
+ if err := tbl.pool.UnmarshalBinary(buf); err != nil {
+ return err
+ }
+ buf = buf[tbl.pool.size():]
+ for i := range tbl.pkgs {
+ pkg := new(Package)
+ if err := pkg.UnmarshalBinary(buf); err != nil {
+ return err
+ }
+ tbl.pkgs[i] = pkg
+ buf = buf[pkg.byteSize:]
+ }
+
+ return nil
+}
+
+// Package contains a collection of resource data types.
+type Package struct {
+ chunkHeader
+
+ id uint32
+ name string
+
+ lastPublicType uint32 // last index into typePool that is for public use
+ lastPublicKey uint32 // last index into keyPool that is for public use
+
+ typePool *Pool // type names; e.g. theme
+ keyPool *Pool // resource names; e.g. Theme.NoTitleBar
+
+ specs []*TypeSpec
+}
+
+func (pkg *Package) UnmarshalBinary(bin []byte) error {
+ if err := (&pkg.chunkHeader).UnmarshalBinary(bin); err != nil {
+ return err
+ }
+ if pkg.typ != ResTablePackage {
+ return errWrongType(pkg.typ, ResTablePackage)
+ }
+
+ pkg.id = btou32(bin[8:])
+
+ var name []uint16
+ for i := 0; i < 128; i++ {
+ x := btou16(bin[12+i*2:])
+ if x == 0 {
+ break
+ }
+ name = append(name, x)
+ }
+ pkg.name = string(utf16.Decode(name))
+
+ typeOffset := btou32(bin[268:]) // 0 if inheriting from another package
+ pkg.lastPublicType = btou32(bin[272:])
+ keyOffset := btou32(bin[276:]) // 0 if inheriting from another package
+ pkg.lastPublicKey = btou32(bin[280:])
+
+ var idOffset uint32 // value determined by either typePool or keyPool below
+
+ if typeOffset != 0 {
+ pkg.typePool = new(Pool)
+ if err := pkg.typePool.UnmarshalBinary(bin[typeOffset:]); err != nil {
+ return err
+ }
+ idOffset = typeOffset + pkg.typePool.byteSize
+ }
+
+ if keyOffset != 0 {
+ pkg.keyPool = new(Pool)
+ if err := pkg.keyPool.UnmarshalBinary(bin[keyOffset:]); err != nil {
+ return err
+ }
+ idOffset = keyOffset + pkg.keyPool.byteSize
+ }
+
+ if idOffset == 0 {
+ return nil
+ }
+
+ buf := bin[idOffset:]
+ for len(pkg.specs) < len(pkg.typePool.strings) {
+ t := ResType(btou16(buf))
+ switch t {
+ case ResTableTypeSpec:
+ spec := new(TypeSpec)
+ if err := spec.UnmarshalBinary(buf); err != nil {
+ return err
+ }
+ pkg.specs = append(pkg.specs, spec)
+ buf = buf[spec.byteSize:]
+ case ResTableType:
+ typ := new(Type)
+ if err := typ.UnmarshalBinary(buf); err != nil {
+ return err
+ }
+ last := pkg.specs[len(pkg.specs)-1]
+ last.types = append(last.types, typ)
+ buf = buf[typ.byteSize:]
+ default:
+ return errWrongType(t, ResTableTypeSpec, ResTableType)
+ }
+ }
+
+ return nil
+}
+
+// TypeSpec provides a specification for the resources defined by a particular type.
+type TypeSpec struct {
+ chunkHeader
+ id uint8 // id-1 is name index in Package.typePool
+ res0 uint8 // must be 0
+ res1 uint16 // must be 0
+ entryCount uint32 // number of uint32 entry configuration masks that follow
+
+ entries []uint32
+ types []*Type
+}
+
+func (spec *TypeSpec) UnmarshalBinary(bin []byte) error {
+ if err := (&spec.chunkHeader).UnmarshalBinary(bin); err != nil {
+ return err
+ }
+ if spec.typ != ResTableTypeSpec {
+ return errWrongType(spec.typ, ResTableTypeSpec)
+ }
+ spec.id = uint8(bin[8])
+ spec.res0 = uint8(bin[9])
+ spec.res1 = btou16(bin[10:])
+ spec.entryCount = btou32(bin[12:])
+
+ spec.entries = make([]uint32, spec.entryCount)
+ for i := range spec.entries {
+ spec.entries[i] = btou32(bin[16+i*4:])
+ }
+
+ return nil
+}
+
+type Type struct {
+ chunkHeader
+ id uint8
+ res0 uint8 // must be 0
+ res1 uint16 // must be 0
+ entryCount uint32 // number of uint32 entry configuration masks that follow
+ entriesStart uint32 // offset from header where Entry data starts
+
+ // TODO implement and decode Config if strictly necessary
+ // config Config // configuration this collection of entries is designed for
+
+ indices []uint32 // values that map to typePool
+ entries []*Entry
+}
+
+func (typ *Type) UnmarshalBinary(bin []byte) error {
+ if err := (&typ.chunkHeader).UnmarshalBinary(bin); err != nil {
+ return err
+ }
+ if typ.typ != ResTableType {
+ return errWrongType(typ.typ, ResTableType)
+ }
+
+ typ.id = uint8(bin[8])
+ typ.res0 = uint8(bin[9])
+ typ.res1 = btou16(bin[10:])
+ typ.entryCount = btou32(bin[12:])
+ typ.entriesStart = btou32(bin[16:])
+
+ if typ.res0 != 0 || typ.res1 != 0 {
+ return fmt.Errorf("res0 res1 not zero")
+ }
+
+ buf := bin[typ.headerByteSize:typ.entriesStart]
+ for len(buf) > 0 {
+ typ.indices = append(typ.indices, btou32(buf))
+ buf = buf[4:]
+ }
+
+ if len(typ.indices) != int(typ.entryCount) {
+ return fmt.Errorf("indices len[%v] doesn't match entryCount[%v]", len(typ.indices), typ.entryCount)
+ }
+ typ.entries = make([]*Entry, typ.entryCount)
+
+ for i, x := range typ.indices {
+ if x == NoEntry {
+ continue
+ }
+ nt := &Entry{}
+ if err := nt.UnmarshalBinary(bin[typ.entriesStart+x:]); err != nil {
+ return err
+ }
+ typ.entries[i] = nt
+ }
+
+ return nil
+}
+
+// Entry is a resource key typically followed by a value or resource map.
+type Entry struct {
+ size uint16
+ flags uint16
+ key PoolRef // ref into key pool
+}
+
+func (nt *Entry) UnmarshalBinary(bin []byte) error {
+ nt.size = btou16(bin)
+ nt.flags = btou16(bin[2:])
+ nt.key = PoolRef(btou32(bin[4:]))
+ return nil
+}