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