first commit
This commit is contained in:
		
							
								
								
									
										196
									
								
								pkg/android_binary/apk/apk.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										196
									
								
								pkg/android_binary/apk/apk.go
									
									
									
									
									
										Normal 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))
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										108
									
								
								pkg/android_binary/apk/apkxml.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								pkg/android_binary/apk/apkxml.go
									
									
									
									
									
										Normal 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"`
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								pkg/android_binary/apk/errors.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								pkg/android_binary/apk/errors.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
package apk
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var newError = errors.New
 | 
			
		||||
var errorf = fmt.Errorf
 | 
			
		||||
							
								
								
									
										258
									
								
								pkg/android_binary/common.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										258
									
								
								pkg/android_binary/common.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										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
									
								
							
							
						
						
									
										333
									
								
								pkg/android_binary/type.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										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