// 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 ( "bytes" "compress/gzip" "errors" "fmt" "io" "strings" "unicode/utf16" ) const NoEntry = 0xFFFFFFFF // TableRef uniquely identifies entries within a resource table. type TableRef uint32 // Resolve returns the Entry of TableRef in the given table. // // A TableRef is structured as follows: // // 0xpptteeee // pp: package index // tt: type spec index in package // eeee: entry index in type spec // // The package and type spec values start at 1 for the first item, // to help catch cases where they have not been supplied. func (ref TableRef) Resolve(tbl *Table) (*Entry, error) { pkg := tbl.pkgs[uint8(ref>>24)-1] spec := pkg.specs[uint8(ref>>16)-1] idx := uint16(ref) for _, typ := range spec.types { if idx < uint16(len(typ.entries)) { nt := typ.entries[idx] if nt == nil { return nil, errors.New("nil entry match") } return nt, nil } } return nil, errors.New("failed to resolve table reference") } // 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. type Table struct { chunkHeader pool *Pool pkgs []*Package } // NewMipmapTable returns a resource table initialized for a single xxxhdpi mipmap resource // and the path to write resource data to. func NewMipmapTable(pkgname string) (*Table, string) { pkg := &Package{id: 127, name: pkgname, typePool: &Pool{}, keyPool: &Pool{}} attr := pkg.typePool.ref("attr") mipmap := pkg.typePool.ref("mipmap") icon := pkg.keyPool.ref("icon") nt := &Entry{values: []*Value{{data: &Data{Type: DataString}}}} typ := &Type{id: 2, indices: []uint32{0}, entries: []*Entry{nt}} typ.config.screenType.density = 640 typ.config.version.sdk = 4 pkg.specs = append(pkg.specs, &TypeSpec{ id: uint8(attr) + 1, //1, }, &TypeSpec{ id: uint8(mipmap) + 1, //2, entryCount: 1, entries: []uint32{uint32(icon)}, // {0} types: []*Type{typ}, }) pkg.lastPublicType = uint32(len(pkg.typePool.strings)) // 2 pkg.lastPublicKey = uint32(len(pkg.keyPool.strings)) // 1 name := "res/mipmap-xxxhdpi-v4/icon.png" tbl := &Table{pool: &Pool{}, pkgs: []*Package{pkg}} tbl.pool.ref(name) return tbl, name } // OpenSDKTable decodes resources.arsc from sdk platform jar. func OpenSDKTable() (*Table, error) { bin, err := apiResources() if err != nil { return nil, err } tbl := new(Table) if err := tbl.UnmarshalBinary(bin); err != nil { return nil, err } return tbl, nil } // OpenTable decodes the prepacked resources.arsc for the supported sdk platform. func OpenTable() (*Table, error) { zr, err := gzip.NewReader(bytes.NewReader(arsc)) if err != nil { return nil, fmt.Errorf("gzip: %v", err) } defer zr.Close() var buf bytes.Buffer if _, err := io.Copy(&buf, zr); err != nil { return nil, fmt.Errorf("io: %v", err) } tbl := new(Table) if err := tbl.UnmarshalBinary(buf.Bytes()); err != nil { return nil, err } return tbl, nil } // SpecByName parses the spec name from an entry string if necessary and returns // the Package and TypeSpec associated with that name along with their respective // indices. // // For example: // // tbl.SpecByName("@android:style/Theme.NoTitleBar") // tbl.SpecByName("style") // // Both locate the spec by name "style". func (tbl *Table) SpecByName(name string) (int, *Package, int, *TypeSpec, error) { n := strings.TrimPrefix(name, "@android:") n = strings.Split(n, "/")[0] for pp, pkg := range tbl.pkgs { for tt, spec := range pkg.specs { if n == pkg.typePool.strings[spec.id-1] { return pp, pkg, tt, spec, nil } } } return 0, nil, 0, nil, fmt.Errorf("spec by name not found: %q", name) } // RefByName returns the TableRef by a given name. The ref may be used to resolve the // associated Entry and is used for the generation of binary manifest files. func (tbl *Table) RefByName(name string) (TableRef, error) { pp, pkg, tt, spec, err := tbl.SpecByName(name) if err != nil { return 0, err } q := strings.Split(name, "/") if len(q) != 2 { return 0, fmt.Errorf("invalid entry format, missing forward-slash: %q", name) } n := q[1] for _, t := range spec.types { for eeee, nt := range t.entries { if nt == nil { // NoEntry continue } if n == pkg.keyPool.strings[nt.key] { return TableRef(uint32(eeee) | uint32(tt+1)<<16 | uint32(pp+1)<<24), nil } } } return 0, fmt.Errorf("failed to find table ref by %q", name) } 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 } func (tbl *Table) MarshalBinary() ([]byte, error) { bin := make([]byte, 12) putu16(bin, uint16(ResTable)) putu16(bin[2:], 12) putu32(bin[8:], uint32(len(tbl.pkgs))) if tbl.pool.IsUTF8() { tbl.pool.flags ^= UTF8Flag defer func() { tbl.pool.flags |= UTF8Flag }() } b, err := tbl.pool.MarshalBinary() if err != nil { return nil, err } bin = append(bin, b...) for _, pkg := range tbl.pkgs { b, err = pkg.MarshalBinary() if err != nil { return nil, err } bin = append(bin, b...) } putu32(bin[4:], uint32(len(bin))) return bin, 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 aliases []*StagedAlias 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:pkg.byteSize] for len(buf) > 0 { 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:] case ResTableStagedAlias: alias := new(StagedAlias) if err := alias.UnmarshalBinary(buf); err != nil { return err } pkg.aliases = append(pkg.aliases, alias) buf = buf[alias.byteSize:] default: return errWrongType(t, ResTableTypeSpec, ResTableType, ResTableStagedAlias) } } return nil } func (pkg *Package) MarshalBinary() ([]byte, error) { // Package header size is determined by C++ struct ResTable_package // see frameworks/base/include/ResourceTypes.h bin := make([]byte, 288) putu16(bin, uint16(ResTablePackage)) putu16(bin[2:], 288) putu32(bin[8:], pkg.id) p := utf16.Encode([]rune(pkg.name)) for i, x := range p { putu16(bin[12+i*2:], x) } if pkg.typePool != nil { if pkg.typePool.IsUTF8() { pkg.typePool.flags ^= UTF8Flag defer func() { pkg.typePool.flags |= UTF8Flag }() } b, err := pkg.typePool.MarshalBinary() if err != nil { return nil, err } putu32(bin[268:], uint32(len(bin))) putu32(bin[272:], pkg.lastPublicType) bin = append(bin, b...) } if pkg.keyPool != nil { if pkg.keyPool.IsUTF8() { pkg.keyPool.flags ^= UTF8Flag defer func() { pkg.keyPool.flags |= UTF8Flag }() } b, err := pkg.keyPool.MarshalBinary() if err != nil { return nil, err } putu32(bin[276:], uint32(len(bin))) putu32(bin[280:], pkg.lastPublicKey) bin = append(bin, b...) } for _, alias := range pkg.aliases { b, err := alias.MarshalBinary() if err != nil { return nil, err } bin = append(bin, b...) } for _, spec := range pkg.specs { b, err := spec.MarshalBinary() if err != nil { return nil, err } bin = append(bin, b...) } putu32(bin[4:], uint32(len(bin))) return bin, 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 // entry configuration masks 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 } func (spec *TypeSpec) MarshalBinary() ([]byte, error) { bin := make([]byte, 16+len(spec.entries)*4) putu16(bin, uint16(ResTableTypeSpec)) putu16(bin[2:], 16) putu32(bin[4:], uint32(len(bin))) bin[8] = byte(spec.id) // [9] = 0 // [10:12] = 0 putu32(bin[12:], uint32(len(spec.entries))) for i, x := range spec.entries { putu32(bin[16+i*4:], x) } for _, typ := range spec.types { b, err := typ.MarshalBinary() if err != nil { return nil, err } bin = append(bin, b...) } return bin, nil } // Type provides a collection of entries for a specific device configuration. 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 // configuration this collection of entries is designed for config struct { size uint32 imsi struct { mcc uint16 // mobile country code mnc uint16 // mobile network code } locale struct { language uint16 country uint16 } screenType struct { orientation uint8 touchscreen uint8 density uint16 } input struct { keyboard uint8 navigation uint8 inputFlags uint8 inputPad0 uint8 } screenSize struct { width uint16 height uint16 } version struct { sdk uint16 minor uint16 // always 0 } screenConfig struct { layout uint8 uiMode uint8 smallestWidthDP uint16 } screenSizeDP struct { width uint16 height uint16 } } 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") } typ.config.size = btou32(bin[20:]) typ.config.imsi.mcc = btou16(bin[24:]) typ.config.imsi.mnc = btou16(bin[26:]) typ.config.locale.language = btou16(bin[28:]) typ.config.locale.country = btou16(bin[30:]) typ.config.screenType.orientation = uint8(bin[32]) typ.config.screenType.touchscreen = uint8(bin[33]) typ.config.screenType.density = btou16(bin[34:]) typ.config.input.keyboard = uint8(bin[36]) typ.config.input.navigation = uint8(bin[37]) typ.config.input.inputFlags = uint8(bin[38]) typ.config.input.inputPad0 = uint8(bin[39]) typ.config.screenSize.width = btou16(bin[40:]) typ.config.screenSize.height = btou16(bin[42:]) typ.config.version.sdk = btou16(bin[44:]) typ.config.version.minor = btou16(bin[46:]) typ.config.screenConfig.layout = uint8(bin[48]) typ.config.screenConfig.uiMode = uint8(bin[49]) typ.config.screenConfig.smallestWidthDP = btou16(bin[50:]) typ.config.screenSizeDP.width = btou16(bin[52:]) typ.config.screenSizeDP.height = btou16(bin[54:]) // fmt.Println("language/country:", u16tos(typ.config.locale.language), u16tos(typ.config.locale.country)) buf := bin[typ.headerByteSize:typ.entriesStart] if len(buf) != 4*int(typ.entryCount) { return fmt.Errorf("index buffer len[%v] doesn't match entryCount[%v]", len(buf), typ.entryCount) } typ.indices = make([]uint32, typ.entryCount) for i := range typ.indices { typ.indices[i] = btou32(buf[i*4:]) } 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 } func (typ *Type) MarshalBinary() ([]byte, error) { bin := make([]byte, 56+len(typ.entries)*4) putu16(bin, uint16(ResTableType)) putu16(bin[2:], 56) bin[8] = byte(typ.id) // [9] = 0 // [10:12] = 0 putu32(bin[12:], uint32(len(typ.entries))) putu32(bin[16:], uint32(56+len(typ.entries)*4)) // assure typ.config.size is always written as 36; extended configuration beyond supported // API level is not supported by this marshal implementation but will be forward-compatible. putu32(bin[20:], 36) putu16(bin[24:], typ.config.imsi.mcc) putu16(bin[26:], typ.config.imsi.mnc) putu16(bin[28:], typ.config.locale.language) putu16(bin[30:], typ.config.locale.country) bin[32] = typ.config.screenType.orientation bin[33] = typ.config.screenType.touchscreen putu16(bin[34:], typ.config.screenType.density) bin[36] = typ.config.input.keyboard bin[37] = typ.config.input.navigation bin[38] = typ.config.input.inputFlags bin[39] = typ.config.input.inputPad0 putu16(bin[40:], typ.config.screenSize.width) putu16(bin[42:], typ.config.screenSize.height) putu16(bin[44:], typ.config.version.sdk) putu16(bin[46:], typ.config.version.minor) bin[48] = typ.config.screenConfig.layout bin[49] = typ.config.screenConfig.uiMode putu16(bin[50:], typ.config.screenConfig.smallestWidthDP) putu16(bin[52:], typ.config.screenSizeDP.width) putu16(bin[54:], typ.config.screenSizeDP.height) var ntbin []byte for i, nt := range typ.entries { if nt == nil { // NoEntry putu32(bin[56+i*4:], NoEntry) continue } putu32(bin[56+i*4:], uint32(len(ntbin))) b, err := nt.MarshalBinary() if err != nil { return nil, err } ntbin = append(ntbin, b...) } bin = append(bin, ntbin...) putu32(bin[4:], uint32(len(bin))) return bin, nil } type StagedAliasEntry struct { stagedID uint32 finalizedID uint32 } func (ae *StagedAliasEntry) MarshalBinary() ([]byte, error) { bin := make([]byte, 8) putu32(bin, ae.stagedID) putu32(bin[4:], ae.finalizedID) return bin, nil } func (ae *StagedAliasEntry) UnmarshalBinary(bin []byte) error { ae.stagedID = btou32(bin) ae.finalizedID = btou32(bin[4:]) return nil } type StagedAlias struct { chunkHeader count uint32 entries []StagedAliasEntry } func (a *StagedAlias) UnmarshalBinary(bin []byte) error { if err := (&a.chunkHeader).UnmarshalBinary(bin); err != nil { return err } if a.typ != ResTableStagedAlias { return errWrongType(a.typ, ResTableStagedAlias) } a.count = btou32(bin[8:]) a.entries = make([]StagedAliasEntry, a.count) for i := range a.entries { if err := a.entries[i].UnmarshalBinary(bin[12+i*8:]); err != nil { return err } } return nil } func (a *StagedAlias) MarshalBinary() ([]byte, error) { chunkHeaderBin, err := a.chunkHeader.MarshalBinary() if err != nil { return nil, err } countBin := make([]byte, 4) putu32(countBin, a.count) bin := append(chunkHeaderBin, countBin...) for _, entry := range a.entries { entryBin, err := entry.MarshalBinary() if err != nil { return nil, err } bin = append(bin, entryBin...) } return bin, 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 // only filled if this is a map entry; when size is 16 parent TableRef // id of parent mapping or zero if none count uint32 // name and value pairs that follow for FlagComplex values []*Value } func (nt *Entry) UnmarshalBinary(bin []byte) error { nt.size = btou16(bin) nt.flags = btou16(bin[2:]) nt.key = PoolRef(btou32(bin[4:])) if nt.size == 16 { nt.parent = TableRef(btou32(bin[8:])) nt.count = btou32(bin[12:]) nt.values = make([]*Value, nt.count) for i := range nt.values { val := &Value{} if err := val.UnmarshalBinary(bin[16+i*12:]); err != nil { return err } nt.values[i] = val } } else { data := &Data{} if err := data.UnmarshalBinary(bin[8:]); err != nil { return err } // TODO boxing data not strictly correct as binary repr isn't of Value. nt.values = append(nt.values, &Value{0, data}) } return nil } func (nt *Entry) MarshalBinary() ([]byte, error) { bin := make([]byte, 8) sz := nt.size if sz == 0 { sz = 8 } putu16(bin, sz) putu16(bin[2:], nt.flags) putu32(bin[4:], uint32(nt.key)) if sz == 16 { bin = append(bin, make([]byte, 8+len(nt.values)*12)...) putu32(bin[8:], uint32(nt.parent)) putu32(bin[12:], uint32(len(nt.values))) for i, val := range nt.values { b, err := val.MarshalBinary() if err != nil { return nil, err } copy(bin[16+i*12:], b) } } else { b, err := nt.values[0].data.MarshalBinary() if err != nil { return nil, err } bin = append(bin, b...) } return bin, nil } type Value struct { name TableRef data *Data } func (val *Value) UnmarshalBinary(bin []byte) error { val.name = TableRef(btou32(bin)) val.data = &Data{} return val.data.UnmarshalBinary(bin[4:]) } func (val *Value) MarshalBinary() ([]byte, error) { bin := make([]byte, 12) putu32(bin, uint32(val.name)) b, err := val.data.MarshalBinary() if err != nil { return nil, err } copy(bin[4:], b) return bin, nil } type DataType uint8 // explicitly defined for clarity and resolvability with apt source const ( DataNull DataType = 0x00 // either 0 or 1 for resource undefined or empty DataReference DataType = 0x01 // ResTable_ref, a reference to another resource table entry DataAttribute DataType = 0x02 // attribute resource identifier DataString DataType = 0x03 // index into the containing resource table's global value string pool DataFloat DataType = 0x04 // single-precision floating point number DataDimension DataType = 0x05 // complex number encoding a dimension value, such as "100in" DataFraction DataType = 0x06 // complex number encoding a fraction of a container DataDynamicReference DataType = 0x07 // dynamic ResTable_ref, which needs to be resolved before it can be used like a TYPE_REFERENCE. DataIntDec DataType = 0x10 // raw integer value of the form n..n DataIntHex DataType = 0x11 // raw integer value of the form 0xn..n DataIntBool DataType = 0x12 // either 0 or 1, for input "false" or "true" DataIntColorARGB8 DataType = 0x1c // raw integer value of the form #aarrggbb DataIntColorRGB8 DataType = 0x1d // raw integer value of the form #rrggbb DataIntColorARGB4 DataType = 0x1e // raw integer value of the form #argb DataIntColorRGB4 DataType = 0x1f // raw integer value of the form #rgb ) type Data struct { ByteSize uint16 Res0 uint8 // always 0, useful for debugging bad read offsets Type DataType Value uint32 } func (d *Data) UnmarshalBinary(bin []byte) error { d.ByteSize = btou16(bin) d.Res0 = uint8(bin[2]) d.Type = DataType(bin[3]) d.Value = btou32(bin[4:]) return nil } func (d *Data) MarshalBinary() ([]byte, error) { bin := make([]byte, 8) putu16(bin, 8) bin[2] = byte(d.Res0) bin[3] = byte(d.Type) putu32(bin[4:], d.Value) return bin, nil }