first commit
This commit is contained in:
271
pkg/android_binary/xml.go
Normal file
271
pkg/android_binary/xml.go
Normal file
@ -0,0 +1,271 @@
|
||||
package android_binary
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// XMLFile is an XML file expressed in binary format.
|
||||
type XMLFile struct {
|
||||
stringPool *ResStringPool
|
||||
resourceMap []uint32
|
||||
notPrecessedNS map[ResStringPoolRef]ResStringPoolRef
|
||||
namespaces map[ResStringPoolRef]ResStringPoolRef
|
||||
xmlBuffer bytes.Buffer
|
||||
}
|
||||
|
||||
// ResXMLTreeNode is basic XML tree node.
|
||||
type ResXMLTreeNode struct {
|
||||
Header ResChunkHeader
|
||||
LineNumber uint32
|
||||
Comment ResStringPoolRef
|
||||
}
|
||||
|
||||
// ResXMLTreeNamespaceExt is extended XML tree node for namespace start/end nodes.
|
||||
type ResXMLTreeNamespaceExt struct {
|
||||
Prefix ResStringPoolRef
|
||||
URI ResStringPoolRef
|
||||
}
|
||||
|
||||
// ResXMLTreeAttrExt is extended XML tree node for start tags -- includes attribute.
|
||||
type ResXMLTreeAttrExt struct {
|
||||
NS ResStringPoolRef
|
||||
Name ResStringPoolRef
|
||||
AttributeStart uint16
|
||||
AttributeSize uint16
|
||||
AttributeCount uint16
|
||||
IDIndex uint16
|
||||
ClassIndex uint16
|
||||
StyleIndex uint16
|
||||
}
|
||||
|
||||
// ResXMLTreeAttribute is an attribute of start tags.
|
||||
type ResXMLTreeAttribute struct {
|
||||
NS ResStringPoolRef
|
||||
Name ResStringPoolRef
|
||||
RawValue ResStringPoolRef
|
||||
TypedValue ResValue
|
||||
}
|
||||
|
||||
// ResXMLTreeEndElementExt is extended XML tree node for element start/end nodes.
|
||||
type ResXMLTreeEndElementExt struct {
|
||||
NS ResStringPoolRef
|
||||
Name ResStringPoolRef
|
||||
}
|
||||
|
||||
// NewXMLFile returns a new XMLFile.
|
||||
func NewXMLFile(r io.ReaderAt) (*XMLFile, error) {
|
||||
f := new(XMLFile)
|
||||
sr := io.NewSectionReader(r, 0, 1<<63-1)
|
||||
|
||||
fmt.Fprintf(&f.xmlBuffer, xml.Header)
|
||||
|
||||
header := new(ResChunkHeader)
|
||||
if err := binary.Read(sr, binary.LittleEndian, header); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
offset := int64(header.HeaderSize)
|
||||
for offset < int64(header.Size) {
|
||||
chunkHeader, err := f.readChunk(r, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
offset += int64(chunkHeader.Size)
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// Reader returns a reader of XML file expressed in text format.
|
||||
func (f *XMLFile) Reader() *bytes.Reader {
|
||||
return bytes.NewReader(f.xmlBuffer.Bytes())
|
||||
}
|
||||
|
||||
// Decode decodes XML file and stores the result in the value pointed to by v.
|
||||
// To resolve the resource references, Decode also stores default TableFile and ResTableConfig in the value pointed to by v.
|
||||
func (f *XMLFile) Decode(v any, table *TableFile, config *ResTableConfig) error {
|
||||
decoder := xml.NewDecoder(f.Reader())
|
||||
if err := decoder.Decode(v); err != nil {
|
||||
return err
|
||||
}
|
||||
inject(reflect.ValueOf(v), table, config)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *XMLFile) readChunk(r io.ReaderAt, offset int64) (*ResChunkHeader, error) {
|
||||
sr := io.NewSectionReader(r, offset, 1<<63-1-offset)
|
||||
chunkHeader := &ResChunkHeader{}
|
||||
if _, err := sr.Seek(0, io.SeekStart); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := binary.Read(sr, binary.LittleEndian, chunkHeader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var err error
|
||||
if _, err := sr.Seek(0, io.SeekStart); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch chunkHeader.Type {
|
||||
case ResStringPoolChunkType:
|
||||
f.stringPool, err = readStringPool(sr)
|
||||
case ResXMLStartNamespaceType:
|
||||
err = f.readStartNamespace(sr)
|
||||
case ResXMLEndNamespaceType:
|
||||
err = f.readEndNamespace(sr)
|
||||
case ResXMLStartElementType:
|
||||
err = f.readStartElement(sr)
|
||||
case ResXMLEndElementType:
|
||||
err = f.readEndElement(sr)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return chunkHeader, nil
|
||||
}
|
||||
|
||||
// GetString returns a string referenced by ref.
|
||||
func (f *XMLFile) GetString(ref ResStringPoolRef) string {
|
||||
return f.stringPool.GetString(ref)
|
||||
}
|
||||
|
||||
func (f *XMLFile) readStartNamespace(sr *io.SectionReader) error {
|
||||
header := new(ResXMLTreeNode)
|
||||
if err := binary.Read(sr, binary.LittleEndian, header); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := sr.Seek(int64(header.Header.HeaderSize), io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
namespace := new(ResXMLTreeNamespaceExt)
|
||||
if err := binary.Read(sr, binary.LittleEndian, namespace); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if f.notPrecessedNS == nil {
|
||||
f.notPrecessedNS = make(map[ResStringPoolRef]ResStringPoolRef)
|
||||
}
|
||||
f.notPrecessedNS[namespace.URI] = namespace.Prefix
|
||||
|
||||
if f.namespaces == nil {
|
||||
f.namespaces = make(map[ResStringPoolRef]ResStringPoolRef)
|
||||
}
|
||||
f.namespaces[namespace.URI] = namespace.Prefix
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *XMLFile) readEndNamespace(sr *io.SectionReader) error {
|
||||
header := new(ResXMLTreeNode)
|
||||
if err := binary.Read(sr, binary.LittleEndian, header); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := sr.Seek(int64(header.Header.HeaderSize), io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
namespace := new(ResXMLTreeNamespaceExt)
|
||||
if err := binary.Read(sr, binary.LittleEndian, namespace); err != nil {
|
||||
return err
|
||||
}
|
||||
delete(f.namespaces, namespace.URI)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *XMLFile) addNamespacePrefix(ns, name ResStringPoolRef) string {
|
||||
if ns != NilResStringPoolRef {
|
||||
prefix := f.GetString(f.namespaces[ns])
|
||||
return fmt.Sprintf("%s:%s", prefix, f.GetString(name))
|
||||
}
|
||||
return f.GetString(name)
|
||||
}
|
||||
|
||||
func (f *XMLFile) readStartElement(sr *io.SectionReader) error {
|
||||
header := new(ResXMLTreeNode)
|
||||
if err := binary.Read(sr, binary.LittleEndian, header); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := sr.Seek(int64(header.Header.HeaderSize), io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
ext := new(ResXMLTreeAttrExt)
|
||||
if err := binary.Read(sr, binary.LittleEndian, ext); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Fprintf(&f.xmlBuffer, "<%s", f.addNamespacePrefix(ext.NS, ext.Name))
|
||||
|
||||
// output XML namespaces
|
||||
if f.notPrecessedNS != nil {
|
||||
for uri, prefix := range f.notPrecessedNS {
|
||||
fmt.Fprintf(&f.xmlBuffer, " xmlns:%s=\"", f.GetString(prefix))
|
||||
xml.Escape(&f.xmlBuffer, []byte(f.GetString(uri)))
|
||||
fmt.Fprint(&f.xmlBuffer, "\"")
|
||||
}
|
||||
f.notPrecessedNS = nil
|
||||
}
|
||||
|
||||
// process attributes
|
||||
offset := int64(ext.AttributeStart + header.Header.HeaderSize)
|
||||
for i := 0; i < int(ext.AttributeCount); i++ {
|
||||
if _, err := sr.Seek(offset, io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
attr := new(ResXMLTreeAttribute)
|
||||
binary.Read(sr, binary.LittleEndian, attr)
|
||||
|
||||
var value string
|
||||
if attr.RawValue != NilResStringPoolRef {
|
||||
value = f.GetString(attr.RawValue)
|
||||
} else {
|
||||
data := attr.TypedValue.Data
|
||||
switch attr.TypedValue.DataType {
|
||||
case TypeNull:
|
||||
value = ""
|
||||
case TypeReference:
|
||||
value = fmt.Sprintf("@0x%08X", data)
|
||||
case TypeIntDec:
|
||||
value = fmt.Sprintf("%d", data)
|
||||
case TypeIntHex:
|
||||
value = fmt.Sprintf("0x%08X", data)
|
||||
case TypeIntBoolean:
|
||||
if data != 0 {
|
||||
value = "true"
|
||||
} else {
|
||||
value = "false"
|
||||
}
|
||||
default:
|
||||
value = fmt.Sprintf("@0x%08X", data)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(&f.xmlBuffer, " %s=\"", f.addNamespacePrefix(attr.NS, attr.Name))
|
||||
xml.Escape(&f.xmlBuffer, []byte(value))
|
||||
fmt.Fprint(&f.xmlBuffer, "\"")
|
||||
offset += int64(ext.AttributeSize)
|
||||
}
|
||||
fmt.Fprint(&f.xmlBuffer, ">")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *XMLFile) readEndElement(sr *io.SectionReader) error {
|
||||
header := new(ResXMLTreeNode)
|
||||
if err := binary.Read(sr, binary.LittleEndian, header); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := sr.Seek(int64(header.Header.HeaderSize), io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
ext := new(ResXMLTreeEndElementExt)
|
||||
if err := binary.Read(sr, binary.LittleEndian, ext); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(&f.xmlBuffer, "</%s>", f.addNamespacePrefix(ext.NS, ext.Name))
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user