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>
288 lines
6.6 KiB
Go
288 lines
6.6 KiB
Go
// 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
|
|
}
|