cmd/gomobile: new binary resource implementation
Change-Id: I9708170ac2c5914bb8e978b4703f4e6f7415c737 Reviewed-on: https://go-review.googlesource.com/16553 Reviewed-by: David Crawshaw <crawshaw@golang.org>
This commit is contained in:
parent
2f30cbe53f
commit
c897050410
342
internal/binres/binres.go
Normal file
342
internal/binres/binres.go
Normal file
@ -0,0 +1,342 @@
|
||||
// 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.
|
||||
|
||||
//go:generate stringer -output binres_string.go -type ResType,DataType
|
||||
|
||||
// Package binres implements encoding and decoding of android binary resources.
|
||||
//
|
||||
// Binary resource structs support unmarshalling the binary output of aapt.
|
||||
// Implementations of marshalling for each struct must produce the exact input
|
||||
// sent to unmarshalling. This allows tests to validate each struct representation
|
||||
// of the binary format as follows:
|
||||
//
|
||||
// * unmarshal the output of aapt
|
||||
// * marshal the struct representation
|
||||
// * perform byte-to-byte comparison with aapt output per chunk header and body
|
||||
//
|
||||
// This process should strive to make structs idiomatic to make parsing xml text
|
||||
// into structs trivial.
|
||||
//
|
||||
// Once the struct representation is validated, tests for parsing xml text
|
||||
// into structs can become self-referential as the following holds true:
|
||||
//
|
||||
// * the unmarshalled input of aapt output is the only valid target
|
||||
// * the unmarshalled input of xml text may be compared to the unmarshalled
|
||||
// input of aapt output to identify errors, e.g. text-trims, wrong flags, etc
|
||||
//
|
||||
// This provides validation, byte-for-byte, for producing binary xml resources.
|
||||
//
|
||||
// It should be made clear that unmarshalling binary resources is currently only
|
||||
// in scope for proving that the BinaryMarshaler works correctly. Any other use
|
||||
// is currently out of scope.
|
||||
//
|
||||
// A simple view of binary xml document structure:
|
||||
//
|
||||
// XML
|
||||
// Pool
|
||||
// Map
|
||||
// Namespace
|
||||
// [...node]
|
||||
//
|
||||
// Additional resources:
|
||||
// https://android.googlesource.com/platform/frameworks/base/+/master/include/androidfw/ResourceTypes.h
|
||||
// https://justanapplication.wordpress.com/2011/09/13/ (a series of articles, increment date)
|
||||
package binres
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type ResType uint16
|
||||
|
||||
func (t ResType) IsSupported() bool {
|
||||
// explicit for clarity
|
||||
return t == ResStringPool || t == ResXML ||
|
||||
t == ResXMLStartNamespace || t == ResXMLEndNamespace ||
|
||||
t == ResXMLStartElement || t == ResXMLEndElement ||
|
||||
t == ResXMLCharData ||
|
||||
t == ResXMLResourceMap ||
|
||||
t == ResTable || t == ResTablePackage
|
||||
}
|
||||
|
||||
// explicitly defined for clarity and resolvability with apt source
|
||||
const (
|
||||
ResNull ResType = 0x0000
|
||||
ResStringPool ResType = 0x0001
|
||||
ResTable ResType = 0x0002
|
||||
ResXML ResType = 0x0003
|
||||
|
||||
ResXMLStartNamespace ResType = 0x0100
|
||||
ResXMLEndNamespace ResType = 0x0101
|
||||
ResXMLStartElement ResType = 0x0102
|
||||
ResXMLEndElement ResType = 0x0103
|
||||
ResXMLCharData ResType = 0x0104
|
||||
|
||||
ResXMLResourceMap ResType = 0x0180
|
||||
|
||||
ResTablePackage ResType = 0x0200
|
||||
ResTableType ResType = 0x0201
|
||||
ResTableTypeSpec ResType = 0x0202
|
||||
)
|
||||
|
||||
var (
|
||||
btou16 = binary.LittleEndian.Uint16
|
||||
btou32 = binary.LittleEndian.Uint32
|
||||
putu16 = binary.LittleEndian.PutUint16
|
||||
putu32 = binary.LittleEndian.PutUint32
|
||||
)
|
||||
|
||||
// unmarshaler wraps BinaryUnmarshaler to provide byte size of decoded chunks.
|
||||
type unmarshaler interface {
|
||||
encoding.BinaryUnmarshaler
|
||||
|
||||
// size returns the byte size unmarshalled after a call to
|
||||
// UnmarshalBinary, or otherwise zero.
|
||||
size() int
|
||||
}
|
||||
|
||||
// chunkHeader appears at the front of every data chunk in a resource.
|
||||
// TODO look into removing this, it's not necessary for marshalling and
|
||||
// the information provided may possibly be given more simply. For unmarshal,
|
||||
// a simple function would do.
|
||||
type chunkHeader struct {
|
||||
// Type of data that follows this header.
|
||||
typ ResType
|
||||
|
||||
// Advance slice index by this value to find its associated data, if any.
|
||||
headerByteSize uint16
|
||||
|
||||
// This is the header size plus the size of any data associated with the chunk.
|
||||
// Advance slice index by this value to completely skip its contents, including
|
||||
// any child chunks. If this value is the same as headerByteSize, there is
|
||||
// no data associated with the chunk.
|
||||
byteSize uint32
|
||||
}
|
||||
|
||||
// size implements unmarshaler.
|
||||
func (hdr chunkHeader) size() int { return int(hdr.byteSize) }
|
||||
|
||||
func (hdr *chunkHeader) UnmarshalBinary(bin []byte) error {
|
||||
hdr.typ = ResType(btou16(bin))
|
||||
if !hdr.typ.IsSupported() {
|
||||
return fmt.Errorf("%s not supported", hdr.typ)
|
||||
}
|
||||
hdr.headerByteSize = btou16(bin[2:])
|
||||
hdr.byteSize = btou32(bin[4:])
|
||||
return nil
|
||||
}
|
||||
|
||||
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)
|
||||
putu32(bin[4:], hdr.byteSize)
|
||||
return bin, nil
|
||||
}
|
||||
|
||||
type XML struct {
|
||||
chunkHeader
|
||||
|
||||
Pool *Pool
|
||||
Map *Map
|
||||
|
||||
Namespace *Namespace
|
||||
Children []*Element
|
||||
|
||||
// tmp field used when unmarshalling
|
||||
stack []*Element
|
||||
}
|
||||
|
||||
// TODO this is used strictly for querying in tests and dependent on
|
||||
// current XML.UnmarshalBinary implementation. Look into moving directly
|
||||
// into tests.
|
||||
var debugIndices = make(map[encoding.BinaryMarshaler]int)
|
||||
|
||||
func (bx *XML) UnmarshalBinary(bin []byte) error {
|
||||
buf := bin
|
||||
if err := (&bx.chunkHeader).UnmarshalBinary(bin); err != nil {
|
||||
return err
|
||||
}
|
||||
buf = buf[8:]
|
||||
|
||||
// TODO this is tracked strictly for querying in tests; look into moving this
|
||||
// functionality directly into tests if possible.
|
||||
debugIndex := 8
|
||||
|
||||
for len(buf) > 0 {
|
||||
t := ResType(btou16(buf))
|
||||
k, err := bx.kind(t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := k.UnmarshalBinary(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
debugIndices[k.(encoding.BinaryMarshaler)] = debugIndex
|
||||
debugIndex += int(k.size())
|
||||
buf = buf[k.size():]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bx *XML) kind(t ResType) (unmarshaler, error) {
|
||||
switch t {
|
||||
case ResStringPool:
|
||||
if bx.Pool != nil {
|
||||
return nil, fmt.Errorf("pool already exists")
|
||||
}
|
||||
bx.Pool = new(Pool)
|
||||
return bx.Pool, nil
|
||||
case ResXMLResourceMap:
|
||||
if bx.Map != nil {
|
||||
return nil, fmt.Errorf("resource map already exists")
|
||||
}
|
||||
bx.Map = new(Map)
|
||||
return bx.Map, nil
|
||||
case ResXMLStartNamespace:
|
||||
if bx.Namespace != nil {
|
||||
return nil, fmt.Errorf("namespace start already exists")
|
||||
}
|
||||
bx.Namespace = new(Namespace)
|
||||
return bx.Namespace, nil
|
||||
case ResXMLEndNamespace:
|
||||
if bx.Namespace.end != nil {
|
||||
return nil, fmt.Errorf("namespace end already exists")
|
||||
}
|
||||
bx.Namespace.end = new(Namespace)
|
||||
return bx.Namespace.end, nil
|
||||
case ResXMLStartElement:
|
||||
el := new(Element)
|
||||
if len(bx.stack) == 0 {
|
||||
bx.Children = append(bx.Children, el)
|
||||
} else {
|
||||
n := len(bx.stack)
|
||||
var p *Element
|
||||
p, bx.stack = bx.stack[n-1], bx.stack[:n-1]
|
||||
p.Children = append(p.Children, el)
|
||||
bx.stack = append(bx.stack, p)
|
||||
}
|
||||
bx.stack = append(bx.stack, el)
|
||||
return el, nil
|
||||
case ResXMLEndElement:
|
||||
n := len(bx.stack)
|
||||
var el *Element
|
||||
el, bx.stack = bx.stack[n-1], bx.stack[:n-1]
|
||||
if el.end != nil {
|
||||
return nil, fmt.Errorf("element end already exists")
|
||||
}
|
||||
el.end = new(ElementEnd)
|
||||
return el.end, nil
|
||||
case ResXMLCharData: // TODO
|
||||
cdt := new(CharData)
|
||||
el := bx.stack[len(bx.stack)-1]
|
||||
if el.head == nil {
|
||||
el.head = cdt
|
||||
} else if el.tail == nil {
|
||||
el.tail = cdt
|
||||
} else {
|
||||
return nil, fmt.Errorf("element head and tail already contain chardata")
|
||||
}
|
||||
return cdt, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected type %s", t)
|
||||
}
|
||||
}
|
||||
|
||||
func (bx *XML) MarshalBinary() ([]byte, error) {
|
||||
var (
|
||||
bin, b []byte
|
||||
err error
|
||||
)
|
||||
b, err = bx.chunkHeader.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bin = append(bin, b...)
|
||||
|
||||
b, err = bx.Pool.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bin = append(bin, b...)
|
||||
|
||||
b, err = bx.Map.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bin = append(bin, b...)
|
||||
|
||||
b, err = bx.Namespace.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bin = append(bin, b...)
|
||||
|
||||
for _, child := range bx.Children {
|
||||
if err := marshalRecurse(child, &bin); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
b, err = bx.Namespace.end.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bin = append(bin, b...)
|
||||
|
||||
return bin, nil
|
||||
}
|
||||
|
||||
func marshalRecurse(el *Element, bin *[]byte) error {
|
||||
b, err := el.MarshalBinary()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*bin = append(*bin, b...)
|
||||
|
||||
if el.head != nil {
|
||||
b, err := el.head.MarshalBinary()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*bin = append(*bin, b...)
|
||||
}
|
||||
|
||||
for _, child := range el.Children {
|
||||
if err := marshalRecurse(child, bin); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
b, err = el.end.MarshalBinary()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*bin = append(*bin, b...)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bx *XML) iterElements() <-chan *Element {
|
||||
ch := make(chan *Element, 1)
|
||||
go func() {
|
||||
for _, el := range bx.Children {
|
||||
iterElementsRecurse(el, ch)
|
||||
}
|
||||
close(ch)
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
|
||||
func iterElementsRecurse(el *Element, ch chan *Element) {
|
||||
ch <- el
|
||||
for _, e := range el.Children {
|
||||
iterElementsRecurse(e, ch)
|
||||
}
|
||||
}
|
87
internal/binres/binres_string.go
Normal file
87
internal/binres/binres_string.go
Normal file
@ -0,0 +1,87 @@
|
||||
// generated by stringer -output binres_string.go -type ResType,DataType; DO NOT EDIT
|
||||
|
||||
package binres
|
||||
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
_ResType_name_0 = "ResNullResStringPoolResTableResXML"
|
||||
_ResType_name_1 = "ResXMLStartNamespaceResXMLEndNamespaceResXMLStartElementResXMLEndElementResXMLCharData"
|
||||
_ResType_name_2 = "ResXMLResourceMap"
|
||||
_ResType_name_3 = "ResTablePackageResTableTypeResTableTypeSpec"
|
||||
)
|
||||
|
||||
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}
|
||||
)
|
||||
|
||||
func (i ResType) String() string {
|
||||
switch {
|
||||
case 0 <= i && i <= 3:
|
||||
lo := uint8(0)
|
||||
if i > 0 {
|
||||
lo = _ResType_index_0[i-1]
|
||||
}
|
||||
return _ResType_name_0[lo:_ResType_index_0[i]]
|
||||
case 256 <= i && i <= 260:
|
||||
i -= 256
|
||||
lo := uint8(0)
|
||||
if i > 0 {
|
||||
lo = _ResType_index_1[i-1]
|
||||
}
|
||||
return _ResType_name_1[lo:_ResType_index_1[i]]
|
||||
case i == 384:
|
||||
return _ResType_name_2
|
||||
case 512 <= i && i <= 514:
|
||||
i -= 512
|
||||
lo := uint8(0)
|
||||
if i > 0 {
|
||||
lo = _ResType_index_3[i-1]
|
||||
}
|
||||
return _ResType_name_3[lo:_ResType_index_3[i]]
|
||||
default:
|
||||
return fmt.Sprintf("ResType(%d)", i)
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
_DataType_name_0 = "DataNullDataReferenceDataAttributeDataStringDataFloatDataDimensionDataFractionDataDynamicReference"
|
||||
_DataType_name_1 = "DataIntDecDataIntHexDataIntBool"
|
||||
_DataType_name_2 = "DataIntColorARGB8DataIntColorRGB8DataIntColorARGB4DataIntColorRGB4"
|
||||
)
|
||||
|
||||
var (
|
||||
_DataType_index_0 = [...]uint8{8, 21, 34, 44, 53, 66, 78, 98}
|
||||
_DataType_index_1 = [...]uint8{10, 20, 31}
|
||||
_DataType_index_2 = [...]uint8{17, 33, 50, 66}
|
||||
)
|
||||
|
||||
func (i DataType) String() string {
|
||||
switch {
|
||||
case 0 <= i && i <= 7:
|
||||
lo := uint8(0)
|
||||
if i > 0 {
|
||||
lo = _DataType_index_0[i-1]
|
||||
}
|
||||
return _DataType_name_0[lo:_DataType_index_0[i]]
|
||||
case 16 <= i && i <= 18:
|
||||
i -= 16
|
||||
lo := uint8(0)
|
||||
if i > 0 {
|
||||
lo = _DataType_index_1[i-1]
|
||||
}
|
||||
return _DataType_name_1[lo:_DataType_index_1[i]]
|
||||
case 28 <= i && i <= 31:
|
||||
i -= 28
|
||||
lo := uint8(0)
|
||||
if i > 0 {
|
||||
lo = _DataType_index_2[i-1]
|
||||
}
|
||||
return _DataType_name_2[lo:_DataType_index_2[i]]
|
||||
default:
|
||||
return fmt.Sprintf("DataType(%d)", i)
|
||||
}
|
||||
}
|
271
internal/binres/binres_test.go
Normal file
271
internal/binres/binres_test.go
Normal file
@ -0,0 +1,271 @@
|
||||
// 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"
|
||||
"encoding"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func printrecurse(t *testing.T, pl *Pool, el *Element, ws string) {
|
||||
for _, attr := range el.attrs {
|
||||
ns := ""
|
||||
if attr.NS != math.MaxUint32 {
|
||||
ns = pl.strings[int(attr.NS)]
|
||||
nss := strings.Split(ns, "/")
|
||||
ns = nss[len(nss)-1]
|
||||
}
|
||||
|
||||
val := ""
|
||||
if attr.RawValue != math.MaxUint32 {
|
||||
val = pl.strings[int(attr.RawValue)]
|
||||
} else {
|
||||
switch attr.TypedValue.Type {
|
||||
case DataIntDec:
|
||||
val = fmt.Sprintf("%v", attr.TypedValue.Value)
|
||||
case DataIntBool:
|
||||
val = fmt.Sprintf("%v", attr.TypedValue.Value == 1)
|
||||
default:
|
||||
val = fmt.Sprintf("0x%08X", attr.TypedValue.Value)
|
||||
}
|
||||
}
|
||||
dt := attr.TypedValue.Type
|
||||
|
||||
t.Logf("%s|attr:ns(%v) name(%s) val(%s) valtyp(%s)\n", ws, ns, pl.strings[int(attr.Name)], val, dt)
|
||||
}
|
||||
t.Log()
|
||||
for _, e := range el.Children {
|
||||
printrecurse(t, pl, e, ws+" ")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBootstrap(t *testing.T) {
|
||||
bin, err := ioutil.ReadFile("testdata/bootstrap.bin")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
checkMarshal := func(res encoding.BinaryMarshaler, bsize int) {
|
||||
b, err := res.MarshalBinary()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
idx := debugIndices[res]
|
||||
a := bin[idx : idx+bsize]
|
||||
if !bytes.Equal(a, b) {
|
||||
x, y := len(a), len(b)
|
||||
if x != y {
|
||||
t.Errorf("%v: %T: byte length does not match, have %v, want %v", idx, res, y, x)
|
||||
}
|
||||
if x > y {
|
||||
x, y = y, x
|
||||
}
|
||||
mismatch := false
|
||||
for i := 0; i < x; i++ {
|
||||
if mismatch = a[i] != b[i]; mismatch {
|
||||
t.Errorf("%v: %T: first byte mismatch at %v of %v", idx, res, i, bsize)
|
||||
break
|
||||
}
|
||||
}
|
||||
if mismatch {
|
||||
// print out a reasonable amount of data to help identify issues
|
||||
truncate := x > 1300
|
||||
if truncate {
|
||||
x = 1300
|
||||
}
|
||||
t.Log(" HAVE WANT")
|
||||
for i := 0; i < x; i += 4 {
|
||||
he, we := 4, 4
|
||||
if i+he >= x {
|
||||
he = x - i
|
||||
}
|
||||
if i+we >= y {
|
||||
we = y - i
|
||||
}
|
||||
t.Logf("%3v | % X % X\n", i, b[i:i+he], a[i:i+we])
|
||||
}
|
||||
if truncate {
|
||||
t.Log("... output truncated.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bxml := new(XML)
|
||||
if err := bxml.UnmarshalBinary(bin); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for i, x := range bxml.Pool.strings {
|
||||
t.Logf("Pool(%v): %q\n", i, x)
|
||||
}
|
||||
|
||||
for _, e := range bxml.Children {
|
||||
printrecurse(t, bxml.Pool, e, "")
|
||||
}
|
||||
|
||||
checkMarshal(&bxml.chunkHeader, int(bxml.headerByteSize))
|
||||
checkMarshal(bxml.Pool, bxml.Pool.size())
|
||||
checkMarshal(bxml.Map, bxml.Map.size())
|
||||
checkMarshal(bxml.Namespace, bxml.Namespace.size())
|
||||
|
||||
for el := range bxml.iterElements() {
|
||||
checkMarshal(el, el.size())
|
||||
checkMarshal(el.end, el.end.size())
|
||||
}
|
||||
|
||||
checkMarshal(bxml.Namespace.end, bxml.Namespace.end.size())
|
||||
checkMarshal(bxml, bxml.size())
|
||||
}
|
||||
|
||||
func retset(xs []string) []string {
|
||||
m := make(map[string]struct{})
|
||||
fo := xs[:0]
|
||||
for _, x := range xs {
|
||||
if x == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := m[x]; !ok {
|
||||
m[x] = struct{}{}
|
||||
fo = append(fo, x)
|
||||
}
|
||||
}
|
||||
return fo
|
||||
}
|
||||
|
||||
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 {
|
||||
if a[j].Name.Space == "" {
|
||||
return a[i].Name.Space != ""
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// WIP approximation of first steps to be taken to encode manifest
|
||||
func TestEncode(t *testing.T) {
|
||||
f, err := os.Open("testdata/bootstrap.xml")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var attrs []xml.Attr
|
||||
|
||||
dec := xml.NewDecoder(f)
|
||||
for {
|
||||
tkn, err := dec.Token()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return
|
||||
// t.Fatal(err)
|
||||
}
|
||||
tkn = xml.CopyToken(tkn)
|
||||
|
||||
switch tkn := tkn.(type) {
|
||||
case xml.StartElement:
|
||||
attrs = append(attrs, tkn.Attr...)
|
||||
default:
|
||||
// t.Error("unhandled token type", tkn)
|
||||
}
|
||||
}
|
||||
|
||||
bvc := xml.Attr{
|
||||
Name: xml.Name{
|
||||
Space: "",
|
||||
Local: "platformBuildVersionCode",
|
||||
},
|
||||
Value: "10",
|
||||
}
|
||||
bvn := xml.Attr{
|
||||
Name: xml.Name{
|
||||
Space: "",
|
||||
Local: "platformBuildVersionName",
|
||||
},
|
||||
Value: "2.3.3",
|
||||
}
|
||||
attrs = append(attrs, bvc, bvn)
|
||||
|
||||
sort.Sort(ByNamespace(attrs))
|
||||
var names, vals []string
|
||||
for _, attr := range attrs {
|
||||
if strings.HasSuffix(attr.Name.Space, "tools") {
|
||||
continue
|
||||
}
|
||||
names = append(names, attr.Name.Local)
|
||||
vals = append(vals, attr.Value)
|
||||
}
|
||||
|
||||
var all []string
|
||||
all = append(all, names...)
|
||||
all = append(all, vals...)
|
||||
|
||||
// do not eliminate duplicates until the entire slice has been composed.
|
||||
// consider <activity android:label="label" .../>
|
||||
// all attribute names come first followed by values; in such a case, the value "label"
|
||||
// would be a reference to the same "android:label" in the string pool which will occur
|
||||
// within the beginning of the pool where other attr names are located.
|
||||
pl := new(Pool)
|
||||
for _, x := range retset(all) {
|
||||
pl.strings = append(pl.strings, x)
|
||||
// t.Logf("Pool(%v) %q\n", i, x)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAndroidJar(t *testing.T) {
|
||||
zr, err := zip.OpenReader("/home/daniel/local/android-sdk/platforms/android-10/android.jar")
|
||||
if err != nil {
|
||||
t.Fatal(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 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, 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)
|
||||
}
|
50
internal/binres/data.go
Normal file
50
internal/binres/data.go
Normal file
@ -0,0 +1,50 @@
|
||||
// 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 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, d.ByteSize)
|
||||
bin[2] = byte(d.Res0)
|
||||
bin[3] = byte(d.Type)
|
||||
putu32(bin[4:], d.Value)
|
||||
return bin, nil
|
||||
}
|
225
internal/binres/node.go
Normal file
225
internal/binres/node.go
Normal file
@ -0,0 +1,225 @@
|
||||
// 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
|
||||
|
||||
// NodeHeader is header all xml node types have, providing additional
|
||||
// information regarding an xml node over binChunkHeader.
|
||||
type NodeHeader struct {
|
||||
chunkHeader
|
||||
LineNumber uint32 // line number in source file this element appears
|
||||
Comment PoolRef // optional xml comment associated with element, MaxUint32 if none
|
||||
}
|
||||
|
||||
func (hdr *NodeHeader) UnmarshalBinary(bin []byte) error {
|
||||
if err := (&hdr.chunkHeader).UnmarshalBinary(bin); err != nil {
|
||||
return err
|
||||
}
|
||||
hdr.LineNumber = btou32(bin[8:])
|
||||
hdr.Comment = PoolRef(btou32(bin[12:]))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hdr *NodeHeader) MarshalBinary() ([]byte, error) {
|
||||
bin := make([]byte, 16)
|
||||
b, err := hdr.chunkHeader.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(bin, b)
|
||||
putu32(bin[8:], hdr.LineNumber)
|
||||
putu32(bin[12:], uint32(hdr.Comment))
|
||||
return bin, nil
|
||||
}
|
||||
|
||||
type Namespace struct {
|
||||
NodeHeader
|
||||
prefix PoolRef
|
||||
uri PoolRef
|
||||
|
||||
end *Namespace // TODO don't let this type be recursive
|
||||
}
|
||||
|
||||
func (ns *Namespace) UnmarshalBinary(bin []byte) error {
|
||||
if err := (&ns.NodeHeader).UnmarshalBinary(bin); err != nil {
|
||||
return err
|
||||
}
|
||||
buf := bin[ns.headerByteSize:]
|
||||
ns.prefix = PoolRef(btou32(buf))
|
||||
ns.uri = PoolRef(btou32(buf[4:]))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ns *Namespace) MarshalBinary() ([]byte, error) {
|
||||
bin := make([]byte, 24)
|
||||
b, err := ns.NodeHeader.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(bin, b)
|
||||
putu32(bin[16:], uint32(ns.prefix))
|
||||
putu32(bin[20:], uint32(ns.uri))
|
||||
return bin, nil
|
||||
}
|
||||
|
||||
type Element struct {
|
||||
NodeHeader
|
||||
NS PoolRef
|
||||
Name PoolRef // name of node if element, otherwise chardata if CDATA
|
||||
AttributeStart uint16 // byte offset where attrs start
|
||||
AttributeSize uint16 // byte size of attrs
|
||||
AttributeCount uint16 // length of attrs
|
||||
IdIndex uint16 // Index (1-based) of the "id" attribute. 0 if none.
|
||||
ClassIndex uint16 // Index (1-based) of the "class" attribute. 0 if none.
|
||||
StyleIndex uint16 // Index (1-based) of the "style" attribute. 0 if none.
|
||||
|
||||
attrs []*Attribute
|
||||
Children []*Element
|
||||
end *ElementEnd
|
||||
|
||||
head, tail *CharData
|
||||
}
|
||||
|
||||
func (el *Element) UnmarshalBinary(bin []byte) error {
|
||||
if err := (&el.NodeHeader).UnmarshalBinary(bin); err != nil {
|
||||
return err
|
||||
}
|
||||
buf := bin[el.headerByteSize:] // 16
|
||||
el.NS = PoolRef(btou32(buf))
|
||||
el.Name = PoolRef(btou32(buf[4:]))
|
||||
el.AttributeStart = btou16(buf[8:])
|
||||
el.AttributeSize = btou16(buf[10:])
|
||||
el.AttributeCount = btou16(buf[12:])
|
||||
el.IdIndex = btou16(buf[14:])
|
||||
el.ClassIndex = btou16(buf[16:])
|
||||
el.StyleIndex = btou16(buf[18:])
|
||||
|
||||
buf = buf[el.AttributeStart:]
|
||||
el.attrs = make([]*Attribute, int(el.AttributeCount))
|
||||
for i := range el.attrs {
|
||||
attr := new(Attribute)
|
||||
if err := attr.UnmarshalBinary(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
el.attrs[i] = attr
|
||||
buf = buf[el.AttributeSize:]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (el *Element) MarshalBinary() ([]byte, error) {
|
||||
bin := make([]byte, 16+20+len(el.attrs)*int(el.AttributeSize))
|
||||
b, err := el.NodeHeader.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(bin, b)
|
||||
putu32(bin[16:], uint32(el.NS))
|
||||
putu32(bin[20:], uint32(el.Name))
|
||||
putu16(bin[24:], el.AttributeStart)
|
||||
putu16(bin[26:], el.AttributeSize)
|
||||
putu16(bin[28:], el.AttributeCount)
|
||||
putu16(bin[30:], el.IdIndex)
|
||||
putu16(bin[32:], el.ClassIndex)
|
||||
putu16(bin[34:], el.StyleIndex)
|
||||
|
||||
buf := bin[36:]
|
||||
for _, attr := range el.attrs {
|
||||
b, err := attr.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(buf, b)
|
||||
buf = buf[int(el.AttributeSize):]
|
||||
}
|
||||
|
||||
return bin, nil
|
||||
}
|
||||
|
||||
// ElementEnd marks the end of an element node, either Element or CharData.
|
||||
type ElementEnd struct {
|
||||
NodeHeader
|
||||
NS PoolRef
|
||||
Name PoolRef // name of node if binElement, raw chardata if binCharData
|
||||
}
|
||||
|
||||
func (el *ElementEnd) UnmarshalBinary(bin []byte) error {
|
||||
(&el.NodeHeader).UnmarshalBinary(bin)
|
||||
buf := bin[el.headerByteSize:]
|
||||
el.NS = PoolRef(btou32(buf))
|
||||
el.Name = PoolRef(btou32(buf[4:]))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (el *ElementEnd) MarshalBinary() ([]byte, error) {
|
||||
bin := make([]byte, 24)
|
||||
b, err := el.NodeHeader.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(bin, b)
|
||||
putu32(bin[16:], uint32(el.NS))
|
||||
putu32(bin[20:], uint32(el.Name))
|
||||
return bin, nil
|
||||
}
|
||||
|
||||
type Attribute struct {
|
||||
NS PoolRef
|
||||
Name PoolRef
|
||||
RawValue PoolRef // The original raw string value of this attribute.
|
||||
TypedValue Data // Processesd typed value of this attribute.
|
||||
}
|
||||
|
||||
func (attr *Attribute) UnmarshalBinary(bin []byte) error {
|
||||
attr.NS = PoolRef(btou32(bin))
|
||||
attr.Name = PoolRef(btou32(bin[4:]))
|
||||
attr.RawValue = PoolRef(btou32(bin[8:]))
|
||||
return (&attr.TypedValue).UnmarshalBinary(bin[12:])
|
||||
}
|
||||
|
||||
func (attr *Attribute) MarshalBinary() ([]byte, error) {
|
||||
bin := make([]byte, 20)
|
||||
putu32(bin, uint32(attr.NS))
|
||||
putu32(bin[4:], uint32(attr.Name))
|
||||
putu32(bin[8:], uint32(attr.RawValue))
|
||||
b, err := attr.TypedValue.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(bin[12:], b)
|
||||
return bin, nil
|
||||
}
|
||||
|
||||
// CharData represents a CDATA node and includes ref to node's text value.
|
||||
type CharData struct {
|
||||
NodeHeader
|
||||
RawData PoolRef // raw character data
|
||||
TypedData Data // typed value of character data
|
||||
}
|
||||
|
||||
func (cdt *CharData) UnmarshalBinary(bin []byte) error {
|
||||
if err := (&cdt.NodeHeader).UnmarshalBinary(bin); err != nil {
|
||||
return err
|
||||
}
|
||||
buf := bin[cdt.headerByteSize:]
|
||||
cdt.RawData = PoolRef(btou32(buf))
|
||||
return (&cdt.TypedData).UnmarshalBinary(buf[4:])
|
||||
}
|
||||
|
||||
func (cdt *CharData) MarshalBinary() ([]byte, error) {
|
||||
bin := make([]byte, 28)
|
||||
b, err := cdt.NodeHeader.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(bin, b)
|
||||
putu32(bin[16:], uint32(cdt.RawData))
|
||||
b, err = cdt.TypedData.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(bin[20:], b)
|
||||
return bin, nil
|
||||
}
|
101
internal/binres/package.go
Normal file
101
internal/binres/package.go
Normal file
@ -0,0 +1,101 @@
|
||||
// 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
|
||||
}
|
276
internal/binres/pool.go
Normal file
276
internal/binres/pool.go
Normal file
@ -0,0 +1,276 @@
|
||||
// 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 (
|
||||
"fmt"
|
||||
"unicode/utf16"
|
||||
)
|
||||
|
||||
const (
|
||||
SortedFlag uint32 = 1 << 0
|
||||
UTF8Flag = 1 << 8
|
||||
)
|
||||
|
||||
// PoolRef is the i'th string in a pool.
|
||||
type PoolRef uint32
|
||||
|
||||
// Pool has the following structure marshalled:
|
||||
//
|
||||
// binChunkHeader
|
||||
// StringCount uint32 // Number of strings in this pool
|
||||
// StyleCount uint32 // Number of style spans in pool
|
||||
// Flags uint32 // SortedFlag, UTF8Flag
|
||||
// StringsStart uint32 // Index of string data from header
|
||||
// StylesStart uint32 // Index of style data from header
|
||||
//
|
||||
// StringIndices []uint32 // starting at zero
|
||||
//
|
||||
// // UTF16 entries are concatenations of the following:
|
||||
// // [2]byte uint16 string length, exclusive
|
||||
// // [2]byte [optional] low word if high bit of length was set
|
||||
// // [n]byte data
|
||||
// // [2]byte 0x0000 terminator
|
||||
// Strings []uint16
|
||||
type Pool struct {
|
||||
chunkHeader
|
||||
|
||||
strings []string
|
||||
styles []*Span
|
||||
flags uint32 // SortedFlag, UTF8Flag
|
||||
}
|
||||
|
||||
func (pl *Pool) IsSorted() bool { return pl.flags&SortedFlag == SortedFlag }
|
||||
func (pl *Pool) IsUTF8() bool { return pl.flags&UTF8Flag == UTF8Flag }
|
||||
|
||||
func (pl *Pool) UnmarshalBinary(bin []byte) error {
|
||||
if err := (&pl.chunkHeader).UnmarshalBinary(bin); err != nil {
|
||||
return err
|
||||
}
|
||||
if pl.typ != ResStringPool {
|
||||
return fmt.Errorf("have type %s, want %s", pl.typ, ResStringPool)
|
||||
}
|
||||
|
||||
nstrings := btou32(bin[8:])
|
||||
pl.strings = make([]string, nstrings)
|
||||
|
||||
nstyles := btou32(bin[12:])
|
||||
pl.styles = make([]*Span, nstyles)
|
||||
|
||||
pl.flags = btou32(bin[16:])
|
||||
|
||||
offstrings := btou32(bin[20:])
|
||||
offstyle := btou32(bin[24:])
|
||||
|
||||
hdrlen := 28 // header size is always 28
|
||||
if pl.IsUTF8() {
|
||||
for i := range pl.strings {
|
||||
ii := btou32(bin[hdrlen+i*4:]) // index of string index
|
||||
r := int(offstrings + ii) // read index of string
|
||||
|
||||
// for char and byte sizes below, if leading bit set,
|
||||
// treat first as 7-bit high word and the next byte as low word.
|
||||
|
||||
// get char size of string
|
||||
cn := uint8(bin[r])
|
||||
rcn := int(cn)
|
||||
r++
|
||||
if cn&(1<<7) != 0 {
|
||||
cn0 := int(cn ^ (1 << 7)) // high word
|
||||
cn1 := int(bin[r]) // low word
|
||||
rcn = cn0*256 + cn1
|
||||
r++
|
||||
}
|
||||
|
||||
// get byte size of string
|
||||
// TODO(d) i've seen at least one case in android.jar resource table that has only
|
||||
// highbit set, effectively making 7-bit highword zero. The reason for this is currently
|
||||
// unknown but would make tests that unmarshal-marshal to match bytes impossible to pass.
|
||||
// The values noted were high-word: 0 (after highbit unset), low-word: 141
|
||||
// I don't recall character count but was around ?78?
|
||||
// The case here may very well be that the size is treated like int8 triggering the use of
|
||||
// two bytes to store size even though the implementation uses uint8.
|
||||
n := uint8(bin[r])
|
||||
r++
|
||||
rn := int(n)
|
||||
if n&(1<<7) != 0 {
|
||||
n0 := int(n ^ (1 << 7)) // high word
|
||||
n1 := int(bin[r]) // low word
|
||||
rn = n0*(1<<8) + n1
|
||||
r++
|
||||
}
|
||||
|
||||
//
|
||||
data := bin[r : r+rn]
|
||||
if x := uint8(bin[r+rn]); x != 0 {
|
||||
return fmt.Errorf("expected zero terminator, got %v for byte size %v char len %v", x, rn, rcn)
|
||||
}
|
||||
pl.strings[i] = string(data)
|
||||
}
|
||||
} else {
|
||||
for i := range pl.strings {
|
||||
ii := btou32(bin[hdrlen+i*4:]) // index of string index
|
||||
r := int(offstrings + ii) // read index of string
|
||||
n := btou16(bin[r:]) // string length
|
||||
rn := int(n)
|
||||
r += 2
|
||||
|
||||
if n&(1<<15) != 0 { // TODO this is untested
|
||||
n0 := int(n ^ (1 << 15)) // high word
|
||||
n1 := int(btou16(bin[r:]))
|
||||
rn = n0*(1<<16) + n1
|
||||
r += 2
|
||||
}
|
||||
|
||||
data := make([]uint16, int(rn))
|
||||
for i := range data {
|
||||
data[i] = btou16(bin[r+(2*i):])
|
||||
}
|
||||
|
||||
r += int(n * 2)
|
||||
if x := btou16(bin[r:]); x != 0 {
|
||||
return fmt.Errorf("expected zero terminator, got 0x%04X\n%s", x)
|
||||
}
|
||||
|
||||
pl.strings[i] = string(utf16.Decode(data))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO
|
||||
_ = offstyle
|
||||
// styii := hdrlen + int(nstrings*4)
|
||||
// for i := range pl.styles {
|
||||
// ii := btou32(bin[styii+i*4:])
|
||||
// r := int(offstyle + ii)
|
||||
// spn := new(binSpan)
|
||||
// spn.UnmarshalBinary(bin[r:])
|
||||
// pl.styles[i] = spn
|
||||
// }
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pl *Pool) MarshalBinary() ([]byte, error) {
|
||||
if pl.IsUTF8() {
|
||||
return nil, fmt.Errorf("encode utf8 not supported")
|
||||
}
|
||||
|
||||
var (
|
||||
hdrlen = 28
|
||||
// indices of string indices
|
||||
iis = make([]uint32, len(pl.strings))
|
||||
iislen = len(iis) * 4
|
||||
// utf16 encoded strings concatenated together
|
||||
strs []uint16
|
||||
)
|
||||
for i, x := range pl.strings {
|
||||
if len(x)>>16 > 0 {
|
||||
panic(fmt.Errorf("string lengths over 1<<15 not yet supported, got len %d", len(x)))
|
||||
}
|
||||
p := utf16.Encode([]rune(x))
|
||||
if len(p) == 0 {
|
||||
strs = append(strs, 0x0000, 0x0000)
|
||||
} else {
|
||||
strs = append(strs, uint16(len(p))) // string length (implicitly includes zero terminator to follow)
|
||||
strs = append(strs, p...)
|
||||
strs = append(strs, 0) // zero terminated
|
||||
}
|
||||
// indices start at zero
|
||||
if i+1 != len(iis) {
|
||||
iis[i+1] = uint32(len(strs) * 2) // utf16 byte index
|
||||
}
|
||||
}
|
||||
|
||||
// check strings is 4-byte aligned, pad with zeros if not.
|
||||
for x := (len(strs) * 2) % 4; x != 0; x -= 2 {
|
||||
strs = append(strs, 0x0000)
|
||||
}
|
||||
|
||||
strslen := len(strs) * 2
|
||||
|
||||
hdr := chunkHeader{
|
||||
typ: ResStringPool,
|
||||
headerByteSize: 28,
|
||||
byteSize: uint32(28 + iislen + strslen),
|
||||
}
|
||||
|
||||
bin := make([]byte, hdr.byteSize)
|
||||
|
||||
hdrbin, err := hdr.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(bin, hdrbin)
|
||||
|
||||
putu32(bin[8:], uint32(len(pl.strings)))
|
||||
putu32(bin[12:], uint32(len(pl.styles)))
|
||||
putu32(bin[16:], pl.flags)
|
||||
putu32(bin[20:], uint32(hdrlen+iislen))
|
||||
putu32(bin[24:], 0) // index of styles start, is 0 when styles length is 0
|
||||
|
||||
buf := bin[28:]
|
||||
for _, x := range iis {
|
||||
putu32(buf, x)
|
||||
buf = buf[4:]
|
||||
}
|
||||
for _, x := range strs {
|
||||
putu16(buf, x)
|
||||
buf = buf[2:]
|
||||
}
|
||||
|
||||
if len(buf) != 0 {
|
||||
panic(fmt.Errorf("failed to fill allocated buffer, %v bytes left over", len(buf)))
|
||||
}
|
||||
|
||||
return bin, nil
|
||||
}
|
||||
|
||||
type Span struct {
|
||||
name PoolRef
|
||||
|
||||
firstChar, lastChar uint32
|
||||
}
|
||||
|
||||
func (spn *Span) UnmarshalBinary(bin []byte) error {
|
||||
const end = 0xFFFFFFFF
|
||||
spn.name = PoolRef(btou32(bin))
|
||||
if spn.name == end {
|
||||
return nil
|
||||
}
|
||||
spn.firstChar = btou32(bin[4:])
|
||||
spn.lastChar = btou32(bin[8:])
|
||||
return nil
|
||||
}
|
||||
|
||||
// Map contains a uint32 slice mapping strings in the string
|
||||
// pool back to resource identifiers. The i'th element of the slice
|
||||
// is also the same i'th element of the string pool.
|
||||
type Map struct {
|
||||
chunkHeader
|
||||
rs []uint32
|
||||
}
|
||||
|
||||
func (m *Map) UnmarshalBinary(bin []byte) error {
|
||||
(&m.chunkHeader).UnmarshalBinary(bin)
|
||||
buf := bin[m.headerByteSize:m.byteSize]
|
||||
m.rs = make([]uint32, len(buf)/4)
|
||||
for i := range m.rs {
|
||||
m.rs[i] = btou32(buf[i*4:])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Map) MarshalBinary() ([]byte, error) {
|
||||
bin := make([]byte, 8+len(m.rs)*4)
|
||||
b, err := m.chunkHeader.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
copy(bin, b)
|
||||
for i, r := range m.rs {
|
||||
putu32(bin[8+i*4:], r)
|
||||
}
|
||||
return bin, nil
|
||||
}
|
BIN
internal/binres/testdata/bootstrap.bin
vendored
Normal file
BIN
internal/binres/testdata/bootstrap.bin
vendored
Normal file
Binary file not shown.
33
internal/binres/testdata/bootstrap.xml
vendored
Normal file
33
internal/binres/testdata/bootstrap.xml
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2014 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.
|
||||
-->
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.zentus.balloon"
|
||||
android:versionCode="42"
|
||||
android:versionName=""
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-sdk android:minSdkVersion="9" />
|
||||
<application
|
||||
android:label="Balloon世界"
|
||||
android:hasCode="false"
|
||||
android:debuggable="true"
|
||||
tools:strict="label">
|
||||
<activity android:name="android.app.NativeActivity"
|
||||
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
|
||||
android:label="Balloon"
|
||||
android:screenOrientation="portrait"
|
||||
android:configChanges="orientation|keyboardHidden">
|
||||
<meta-data android:name="android.app.lib_name" android:value="balloon" />
|
||||
<intent-filter>
|
||||
here is some text
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
15
internal/binres/testdata/gen.sh
vendored
Executable file
15
internal/binres/testdata/gen.sh
vendored
Executable file
@ -0,0 +1,15 @@
|
||||
#! /usr/bin/sh
|
||||
|
||||
# version of build-tools tests run against
|
||||
AAPT=${ANDROID_HOME}/build-tools/23.0.1/aapt
|
||||
|
||||
# minimum version of android api for resource identifiers supported
|
||||
APIJAR=${ANDROID_HOME}/platforms/android-10/android.jar
|
||||
|
||||
for f in *.xml; do
|
||||
cp "$f" AndroidManifest.xml
|
||||
"$AAPT" p -M AndroidManifest.xml -I "$APIJAR" -F tmp.apk
|
||||
unzip -qq -o tmp.apk
|
||||
mv AndroidManifest.xml "${f:0:-3}bin"
|
||||
rm tmp.apk
|
||||
done
|
Loading…
x
Reference in New Issue
Block a user