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>
This commit is contained in:
parent
d6f00813f9
commit
cc29d844e9
|
@ -50,6 +50,19 @@ import (
|
|||
"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 @@ func (t ResType) IsSupported() bool {
|
|||
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 @@ const (
|
|||
ResTablePackage ResType = 0x0200
|
||||
ResTableType ResType = 0x0201
|
||||
ResTableTypeSpec ResType = 0x0202
|
||||
ResTableLibrary ResType = 0x0203
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -133,6 +148,7 @@ func (hdr chunkHeader) MarshalBinary() ([]byte, error) {
|
|||
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)
|
||||
|
|
|
@ -8,14 +8,14 @@ const (
|
|||
_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 @@ func (i ResType) String() string {
|
|||
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 {
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
package binres
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"encoding"
|
||||
"encoding/xml"
|
||||
|
@ -15,6 +14,7 @@ import (
|
|||
"log"
|
||||
"math"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -146,11 +146,11 @@ func retset(xs []string) []string {
|
|||
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 @@ func TestEncode(t *testing.T) {
|
|||
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 TestEncode(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue