first commit

This commit is contained in:
2024-07-23 10:23:43 +08:00
commit 7b4c2521a3
126 changed files with 15931 additions and 0 deletions

View File

@ -0,0 +1,196 @@
package apk
import (
"archive/zip"
"bytes"
"fmt"
"git.bvbej.com/bvbej/base-golang/pkg/android_binary"
"image"
"io"
"os"
"strconv"
_ "image/jpeg" // handle jpeg format
_ "image/png" // handle png format
)
// Apk is an application package file for android.
type Apk struct {
f *os.File
zipreader *zip.Reader
manifest Manifest
table *android_binary.TableFile
}
// OpenFile will open the file specified by filename and return Apk
func OpenFile(filename string) (apk *Apk, err error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer func() {
if err != nil {
f.Close()
}
}()
fi, err := f.Stat()
if err != nil {
return nil, err
}
apk, err = OpenZipReader(f, fi.Size())
if err != nil {
return nil, err
}
apk.f = f
return
}
// OpenZipReader has same arguments like zip.NewReader
func OpenZipReader(r io.ReaderAt, size int64) (*Apk, error) {
zipreader, err := zip.NewReader(r, size)
if err != nil {
return nil, err
}
apk := &Apk{
zipreader: zipreader,
}
if err = apk.parseResources(); err != nil {
return nil, err
}
if err = apk.parseManifest(); err != nil {
return nil, errorf("parse-manifest: %w", err)
}
return apk, nil
}
// Close is avaliable only if apk is created with OpenFile
func (k *Apk) Close() error {
if k.f == nil {
return nil
}
return k.f.Close()
}
// Icon returns the icon image of the APK.
func (k *Apk) Icon(resConfig *android_binary.ResTableConfig) (image.Image, error) {
iconPath, err := k.manifest.App.Icon.WithResTableConfig(resConfig).String()
if err != nil {
return nil, err
}
if android_binary.IsResID(iconPath) {
return nil, newError("unable to convert icon-id to icon path")
}
imgData, err := k.readZipFile(iconPath)
if err != nil {
return nil, err
}
m, _, err := image.Decode(bytes.NewReader(imgData))
return m, err
}
// Label returns the label of the APK.
func (k *Apk) Label(resConfig *android_binary.ResTableConfig) (s string, err error) {
s, err = k.manifest.App.Label.WithResTableConfig(resConfig).String()
if err != nil {
return
}
if android_binary.IsResID(s) {
err = newError("unable to convert label-id to string")
}
return
}
// Manifest returns the manifest of the APK.
func (k *Apk) Manifest() Manifest {
return k.manifest
}
// PackageName returns the package name of the APK.
func (k *Apk) PackageName() string {
return k.manifest.Package.MustString()
}
func isMainIntentFilter(intent ActivityIntentFilter) bool {
ok := false
for _, action := range intent.Actions {
s, err := action.Name.String()
if err == nil && s == "android.intent.action.MAIN" {
ok = true
break
}
}
if !ok {
return false
}
ok = false
for _, category := range intent.Categories {
s, err := category.Name.String()
if err == nil && s == "android.intent.category.LAUNCHER" {
ok = true
break
}
}
return ok
}
// MainActivity returns the name of the main activity.
func (k *Apk) MainActivity() (activity string, err error) {
for _, act := range k.manifest.App.Activities {
for _, intent := range act.IntentFilters {
if isMainIntentFilter(intent) {
return act.Name.String()
}
}
}
for _, act := range k.manifest.App.ActivityAliases {
for _, intent := range act.IntentFilters {
if isMainIntentFilter(intent) {
return act.TargetActivity.String()
}
}
}
return "", newError("No main activity found")
}
func (k *Apk) parseManifest() error {
xmlData, err := k.readZipFile("AndroidManifest.xml")
if err != nil {
return errorf("failed to read AndroidManifest.xml: %w", err)
}
xmlfile, err := android_binary.NewXMLFile(bytes.NewReader(xmlData))
if err != nil {
return errorf("failed to parse AndroidManifest.xml: %w", err)
}
return xmlfile.Decode(&k.manifest, k.table, nil)
}
func (k *Apk) parseResources() (err error) {
resData, err := k.readZipFile("resources.arsc")
if err != nil {
return
}
k.table, err = android_binary.NewTableFile(bytes.NewReader(resData))
return
}
func (k *Apk) readZipFile(name string) (data []byte, err error) {
buf := bytes.NewBuffer(nil)
for _, file := range k.zipreader.File {
if file.Name != name {
continue
}
rc, er := file.Open()
if er != nil {
err = er
return
}
defer rc.Close()
_, err = io.Copy(buf, rc)
if err != nil {
return
}
return buf.Bytes(), nil
}
return nil, fmt.Errorf("File %s not found", strconv.Quote(name))
}

View File

@ -0,0 +1,108 @@
package apk
import "git.bvbej.com/bvbej/base-golang/pkg/android_binary"
// Instrumentation is an application instrumentation code.
type Instrumentation struct {
Name android_binary.String `xml:"http://schemas.android.com/apk/res/android name,attr"`
Target android_binary.String `xml:"http://schemas.android.com/apk/res/android targetPackage,attr"`
HandleProfiling android_binary.Bool `xml:"http://schemas.android.com/apk/res/android handleProfiling,attr"`
FunctionalTest android_binary.Bool `xml:"http://schemas.android.com/apk/res/android functionalTest,attr"`
}
// ActivityAction is an action of an activity.
type ActivityAction struct {
Name android_binary.String `xml:"http://schemas.android.com/apk/res/android name,attr"`
}
// ActivityCategory is a category of an activity.
type ActivityCategory struct {
Name android_binary.String `xml:"http://schemas.android.com/apk/res/android name,attr"`
}
// ActivityIntentFilter is an androidbinary.Int32ent filter of an activity.
type ActivityIntentFilter struct {
Actions []ActivityAction `xml:"action"`
Categories []ActivityCategory `xml:"category"`
}
// AppActivity is an activity in an application.
type AppActivity struct {
Theme android_binary.String `xml:"http://schemas.android.com/apk/res/android theme,attr"`
Name android_binary.String `xml:"http://schemas.android.com/apk/res/android name,attr"`
Label android_binary.String `xml:"http://schemas.android.com/apk/res/android label,attr"`
ScreenOrientation android_binary.String `xml:"http://schemas.android.com/apk/res/android screenOrientation,attr"`
IntentFilters []ActivityIntentFilter `xml:"intent-filter"`
}
// AppActivityAlias https://developer.android.com/guide/topics/manifest/activity-alias-element
type AppActivityAlias struct {
Name android_binary.String `xml:"http://schemas.android.com/apk/res/android name,attr"`
Label android_binary.String `xml:"http://schemas.android.com/apk/res/android label,attr"`
TargetActivity android_binary.String `xml:"http://schemas.android.com/apk/res/android targetActivity,attr"`
IntentFilters []ActivityIntentFilter `xml:"intent-filter"`
}
// MetaData is a metadata in an application.
type MetaData struct {
Name android_binary.String `xml:"http://schemas.android.com/apk/res/android name,attr"`
Value android_binary.String `xml:"http://schemas.android.com/apk/res/android value,attr"`
}
// Application is an application in an APK.
type Application struct {
AllowTaskReparenting android_binary.Bool `xml:"http://schemas.android.com/apk/res/android allowTaskReparenting,attr"`
AllowBackup android_binary.Bool `xml:"http://schemas.android.com/apk/res/android allowBackup,attr"`
BackupAgent android_binary.String `xml:"http://schemas.android.com/apk/res/android backupAgent,attr"`
Debuggable android_binary.Bool `xml:"http://schemas.android.com/apk/res/android debuggable,attr"`
Description android_binary.String `xml:"http://schemas.android.com/apk/res/android description,attr"`
Enabled android_binary.Bool `xml:"http://schemas.android.com/apk/res/android enabled,attr"`
HasCode android_binary.Bool `xml:"http://schemas.android.com/apk/res/android hasCode,attr"`
HardwareAccelerated android_binary.Bool `xml:"http://schemas.android.com/apk/res/android hardwareAccelerated,attr"`
Icon android_binary.String `xml:"http://schemas.android.com/apk/res/android icon,attr"`
KillAfterRestore android_binary.Bool `xml:"http://schemas.android.com/apk/res/android killAfterRestore,attr"`
LargeHeap android_binary.Bool `xml:"http://schemas.android.com/apk/res/android largeHeap,attr"`
Label android_binary.String `xml:"http://schemas.android.com/apk/res/android label,attr"`
Logo android_binary.String `xml:"http://schemas.android.com/apk/res/android logo,attr"`
ManageSpaceActivity android_binary.String `xml:"http://schemas.android.com/apk/res/android manageSpaceActivity,attr"`
Name android_binary.String `xml:"http://schemas.android.com/apk/res/android name,attr"`
Permission android_binary.String `xml:"http://schemas.android.com/apk/res/android permission,attr"`
Persistent android_binary.Bool `xml:"http://schemas.android.com/apk/res/android persistent,attr"`
Process android_binary.String `xml:"http://schemas.android.com/apk/res/android process,attr"`
RestoreAnyVersion android_binary.Bool `xml:"http://schemas.android.com/apk/res/android restoreAnyVersion,attr"`
RequiredAccountType android_binary.String `xml:"http://schemas.android.com/apk/res/android requiredAccountType,attr"`
RestrictedAccountType android_binary.String `xml:"http://schemas.android.com/apk/res/android restrictedAccountType,attr"`
SupportsRtl android_binary.Bool `xml:"http://schemas.android.com/apk/res/android supportsRtl,attr"`
TaskAffinity android_binary.String `xml:"http://schemas.android.com/apk/res/android taskAffinity,attr"`
TestOnly android_binary.Bool `xml:"http://schemas.android.com/apk/res/android testOnly,attr"`
Theme android_binary.String `xml:"http://schemas.android.com/apk/res/android theme,attr"`
UIOptions android_binary.String `xml:"http://schemas.android.com/apk/res/android uiOptions,attr"`
VMSafeMode android_binary.Bool `xml:"http://schemas.android.com/apk/res/android vmSafeMode,attr"`
Activities []AppActivity `xml:"activity"`
ActivityAliases []AppActivityAlias `xml:"activity-alias"`
MetaData []MetaData `xml:"meta-data"`
}
// UsesSDK is target SDK version.
type UsesSDK struct {
Min android_binary.Int32 `xml:"http://schemas.android.com/apk/res/android minSdkVersion,attr"`
Target android_binary.Int32 `xml:"http://schemas.android.com/apk/res/android targetSdkVersion,attr"`
Max android_binary.Int32 `xml:"http://schemas.android.com/apk/res/android maxSdkVersion,attr"`
}
// UsesPermission is user grant the system permission.
type UsesPermission struct {
Name android_binary.String `xml:"http://schemas.android.com/apk/res/android name,attr"`
Max android_binary.Int32 `xml:"http://schemas.android.com/apk/res/android maxSdkVersion,attr"`
}
// Manifest is a manifest of an APK.
type Manifest struct {
Package android_binary.String `xml:"package,attr"`
VersionCode android_binary.Int32 `xml:"http://schemas.android.com/apk/res/android versionCode,attr"`
VersionName android_binary.String `xml:"http://schemas.android.com/apk/res/android versionName,attr"`
App Application `xml:"application"`
Instrument Instrumentation `xml:"instrumentation"`
SDK UsesSDK `xml:"uses-sdk"`
UsesPermissions []UsesPermission `xml:"uses-permission"`
}

View File

@ -0,0 +1,9 @@
package apk
import (
"errors"
"fmt"
)
var newError = errors.New
var errorf = fmt.Errorf

View File

@ -0,0 +1,258 @@
package android_binary
import (
"bytes"
"encoding/binary"
"io"
"unicode/utf16"
)
// ChunkType is a type of a resource chunk.
type ChunkType uint16
// Chunk types.
const (
ResNullChunkType ChunkType = 0x0000
ResStringPoolChunkType ChunkType = 0x0001
ResTableChunkType ChunkType = 0x0002
ResXMLChunkType ChunkType = 0x0003
// Chunk types in RES_XML_TYPE
ResXMLFirstChunkType ChunkType = 0x0100
ResXMLStartNamespaceType ChunkType = 0x0100
ResXMLEndNamespaceType ChunkType = 0x0101
ResXMLStartElementType ChunkType = 0x0102
ResXMLEndElementType ChunkType = 0x0103
ResXMLCDataType ChunkType = 0x0104
ResXMLLastChunkType ChunkType = 0x017f
// This contains a uint32_t array mapping strings in the string
// pool back to resource identifiers. It is optional.
ResXMLResourceMapType ChunkType = 0x0180
// Chunk types in RES_TABLE_TYPE
ResTablePackageType ChunkType = 0x0200
ResTableTypeType ChunkType = 0x0201
ResTableTypeSpecType ChunkType = 0x0202
)
// ResChunkHeader is a header of a resource chunk.
type ResChunkHeader struct {
Type ChunkType
HeaderSize uint16
Size uint32
}
// Flags are flags for string pool header.
type Flags uint32
// the values of Flags.
const (
SortedFlag Flags = 1 << 0
UTF8Flag Flags = 1 << 8
)
// ResStringPoolHeader is a chunk header of string pool.
type ResStringPoolHeader struct {
Header ResChunkHeader
StringCount uint32
StyleCount uint32
Flags Flags
StringStart uint32
StylesStart uint32
}
// ResStringPoolSpan is a span of style information associated with
// a string in the pool.
type ResStringPoolSpan struct {
FirstChar, LastChar uint32
}
// ResStringPool is a string pool resource.
type ResStringPool struct {
Header ResStringPoolHeader
Strings []string
Styles []ResStringPoolSpan
}
// NilResStringPoolRef is nil reference for string pool.
const NilResStringPoolRef = ResStringPoolRef(0xFFFFFFFF)
// ResStringPoolRef is a type representing a reference to a string.
type ResStringPoolRef uint32
// DataType is a type of the data value.
type DataType uint8
// The constants for DataType
const (
TypeNull DataType = 0x00
TypeReference DataType = 0x01
TypeAttribute DataType = 0x02
TypeString DataType = 0x03
TypeFloat DataType = 0x04
TypeDemention DataType = 0x05
TypeFraction DataType = 0x06
TypeFirstInt DataType = 0x10
TypeIntDec DataType = 0x10
TypeIntHex DataType = 0x11
TypeIntBoolean DataType = 0x12
TypeFirstColorInt DataType = 0x1c
TypeIntColorARGB8 DataType = 0x1c
TypeIntColorRGB8 DataType = 0x1d
TypeIntColorARGB4 DataType = 0x1e
TypeIntColorRGB4 DataType = 0x1f
TypeLastColorInt DataType = 0x1f
TypeLastInt DataType = 0x1f
)
// ResValue is a representation of a value in a resource
type ResValue struct {
Size uint16
Res0 uint8
DataType DataType
Data uint32
}
// GetString returns a string referenced by ref.
func (pool *ResStringPool) GetString(ref ResStringPoolRef) string {
return pool.Strings[int(ref)]
}
func readStringPool(sr *io.SectionReader) (*ResStringPool, error) {
sp := new(ResStringPool)
if err := binary.Read(sr, binary.LittleEndian, &sp.Header); err != nil {
return nil, err
}
stringStarts := make([]uint32, sp.Header.StringCount)
if err := binary.Read(sr, binary.LittleEndian, stringStarts); err != nil {
return nil, err
}
styleStarts := make([]uint32, sp.Header.StyleCount)
if err := binary.Read(sr, binary.LittleEndian, styleStarts); err != nil {
return nil, err
}
sp.Strings = make([]string, sp.Header.StringCount)
for i, start := range stringStarts {
var str string
var err error
if _, err := sr.Seek(int64(sp.Header.StringStart+start), io.SeekStart); err != nil {
return nil, err
}
if (sp.Header.Flags & UTF8Flag) == 0 {
str, err = readUTF16(sr)
} else {
str, err = readUTF8(sr)
}
if err != nil {
return nil, err
}
sp.Strings[i] = str
}
sp.Styles = make([]ResStringPoolSpan, sp.Header.StyleCount)
for i, start := range styleStarts {
if _, err := sr.Seek(int64(sp.Header.StylesStart+start), io.SeekStart); err != nil {
return nil, err
}
if err := binary.Read(sr, binary.LittleEndian, &sp.Styles[i]); err != nil {
return nil, err
}
}
return sp, nil
}
func readUTF16(sr *io.SectionReader) (string, error) {
// read length of string
size, err := readUTF16length(sr)
if err != nil {
return "", err
}
// read string value
buf := make([]uint16, size)
if err := binary.Read(sr, binary.LittleEndian, buf); err != nil {
return "", err
}
return string(utf16.Decode(buf)), nil
}
func readUTF16length(sr *io.SectionReader) (int, error) {
var size int
var first, second uint16
if err := binary.Read(sr, binary.LittleEndian, &first); err != nil {
return 0, err
}
if (first & 0x8000) != 0 {
if err := binary.Read(sr, binary.LittleEndian, &second); err != nil {
return 0, err
}
size = (int(first&0x7FFF) << 16) + int(second)
} else {
size = int(first)
}
return size, nil
}
func readUTF8(sr *io.SectionReader) (string, error) {
// skip utf16 length
_, err := readUTF8length(sr)
if err != nil {
return "", err
}
// read lenth of string
size, err := readUTF8length(sr)
if err != nil {
return "", err
}
buf := make([]uint8, size)
if err := binary.Read(sr, binary.LittleEndian, buf); err != nil {
return "", err
}
return string(buf), nil
}
func readUTF8length(sr *io.SectionReader) (int, error) {
var size int
var first, second uint8
if err := binary.Read(sr, binary.LittleEndian, &first); err != nil {
return 0, err
}
if (first & 0x80) != 0 {
if err := binary.Read(sr, binary.LittleEndian, &second); err != nil {
return 0, err
}
size = (int(first&0x7F) << 8) + int(second)
} else {
size = int(first)
}
return size, nil
}
func newZeroFilledReader(r io.Reader, actual int64, expected int64) (io.Reader, error) {
if actual >= expected {
// no need to fill
return r, nil
}
// read `actual' bytes from r, and
buf := new(bytes.Buffer)
if _, err := io.CopyN(buf, r, actual); err != nil {
return nil, err
}
// fill zero until `expected' bytes
for i := actual; i < expected; i++ {
if err := buf.WriteByte(0x00); err != nil {
return nil, err
}
}
return buf, nil
}

1093
pkg/android_binary/table.go Normal file

File diff suppressed because it is too large Load Diff

333
pkg/android_binary/type.go Normal file
View File

@ -0,0 +1,333 @@
package android_binary
import (
"encoding/xml"
"fmt"
"reflect"
"strconv"
)
type injector interface {
inject(table *TableFile, config *ResTableConfig)
}
var injectorType = reflect.TypeOf((*injector)(nil)).Elem()
func inject(val reflect.Value, table *TableFile, config *ResTableConfig) {
if val.Kind() == reflect.Ptr {
if val.IsNil() {
return
}
val = val.Elem()
}
if val.CanInterface() && val.Type().Implements(injectorType) {
val.Interface().(injector).inject(table, config)
return
}
if val.CanAddr() {
pv := val.Addr()
if pv.CanInterface() && pv.Type().Implements(injectorType) {
pv.Interface().(injector).inject(table, config)
return
}
}
switch val.Kind() {
default:
// ignore other types
return
case reflect.Slice, reflect.Array:
l := val.Len()
for i := 0; i < l; i++ {
inject(val.Index(i), table, config)
}
return
case reflect.Struct:
l := val.NumField()
for i := 0; i < l; i++ {
inject(val.Field(i), table, config)
}
}
}
// Bool is a boolean value in XML file.
// It may be an immediate value or a reference.
type Bool struct {
value string
table *TableFile
config *ResTableConfig
}
// WithTableFile ties TableFile to the Bool.
func (v Bool) WithTableFile(table *TableFile) Bool {
return Bool{
value: v.value,
table: table,
config: v.config,
}
}
// WithResTableConfig ties ResTableConfig to the Bool.
func (v Bool) WithResTableConfig(config *ResTableConfig) Bool {
return Bool{
value: v.value,
table: v.table,
config: config,
}
}
func (v *Bool) inject(table *TableFile, config *ResTableConfig) {
v.table = table
v.config = config
}
// SetBool sets a boolean value.
func (v *Bool) SetBool(value bool) {
v.value = strconv.FormatBool(value)
}
// SetResID sets a boolean value with the resource id.
func (v *Bool) SetResID(resID ResID) {
v.value = resID.String()
}
// UnmarshalXMLAttr implements xml.UnmarshalerAttr.
func (v *Bool) UnmarshalXMLAttr(attr xml.Attr) error {
v.value = attr.Value
return nil
}
// MarshalXMLAttr implements xml.MarshalerAttr.
func (v Bool) MarshalXMLAttr(name xml.Name) (xml.Attr, error) {
if v.value == "" {
// return the zero value of bool
return xml.Attr{
Name: name,
Value: "false",
}, nil
}
return xml.Attr{
Name: name,
Value: v.value,
}, nil
}
// Bool returns the boolean value.
// It resolves the reference if needed.
func (v Bool) Bool() (bool, error) {
if v.value == "" {
return false, nil
}
if !IsResID(v.value) {
return strconv.ParseBool(v.value)
}
id, err := ParseResID(v.value)
if err != nil {
return false, err
}
value, err := v.table.GetResource(id, v.config)
if err != nil {
return false, err
}
ret, ok := value.(bool)
if !ok {
return false, fmt.Errorf("invalid type: %T", value)
}
return ret, nil
}
// MustBool is same as Bool, but it panics if it fails to parse the value.
func (v Bool) MustBool() bool {
ret, err := v.Bool()
if err != nil {
panic(err)
}
return ret
}
// Int32 is an integer value in XML file.
// It may be an immediate value or a reference.
type Int32 struct {
value string
table *TableFile
config *ResTableConfig
}
// WithTableFile ties TableFile to the Bool.
func (v Int32) WithTableFile(table *TableFile) Int32 {
return Int32{
value: v.value,
table: table,
config: v.config,
}
}
// WithResTableConfig ties ResTableConfig to the Bool.
func (v Int32) WithResTableConfig(config *ResTableConfig) Bool {
return Bool{
value: v.value,
table: v.table,
config: config,
}
}
func (v *Int32) inject(table *TableFile, config *ResTableConfig) {
v.table = table
v.config = config
}
// SetInt32 sets an integer value.
func (v *Int32) SetInt32(value int32) {
v.value = strconv.FormatInt(int64(value), 10)
}
// SetResID sets a boolean value with the resource id.
func (v *Int32) SetResID(resID ResID) {
v.value = resID.String()
}
// UnmarshalXMLAttr implements xml.UnmarshalerAttr.
func (v *Int32) UnmarshalXMLAttr(attr xml.Attr) error {
v.value = attr.Value
return nil
}
// MarshalXMLAttr implements xml.MarshalerAttr.
func (v Int32) MarshalXMLAttr(name xml.Name) (xml.Attr, error) {
if v.value == "" {
// return the zero value of int32
return xml.Attr{
Name: name,
Value: "0",
}, nil
}
return xml.Attr{
Name: name,
Value: v.value,
}, nil
}
// Int32 returns the integer value.
// It resolves the reference if needed.
func (v Int32) Int32() (int32, error) {
if v.value == "" {
return 0, nil
}
if !IsResID(v.value) {
v, err := strconv.ParseInt(v.value, 10, 32)
return int32(v), err
}
id, err := ParseResID(v.value)
if err != nil {
return 0, err
}
value, err := v.table.GetResource(id, v.config)
if err != nil {
return 0, err
}
ret, ok := value.(uint32)
if !ok {
return 0, fmt.Errorf("invalid type: %T", value)
}
return int32(ret), nil
}
// MustInt32 is same as Int32, but it panics if it fails to parse the value.
func (v Int32) MustInt32() int32 {
ret, err := v.Int32()
if err != nil {
panic(err)
}
return ret
}
// String is a boolean value in XML file.
// It may be an immediate value or a reference.
type String struct {
value string
table *TableFile
config *ResTableConfig
}
// WithTableFile ties TableFile to the Bool.
func (v String) WithTableFile(table *TableFile) String {
return String{
value: v.value,
table: table,
config: v.config,
}
}
// WithResTableConfig ties ResTableConfig to the Bool.
func (v String) WithResTableConfig(config *ResTableConfig) String {
return String{
value: v.value,
table: v.table,
config: config,
}
}
func (v *String) inject(table *TableFile, config *ResTableConfig) {
v.table = table
v.config = config
}
// SetString sets a string value.
func (v *String) SetString(value string) {
v.value = value
}
// SetResID sets a boolean value with the resource id.
func (v *String) SetResID(resID ResID) {
v.value = resID.String()
}
// UnmarshalXMLAttr implements xml.UnmarshalerAttr.
func (v *String) UnmarshalXMLAttr(attr xml.Attr) error {
v.value = attr.Value
return nil
}
// MarshalXMLAttr implements xml.MarshalerAttr.
func (v String) MarshalXMLAttr(name xml.Name) (xml.Attr, error) {
return xml.Attr{
Name: name,
Value: v.value,
}, nil
}
// String returns the string value.
// It resolves the reference if needed.
func (v String) String() (string, error) {
if !IsResID(v.value) {
return v.value, nil
}
id, err := ParseResID(v.value)
if err != nil {
return "", err
}
value, err := v.table.GetResource(id, v.config)
if err != nil {
return "", err
}
//todo 读取套娃
switch value.(type) {
case string:
return value.(string), nil
case uint32:
return fmt.Sprintf("%d", value.(uint32)), nil
default:
return "", nil
}
}
// MustString is same as String, but it panics if it fails to parse the value.
func (v String) MustString() string {
ret, err := v.String()
if err != nil {
panic(err)
}
return ret
}

271
pkg/android_binary/xml.go Normal file
View 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
}