├── .github ├── dependabot.yml └── workflows │ ├── codeql-analysis.yml │ ├── fuzz.yml │ └── go.yml ├── LICENSE ├── README.md ├── apk ├── apk.go ├── apk_test.go ├── apkxml.go ├── errors.go └── testdata │ └── helloworld.apk ├── common.go ├── common_test.go ├── example_test.go ├── fuzz_test.go ├── go.mod ├── go.sum ├── table.go ├── table_test.go ├── testdata ├── AndroidManifest.xml ├── MyApplication │ ├── .gitignore │ ├── AndroidManifest.xml │ ├── app │ │ ├── .gitignore │ │ ├── build.gradle │ │ ├── proguard-rules.pro │ │ └── src │ │ │ ├── androidTest │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── shogo82148 │ │ │ │ └── androidbinary │ │ │ │ └── myapplication │ │ │ │ └── ExampleInstrumentedTest.kt │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ └── res │ │ │ │ ├── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ │ ├── drawable │ │ │ │ └── ic_launcher_background.xml │ │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ │ └── values │ │ │ │ ├── colors.xml │ │ │ │ ├── strings.xml │ │ │ │ ├── styles.xml │ │ │ │ └── values.xml │ │ │ └── test │ │ │ └── java │ │ │ └── com │ │ │ └── shogo82148 │ │ │ └── androidbinary │ │ │ └── myapplication │ │ │ └── ExampleUnitTest.kt │ ├── build.gradle │ ├── build.sh │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── resources.arsc │ └── settings.gradle ├── fuzz │ └── FuzzNewXMLFile │ │ ├── 4082a8258c184ace │ │ ├── 48339f138db8f5a2 │ │ ├── edccc9a94336dee2 │ │ └── ff4e319721ef52a0 └── resources.arsc ├── type.go ├── type_test.go ├── xml.go └── xml_test.go /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | 8 | # Maintain dependencies for GitHub Actions 9 | - package-ecosystem: "github-actions" 10 | directory: "/" 11 | schedule: 12 | interval: "daily" 13 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | name: "CodeQL" 7 | 8 | on: 9 | push: 10 | branches: [main] 11 | pull_request: 12 | # The branches below must be a subset of the branches above 13 | branches: [main] 14 | schedule: 15 | - cron: '0 5 * * 2' 16 | 17 | jobs: 18 | analyze: 19 | name: Analyze 20 | runs-on: ubuntu-latest 21 | 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | # Override automatic language detection by changing the below list 26 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] 27 | language: ['go'] 28 | # Learn more... 29 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 30 | 31 | steps: 32 | - name: Checkout repository 33 | uses: actions/checkout@v4 34 | with: 35 | # We must fetch at least the immediate parents so that if this is 36 | # a pull request then we can checkout the head. 37 | fetch-depth: 2 38 | 39 | # If this run was triggered by a pull request event, then checkout 40 | # the head of the pull request instead of the merge commit. 41 | - run: git checkout HEAD^2 42 | if: ${{ github.event_name == 'pull_request' }} 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v3 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v3 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v3 72 | -------------------------------------------------------------------------------- /.github/workflows/fuzz.yml: -------------------------------------------------------------------------------- 1 | name: "fuzz" 2 | on: 3 | workflow_dispatch: 4 | schedule: 5 | - cron: "22 13 * * 1,3" 6 | 7 | permissions: 8 | contents: write 9 | pull-requests: write 10 | 11 | jobs: 12 | list: 13 | runs-on: ubuntu-latest 14 | timeout-minutes: 10 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: actions/setup-go@v5 18 | with: 19 | go-version: "stable" 20 | - id: list 21 | uses: shogo82148/actions-go-fuzz/list@v1 22 | outputs: 23 | fuzz-tests: ${{steps.list.outputs.fuzz-tests}} 24 | 25 | fuzz: 26 | runs-on: ubuntu-latest 27 | timeout-minutes: 360 28 | needs: list 29 | strategy: 30 | fail-fast: false 31 | matrix: 32 | include: ${{fromJson(needs.list.outputs.fuzz-tests)}} 33 | steps: 34 | - uses: actions/checkout@v4 35 | - uses: actions/setup-go@v5 36 | with: 37 | go-version: "stable" 38 | - uses: shogo82148/actions-go-fuzz/run@v1 39 | with: 40 | packages: ${{ matrix.package }} 41 | fuzz-regexp: ${{ matrix.func }} 42 | fuzz-time: "355m" 43 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: [push, pull_request] 3 | jobs: 4 | test: 5 | name: Test 6 | runs-on: ubuntu-latest 7 | strategy: 8 | matrix: 9 | go: 10 | - "1" 11 | - "1.21" 12 | - "1.20" 13 | - "1.19" 14 | - "1.18" 15 | - "1.17" 16 | - "1.16" 17 | - "1.15" 18 | - "1.14" 19 | - "1.13" 20 | 21 | steps: 22 | - name: Check out code into the Go module directory 23 | uses: actions/checkout@v4 24 | 25 | - name: Set up Go ${{ matrix.go }} 26 | uses: actions/setup-go@v5 27 | with: 28 | go-version: ${{ matrix.go }} 29 | 30 | - name: Test 31 | run: go test -v ./... 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Ichinose Shogo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | androidbinary 2 | ===== 3 | 4 | [![Build Status](https://github.com/shogo82148/androidbinary/workflows/Test/badge.svg)](https://github.com/shogo82148/androidbinary/actions) 5 | [![GoDoc](https://godoc.org/github.com/shogo82148/androidbinary?status.svg)](https://godoc.org/github.com/shogo82148/androidbinary) 6 | 7 | Android binary file parser 8 | 9 | ## High Level API 10 | 11 | ### Parse APK files 12 | 13 | ``` go 14 | package main 15 | 16 | import ( 17 | "github.com/shogo82148/androidbinary/apk" 18 | ) 19 | 20 | func main() { 21 | pkg, _ := apk.OpenFile("your-android-app.apk") 22 | defer pkg.Close() 23 | 24 | icon, _ := pkg.Icon(nil) // returns the icon of APK as image.Image 25 | pkgName := pkg.PackageName() // returns the package name 26 | 27 | resConfigEN := &androidbinary.ResTableConfig{ 28 | Language: [2]uint8{uint8('e'), uint8('n')}, 29 | } 30 | appLabel, _ = pkg.Label(resConfigEN) // get app label for en translation 31 | } 32 | ``` 33 | 34 | ## Low Level API 35 | 36 | ### Parse XML binary 37 | 38 | ``` go 39 | package main 40 | 41 | import ( 42 | "encoding/xml" 43 | 44 | "github.com/shogo82148/androidbinary" 45 | "github.com/shogo82148/androidbinary/apk" 46 | ) 47 | 48 | func main() { 49 | f, _ := os.Open("AndroidManifest.xml") 50 | xml, _ := androidbinary.NewXMLFile(f) 51 | reader := xml.Reader() 52 | 53 | // read XML from reader 54 | var manifest apk.Manifest 55 | data, _ := ioutil.ReadAll(reader) 56 | xml.Unmarshal(data, &manifest) 57 | } 58 | ``` 59 | 60 | ### Parse Resource files 61 | 62 | ``` go 63 | package main 64 | 65 | import ( 66 | "fmt" 67 | "github.com/shogo82148/androidbinary" 68 | ) 69 | 70 | func main() { 71 | f, _ := os.Open("resources.arsc") 72 | rsc, _ := androidbinary.NewTableFile(f) 73 | resource, _ := rsc.GetResource(androidbinary.ResID(0xCAFEBABE), nil) 74 | fmt.Println(resource) 75 | } 76 | ``` 77 | 78 | ## License 79 | 80 | This software is released under the MIT License, see LICENSE. 81 | -------------------------------------------------------------------------------- /apk/apk.go: -------------------------------------------------------------------------------- 1 | package apk 2 | 3 | import ( 4 | "archive/zip" 5 | "bytes" 6 | "fmt" 7 | "image" 8 | "io" 9 | "os" 10 | 11 | "github.com/shogo82148/androidbinary" 12 | 13 | _ "image/jpeg" // handle jpeg format 14 | _ "image/png" // handle png format 15 | ) 16 | 17 | // Apk is an application package file for android. 18 | type Apk struct { 19 | f *os.File 20 | zipreader *zip.Reader 21 | manifest Manifest 22 | table *androidbinary.TableFile 23 | } 24 | 25 | // OpenFile will open the file specified by filename and return Apk 26 | func OpenFile(filename string) (apk *Apk, err error) { 27 | f, err := os.Open(filename) 28 | if err != nil { 29 | return nil, err 30 | } 31 | defer func() { 32 | if err != nil { 33 | f.Close() 34 | } 35 | }() 36 | fi, err := f.Stat() 37 | if err != nil { 38 | return nil, err 39 | } 40 | apk, err = OpenZipReader(f, fi.Size()) 41 | if err != nil { 42 | return nil, err 43 | } 44 | apk.f = f 45 | return 46 | } 47 | 48 | // OpenZipReader has same arguments like zip.NewReader 49 | func OpenZipReader(r io.ReaderAt, size int64) (*Apk, error) { 50 | zipreader, err := zip.NewReader(r, size) 51 | if err != nil { 52 | return nil, err 53 | } 54 | apk := &Apk{ 55 | zipreader: zipreader, 56 | } 57 | if err = apk.parseResources(); err != nil { 58 | return nil, err 59 | } 60 | if err = apk.parseManifest(); err != nil { 61 | return nil, errorf("parse-manifest: %w", err) 62 | } 63 | return apk, nil 64 | } 65 | 66 | // Close is avaliable only if apk is created with OpenFile 67 | func (k *Apk) Close() error { 68 | if k.f == nil { 69 | return nil 70 | } 71 | return k.f.Close() 72 | } 73 | 74 | // Icon returns the icon image of the APK. 75 | func (k *Apk) Icon(resConfig *androidbinary.ResTableConfig) (image.Image, error) { 76 | iconPath, err := k.manifest.App.Icon.WithResTableConfig(resConfig).String() 77 | if err != nil { 78 | return nil, err 79 | } 80 | if androidbinary.IsResID(iconPath) { 81 | return nil, newError("unable to convert icon-id to icon path") 82 | } 83 | imgData, err := k.readZipFile(iconPath) 84 | if err != nil { 85 | return nil, err 86 | } 87 | m, _, err := image.Decode(bytes.NewReader(imgData)) 88 | return m, err 89 | } 90 | 91 | // Label returns the label of the APK. 92 | func (k *Apk) Label(resConfig *androidbinary.ResTableConfig) (s string, err error) { 93 | s, err = k.manifest.App.Label.WithResTableConfig(resConfig).String() 94 | if err != nil { 95 | return 96 | } 97 | if androidbinary.IsResID(s) { 98 | err = newError("unable to convert label-id to string") 99 | } 100 | return 101 | } 102 | 103 | // Manifest returns the manifest of the APK. 104 | func (k *Apk) Manifest() Manifest { 105 | return k.manifest 106 | } 107 | 108 | // PackageName returns the package name of the APK. 109 | func (k *Apk) PackageName() string { 110 | return k.manifest.Package.MustString() 111 | } 112 | 113 | func isMainIntentFilter(intent ActivityIntentFilter) bool { 114 | ok := false 115 | for _, action := range intent.Actions { 116 | s, err := action.Name.String() 117 | if err == nil && s == "android.intent.action.MAIN" { 118 | ok = true 119 | break 120 | } 121 | } 122 | if !ok { 123 | return false 124 | } 125 | ok = false 126 | for _, category := range intent.Categories { 127 | s, err := category.Name.String() 128 | if err == nil && s == "android.intent.category.LAUNCHER" { 129 | ok = true 130 | break 131 | } 132 | } 133 | return ok 134 | } 135 | 136 | // MainActivity returns the name of the main activity. 137 | func (k *Apk) MainActivity() (activity string, err error) { 138 | for _, act := range k.manifest.App.Activities { 139 | for _, intent := range act.IntentFilters { 140 | if isMainIntentFilter(intent) { 141 | return act.Name.String() 142 | } 143 | } 144 | } 145 | for _, act := range k.manifest.App.ActivityAliases { 146 | for _, intent := range act.IntentFilters { 147 | if isMainIntentFilter(intent) { 148 | return act.TargetActivity.String() 149 | } 150 | } 151 | } 152 | 153 | return "", newError("No main activity found") 154 | } 155 | 156 | func (k *Apk) parseManifest() error { 157 | xmlData, err := k.readZipFile("AndroidManifest.xml") 158 | if err != nil { 159 | return errorf("failed to read AndroidManifest.xml: %w", err) 160 | } 161 | xmlfile, err := androidbinary.NewXMLFile(bytes.NewReader(xmlData)) 162 | if err != nil { 163 | return errorf("failed to parse AndroidManifest.xml: %w", err) 164 | } 165 | return xmlfile.Decode(&k.manifest, k.table, nil) 166 | } 167 | 168 | func (k *Apk) parseResources() (err error) { 169 | resData, err := k.readZipFile("resources.arsc") 170 | if err != nil { 171 | return 172 | } 173 | k.table, err = androidbinary.NewTableFile(bytes.NewReader(resData)) 174 | return 175 | } 176 | 177 | func (k *Apk) readZipFile(name string) (data []byte, err error) { 178 | buf := bytes.NewBuffer(nil) 179 | for _, file := range k.zipreader.File { 180 | if file.Name != name { 181 | continue 182 | } 183 | rc, er := file.Open() 184 | if er != nil { 185 | err = er 186 | return 187 | } 188 | defer rc.Close() 189 | _, err = io.Copy(buf, rc) 190 | if err != nil { 191 | return 192 | } 193 | return buf.Bytes(), nil 194 | } 195 | return nil, fmt.Errorf("apk: file %q not found", name) 196 | } 197 | -------------------------------------------------------------------------------- /apk/apk_test.go: -------------------------------------------------------------------------------- 1 | package apk 2 | 3 | import ( 4 | _ "image/jpeg" 5 | _ "image/png" 6 | "testing" 7 | ) 8 | 9 | func TestParseAPKFile(t *testing.T) { 10 | apk, err := OpenFile("testdata/helloworld.apk") 11 | if err != nil { 12 | t.Errorf("OpenFile error: %v", err) 13 | } 14 | defer apk.Close() 15 | 16 | icon, err := apk.Icon(nil) 17 | if err != nil { 18 | t.Errorf("Icon error: %v", err) 19 | } 20 | if icon == nil { 21 | t.Error("Icon is nil") 22 | } 23 | 24 | label, err := apk.Label(nil) 25 | if err != nil { 26 | t.Errorf("Label error: %v", err) 27 | } 28 | if label != "HelloWorld" { 29 | t.Errorf("Label is not HelloWorld: %s", label) 30 | } 31 | t.Log("app label:", label) 32 | 33 | if apk.PackageName() != "com.example.helloworld" { 34 | t.Errorf("PackageName is not com.example.helloworld: %s", apk.PackageName()) 35 | } 36 | 37 | manifest := apk.Manifest() 38 | if manifest.SDK.Target.MustInt32() != int32(24) { 39 | t.Errorf("SDK target is not 24: %d", manifest.SDK.Target.MustInt32()) 40 | } 41 | 42 | mainActivity, err := apk.MainActivity() 43 | if err != nil { 44 | t.Errorf("MainActivity error: %v", err) 45 | } 46 | if mainActivity != "com.example.helloworld.MainActivity" { 47 | t.Errorf("MainActivity is not com.example.helloworld.MainActivity: %s", mainActivity) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /apk/apkxml.go: -------------------------------------------------------------------------------- 1 | package apk 2 | 3 | import ( 4 | "github.com/shogo82148/androidbinary" 5 | ) 6 | 7 | // Instrumentation is an application instrumentation code. 8 | type Instrumentation struct { 9 | Name androidbinary.String `xml:"http://schemas.android.com/apk/res/android name,attr"` 10 | Target androidbinary.String `xml:"http://schemas.android.com/apk/res/android targetPackage,attr"` 11 | HandleProfiling androidbinary.Bool `xml:"http://schemas.android.com/apk/res/android handleProfiling,attr"` 12 | FunctionalTest androidbinary.Bool `xml:"http://schemas.android.com/apk/res/android functionalTest,attr"` 13 | } 14 | 15 | // ActivityAction is an action of an activity. 16 | type ActivityAction struct { 17 | Name androidbinary.String `xml:"http://schemas.android.com/apk/res/android name,attr"` 18 | } 19 | 20 | // ActivityCategory is a category of an activity. 21 | type ActivityCategory struct { 22 | Name androidbinary.String `xml:"http://schemas.android.com/apk/res/android name,attr"` 23 | } 24 | 25 | // ActivityIntentFilter is an androidbinary.Int32ent filter of an activity. 26 | type ActivityIntentFilter struct { 27 | Actions []ActivityAction `xml:"action"` 28 | Categories []ActivityCategory `xml:"category"` 29 | } 30 | 31 | // AppActivity is an activity in an application. 32 | type AppActivity struct { 33 | Theme androidbinary.String `xml:"http://schemas.android.com/apk/res/android theme,attr"` 34 | Name androidbinary.String `xml:"http://schemas.android.com/apk/res/android name,attr"` 35 | Label androidbinary.String `xml:"http://schemas.android.com/apk/res/android label,attr"` 36 | ScreenOrientation androidbinary.String `xml:"http://schemas.android.com/apk/res/android screenOrientation,attr"` 37 | IntentFilters []ActivityIntentFilter `xml:"intent-filter"` 38 | } 39 | 40 | // AppActivityAlias https://developer.android.com/guide/topics/manifest/activity-alias-element 41 | type AppActivityAlias struct { 42 | Name androidbinary.String `xml:"http://schemas.android.com/apk/res/android name,attr"` 43 | Label androidbinary.String `xml:"http://schemas.android.com/apk/res/android label,attr"` 44 | TargetActivity androidbinary.String `xml:"http://schemas.android.com/apk/res/android targetActivity,attr"` 45 | IntentFilters []ActivityIntentFilter `xml:"intent-filter"` 46 | } 47 | 48 | // MetaData is a metadata in an application. 49 | type MetaData struct { 50 | Name androidbinary.String `xml:"http://schemas.android.com/apk/res/android name,attr"` 51 | Value androidbinary.String `xml:"http://schemas.android.com/apk/res/android value,attr"` 52 | } 53 | 54 | // Application is an application in an APK. 55 | type Application struct { 56 | AllowTaskReparenting androidbinary.Bool `xml:"http://schemas.android.com/apk/res/android allowTaskReparenting,attr"` 57 | AllowBackup androidbinary.Bool `xml:"http://schemas.android.com/apk/res/android allowBackup,attr"` 58 | BackupAgent androidbinary.String `xml:"http://schemas.android.com/apk/res/android backupAgent,attr"` 59 | Debuggable androidbinary.Bool `xml:"http://schemas.android.com/apk/res/android debuggable,attr"` 60 | Description androidbinary.String `xml:"http://schemas.android.com/apk/res/android description,attr"` 61 | Enabled androidbinary.Bool `xml:"http://schemas.android.com/apk/res/android enabled,attr"` 62 | HasCode androidbinary.Bool `xml:"http://schemas.android.com/apk/res/android hasCode,attr"` 63 | HardwareAccelerated androidbinary.Bool `xml:"http://schemas.android.com/apk/res/android hardwareAccelerated,attr"` 64 | Icon androidbinary.String `xml:"http://schemas.android.com/apk/res/android icon,attr"` 65 | KillAfterRestore androidbinary.Bool `xml:"http://schemas.android.com/apk/res/android killAfterRestore,attr"` 66 | LargeHeap androidbinary.Bool `xml:"http://schemas.android.com/apk/res/android largeHeap,attr"` 67 | Label androidbinary.String `xml:"http://schemas.android.com/apk/res/android label,attr"` 68 | Logo androidbinary.String `xml:"http://schemas.android.com/apk/res/android logo,attr"` 69 | ManageSpaceActivity androidbinary.String `xml:"http://schemas.android.com/apk/res/android manageSpaceActivity,attr"` 70 | Name androidbinary.String `xml:"http://schemas.android.com/apk/res/android name,attr"` 71 | Permission androidbinary.String `xml:"http://schemas.android.com/apk/res/android permission,attr"` 72 | Persistent androidbinary.Bool `xml:"http://schemas.android.com/apk/res/android persistent,attr"` 73 | Process androidbinary.String `xml:"http://schemas.android.com/apk/res/android process,attr"` 74 | RestoreAnyVersion androidbinary.Bool `xml:"http://schemas.android.com/apk/res/android restoreAnyVersion,attr"` 75 | RequiredAccountType androidbinary.String `xml:"http://schemas.android.com/apk/res/android requiredAccountType,attr"` 76 | RestrictedAccountType androidbinary.String `xml:"http://schemas.android.com/apk/res/android restrictedAccountType,attr"` 77 | SupportsRtl androidbinary.Bool `xml:"http://schemas.android.com/apk/res/android supportsRtl,attr"` 78 | TaskAffinity androidbinary.String `xml:"http://schemas.android.com/apk/res/android taskAffinity,attr"` 79 | TestOnly androidbinary.Bool `xml:"http://schemas.android.com/apk/res/android testOnly,attr"` 80 | Theme androidbinary.String `xml:"http://schemas.android.com/apk/res/android theme,attr"` 81 | UIOptions androidbinary.String `xml:"http://schemas.android.com/apk/res/android uiOptions,attr"` 82 | VMSafeMode androidbinary.Bool `xml:"http://schemas.android.com/apk/res/android vmSafeMode,attr"` 83 | Activities []AppActivity `xml:"activity"` 84 | ActivityAliases []AppActivityAlias `xml:"activity-alias"` 85 | MetaData []MetaData `xml:"meta-data"` 86 | } 87 | 88 | // UsesSDK is target SDK version. 89 | type UsesSDK struct { 90 | Min androidbinary.Int32 `xml:"http://schemas.android.com/apk/res/android minSdkVersion,attr"` 91 | Target androidbinary.Int32 `xml:"http://schemas.android.com/apk/res/android targetSdkVersion,attr"` 92 | Max androidbinary.Int32 `xml:"http://schemas.android.com/apk/res/android maxSdkVersion,attr"` 93 | } 94 | 95 | // UsesPermission is user grant the system permission. 96 | type UsesPermission struct { 97 | Name androidbinary.String `xml:"http://schemas.android.com/apk/res/android name,attr"` 98 | Max androidbinary.Int32 `xml:"http://schemas.android.com/apk/res/android maxSdkVersion,attr"` 99 | } 100 | 101 | // Manifest is a manifest of an APK. 102 | type Manifest struct { 103 | Package androidbinary.String `xml:"package,attr"` 104 | CompileSDKVersion androidbinary.Int32 `xml:"http://schemas.android.com/apk/res/android compileSdkVersion,attr"` 105 | CompileSDKVersionCodename androidbinary.String `xml:"http://schemas.android.com/apk/res/android compileSdkVersionCodename,attr"` 106 | VersionCode androidbinary.Int32 `xml:"http://schemas.android.com/apk/res/android versionCode,attr"` 107 | VersionName androidbinary.String `xml:"http://schemas.android.com/apk/res/android versionName,attr"` 108 | App Application `xml:"application"` 109 | Instrument Instrumentation `xml:"instrumentation"` 110 | SDK UsesSDK `xml:"uses-sdk"` 111 | UsesPermissions []UsesPermission `xml:"uses-permission"` 112 | } 113 | -------------------------------------------------------------------------------- /apk/errors.go: -------------------------------------------------------------------------------- 1 | //go:build go1.13 2 | // +build go1.13 3 | 4 | package apk 5 | 6 | import ( 7 | "errors" 8 | "fmt" 9 | ) 10 | 11 | var newError = errors.New 12 | var errorf = fmt.Errorf 13 | -------------------------------------------------------------------------------- /apk/testdata/helloworld.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shogo82148/androidbinary/565dc46e2be54110ebcdb9c453f49cd4d98cc96d/apk/testdata/helloworld.apk -------------------------------------------------------------------------------- /common.go: -------------------------------------------------------------------------------- 1 | package androidbinary 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "io" 7 | "unicode/utf16" 8 | ) 9 | 10 | // ChunkType is a type of a resource chunk. 11 | type ChunkType uint16 12 | 13 | // Chunk types. 14 | const ( 15 | ResNullChunkType ChunkType = 0x0000 16 | ResStringPoolChunkType ChunkType = 0x0001 17 | ResTableChunkType ChunkType = 0x0002 18 | ResXMLChunkType ChunkType = 0x0003 19 | 20 | // Chunk types in RES_XML_TYPE 21 | ResXMLFirstChunkType ChunkType = 0x0100 22 | ResXMLStartNamespaceType ChunkType = 0x0100 23 | ResXMLEndNamespaceType ChunkType = 0x0101 24 | ResXMLStartElementType ChunkType = 0x0102 25 | ResXMLEndElementType ChunkType = 0x0103 26 | ResXMLCDataType ChunkType = 0x0104 27 | ResXMLLastChunkType ChunkType = 0x017f 28 | 29 | // This contains a uint32_t array mapping strings in the string 30 | // pool back to resource identifiers. It is optional. 31 | ResXMLResourceMapType ChunkType = 0x0180 32 | 33 | // Chunk types in RES_TABLE_TYPE 34 | ResTablePackageType ChunkType = 0x0200 35 | ResTableTypeType ChunkType = 0x0201 36 | ResTableTypeSpecType ChunkType = 0x0202 37 | ) 38 | 39 | // ResChunkHeader is a header of a resource chunk. 40 | type ResChunkHeader struct { 41 | Type ChunkType 42 | HeaderSize uint16 43 | Size uint32 44 | } 45 | 46 | // Flags are flags for string pool header. 47 | type Flags uint32 48 | 49 | // the values of Flags. 50 | const ( 51 | SortedFlag Flags = 1 << 0 52 | UTF8Flag Flags = 1 << 8 53 | ) 54 | 55 | // ResStringPoolHeader is a chunk header of string pool. 56 | type ResStringPoolHeader struct { 57 | Header ResChunkHeader 58 | StringCount uint32 59 | StyleCount uint32 60 | Flags Flags 61 | StringStart uint32 62 | StylesStart uint32 63 | } 64 | 65 | // ResStringPoolSpan is a span of style information associated with 66 | // a string in the pool. 67 | type ResStringPoolSpan struct { 68 | FirstChar, LastChar uint32 69 | } 70 | 71 | // ResStringPool is a string pool resource. 72 | type ResStringPool struct { 73 | Header ResStringPoolHeader 74 | Strings []string 75 | Styles []ResStringPoolSpan 76 | } 77 | 78 | // NilResStringPoolRef is nil reference for string pool. 79 | const NilResStringPoolRef = ResStringPoolRef(0xFFFFFFFF) 80 | 81 | // ResStringPoolRef is a type representing a reference to a string. 82 | type ResStringPoolRef uint32 83 | 84 | // DataType is a type of the data value. 85 | type DataType uint8 86 | 87 | // The constants for DataType 88 | const ( 89 | TypeNull DataType = 0x00 90 | TypeReference DataType = 0x01 91 | TypeAttribute DataType = 0x02 92 | TypeString DataType = 0x03 93 | TypeFloat DataType = 0x04 94 | TypeDemention DataType = 0x05 95 | TypeFraction DataType = 0x06 96 | TypeFirstInt DataType = 0x10 97 | TypeIntDec DataType = 0x10 98 | TypeIntHex DataType = 0x11 99 | TypeIntBoolean DataType = 0x12 100 | TypeFirstColorInt DataType = 0x1c 101 | TypeIntColorARGB8 DataType = 0x1c 102 | TypeIntColorRGB8 DataType = 0x1d 103 | TypeIntColorARGB4 DataType = 0x1e 104 | TypeIntColorRGB4 DataType = 0x1f 105 | TypeLastColorInt DataType = 0x1f 106 | TypeLastInt DataType = 0x1f 107 | ) 108 | 109 | // ResValue is a representation of a value in a resource 110 | type ResValue struct { 111 | Size uint16 112 | Res0 uint8 113 | DataType DataType 114 | Data uint32 115 | } 116 | 117 | // GetString returns a string referenced by ref. 118 | // It panics if the pool doesn't contain ref. 119 | func (pool *ResStringPool) GetString(ref ResStringPoolRef) string { 120 | return pool.Strings[int(ref)] 121 | } 122 | 123 | // HasString returns whether the pool contains ref. 124 | func (pool *ResStringPool) HasString(ref ResStringPoolRef) bool { 125 | if pool == nil { 126 | return false 127 | } 128 | return int(ref) >= 0 && int(ref) < len(pool.Strings) 129 | } 130 | 131 | func readStringPool(sr *io.SectionReader) (*ResStringPool, error) { 132 | sp := new(ResStringPool) 133 | if err := binary.Read(sr, binary.LittleEndian, &sp.Header); err != nil { 134 | return nil, err 135 | } 136 | 137 | stringStarts := make([]uint32, sp.Header.StringCount) 138 | if err := binary.Read(sr, binary.LittleEndian, stringStarts); err != nil { 139 | return nil, err 140 | } 141 | 142 | styleStarts := make([]uint32, sp.Header.StyleCount) 143 | if err := binary.Read(sr, binary.LittleEndian, styleStarts); err != nil { 144 | return nil, err 145 | } 146 | 147 | sp.Strings = make([]string, sp.Header.StringCount) 148 | for i, start := range stringStarts { 149 | var str string 150 | var err error 151 | if _, err := sr.Seek(int64(sp.Header.StringStart+start), io.SeekStart); err != nil { 152 | return nil, err 153 | } 154 | if (sp.Header.Flags & UTF8Flag) == 0 { 155 | str, err = readUTF16(sr) 156 | } else { 157 | str, err = readUTF8(sr) 158 | } 159 | if err != nil { 160 | return nil, err 161 | } 162 | sp.Strings[i] = str 163 | } 164 | 165 | sp.Styles = make([]ResStringPoolSpan, sp.Header.StyleCount) 166 | for i, start := range styleStarts { 167 | if _, err := sr.Seek(int64(sp.Header.StylesStart+start), io.SeekStart); err != nil { 168 | return nil, err 169 | } 170 | if err := binary.Read(sr, binary.LittleEndian, &sp.Styles[i]); err != nil { 171 | return nil, err 172 | } 173 | } 174 | 175 | return sp, nil 176 | } 177 | 178 | func readUTF16(sr *io.SectionReader) (string, error) { 179 | // read length of string 180 | size, err := readUTF16length(sr) 181 | if err != nil { 182 | return "", err 183 | } 184 | 185 | // read string value 186 | buf := make([]uint16, size) 187 | if err := binary.Read(sr, binary.LittleEndian, buf); err != nil { 188 | return "", err 189 | } 190 | return string(utf16.Decode(buf)), nil 191 | } 192 | 193 | func readUTF16length(sr *io.SectionReader) (int, error) { 194 | var size int 195 | var first, second uint16 196 | if err := binary.Read(sr, binary.LittleEndian, &first); err != nil { 197 | return 0, err 198 | } 199 | if (first & 0x8000) != 0 { 200 | if err := binary.Read(sr, binary.LittleEndian, &second); err != nil { 201 | return 0, err 202 | } 203 | size = (int(first&0x7FFF) << 16) + int(second) 204 | } else { 205 | size = int(first) 206 | } 207 | return size, nil 208 | } 209 | 210 | func readUTF8(sr *io.SectionReader) (string, error) { 211 | // skip utf16 length 212 | _, err := readUTF8length(sr) 213 | if err != nil { 214 | return "", err 215 | } 216 | 217 | // read lenth of string 218 | size, err := readUTF8length(sr) 219 | if err != nil { 220 | return "", err 221 | } 222 | 223 | buf := make([]uint8, size) 224 | if err := binary.Read(sr, binary.LittleEndian, buf); err != nil { 225 | return "", err 226 | } 227 | return string(buf), nil 228 | } 229 | 230 | func readUTF8length(sr *io.SectionReader) (int, error) { 231 | var size int 232 | var first, second uint8 233 | if err := binary.Read(sr, binary.LittleEndian, &first); err != nil { 234 | return 0, err 235 | } 236 | if (first & 0x80) != 0 { 237 | if err := binary.Read(sr, binary.LittleEndian, &second); err != nil { 238 | return 0, err 239 | } 240 | size = (int(first&0x7F) << 8) + int(second) 241 | } else { 242 | size = int(first) 243 | } 244 | return size, nil 245 | } 246 | 247 | func newZeroFilledReader(r io.Reader, actual int64, expected int64) (io.Reader, error) { 248 | if actual >= expected { 249 | // no need to fill 250 | return r, nil 251 | } 252 | 253 | // read `actual' bytes from r, and 254 | buf := new(bytes.Buffer) 255 | if _, err := io.CopyN(buf, r, actual); err != nil { 256 | return nil, err 257 | } 258 | 259 | // fill zero until `expected' bytes 260 | for i := actual; i < expected; i++ { 261 | if err := buf.WriteByte(0x00); err != nil { 262 | return nil, err 263 | } 264 | } 265 | 266 | return buf, nil 267 | } 268 | -------------------------------------------------------------------------------- /common_test.go: -------------------------------------------------------------------------------- 1 | package androidbinary 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | var readStringPoolTests = []struct { 11 | input []uint8 12 | strings []string 13 | styles []ResStringPoolSpan 14 | }{ 15 | { 16 | []uint8{ 17 | 0x01, 0x00, // Type = RES_STRING_POOL_TYPE 18 | 0x1C, 0x00, // HeaderSize = 28 bytes 19 | 0x3C, 0x00, 0x00, 0x00, // Size = 60 20 | 0x02, 0x00, 0x00, 0x00, // StringCount = 2 21 | 0x02, 0x00, 0x00, 0x00, // StyleScount = 2 22 | 0x00, 0x00, 0x00, 0x00, // Flags = 0x00 23 | 0x2C, 0x00, 0x00, 0x00, // StringStart = 44 24 | 0x34, 0x00, 0x00, 0x00, // StylesStart = 52 25 | 26 | // StringIndexes 27 | 0x00, 0x00, 0x00, 0x00, 28 | 0x04, 0x00, 0x00, 0x00, 29 | 30 | // StyleIndexes 31 | 0x00, 0x00, 0x00, 0x00, 32 | 0x08, 0x00, 0x00, 0x00, 33 | 34 | // Strings 35 | 0x01, 0x00, 0x61, 0x00, 36 | 0x01, 0x00, 0x42, 0x30, 37 | 38 | // Styles 39 | 0x01, 0x00, 0x00, 0x00, 40 | 0x02, 0x00, 0x00, 0x00, 41 | 0x03, 0x00, 0x00, 0x00, 42 | 0x04, 0x00, 0x00, 0x00, 43 | }, 44 | []string{"a", "\u3042"}, 45 | []ResStringPoolSpan{{1, 2}, {3, 4}}, 46 | }, 47 | } 48 | 49 | func TestReadStringPool(t *testing.T) { 50 | for _, tt := range readStringPoolTests { 51 | buf := bytes.NewReader(tt.input) 52 | sr := io.NewSectionReader(buf, 0, int64(len(tt.input))) 53 | actual, err := readStringPool(sr) 54 | if err != nil { 55 | t.Errorf("got %v want no error", err) 56 | } 57 | if !reflect.DeepEqual(actual.Strings, tt.strings) { 58 | t.Errorf("got %v want %v", actual.Strings, tt.strings) 59 | } 60 | if !reflect.DeepEqual(actual.Styles, tt.styles) { 61 | t.Errorf("got %v want %v", actual.Styles, tt.styles) 62 | } 63 | } 64 | } 65 | 66 | var readUTF16Tests = []struct { 67 | input []uint8 68 | output string 69 | }{ 70 | { 71 | []uint8{0x00, 0x00, 0x61, 0x00}, 72 | "", 73 | }, 74 | { 75 | []uint8{0x01, 0x00, 0x61, 0x00}, 76 | "a", 77 | }, 78 | { 79 | []uint8{0x01, 0x00, 0x42, 0x30}, 80 | "\u3042", 81 | }, 82 | { 83 | []uint8{0x00, 0x80, 0x01, 0x00, 0x61, 0x00}, 84 | "a", 85 | }, 86 | } 87 | 88 | func TestReadUTF16(t *testing.T) { 89 | for _, tt := range readUTF16Tests { 90 | buf := bytes.NewReader(tt.input) 91 | sr := io.NewSectionReader(buf, 0, int64(len(tt.input))) 92 | actual, err := readUTF16(sr) 93 | if err != nil { 94 | t.Errorf("got %v want no error", err) 95 | } 96 | if actual != tt.output { 97 | t.Errorf("got %v want %v", actual, tt.output) 98 | } 99 | } 100 | } 101 | 102 | var readUTF8Tests = []struct { 103 | input []uint8 104 | output string 105 | }{ 106 | { 107 | []uint8{0x00, 0x00, 0x61}, 108 | "", 109 | }, 110 | { 111 | []uint8{0x01, 0x01, 0x61}, 112 | "a", 113 | }, 114 | { 115 | []uint8{0x01, 0x03, 0xE3, 0x81, 0x82}, 116 | "\u3042", 117 | }, 118 | { 119 | []uint8{0x80, 0x01, 0x80, 0x01, 0x61}, 120 | "a", 121 | }, 122 | } 123 | 124 | func TestReadUTF8(t *testing.T) { 125 | for _, tt := range readUTF8Tests { 126 | buf := bytes.NewReader(tt.input) 127 | sr := io.NewSectionReader(buf, 0, int64(len(tt.input))) 128 | actual, err := readUTF8(sr) 129 | if err != nil { 130 | t.Errorf("got %v want no error", err) 131 | } 132 | if actual != tt.output { 133 | t.Errorf("got %v want %v", actual, tt.output) 134 | } 135 | } 136 | } 137 | 138 | var newZeroFilledReaderTests = []struct { 139 | input []uint8 140 | actual int64 141 | expected int64 142 | output []uint8 143 | }{ 144 | { 145 | input: []uint8{0x01, 0x23, 0x45, 0x67}, 146 | actual: 4, 147 | expected: 4, 148 | output: []uint8{0x01, 0x23, 0x45, 0x67}, 149 | }, 150 | { 151 | input: []uint8{0x01, 0x23, 0x45, 0x67}, 152 | actual: 4, 153 | expected: 8, 154 | output: []uint8{0x01, 0x23, 0x45, 0x67, 0x00, 0x00, 0x00, 0x00}, 155 | }, 156 | } 157 | 158 | func TestNewZeroFilledReader(t *testing.T) { 159 | for _, tt := range newZeroFilledReaderTests { 160 | buf := bytes.NewReader(tt.input) 161 | r, err := newZeroFilledReader(buf, tt.actual, tt.expected) 162 | if err != nil { 163 | t.Errorf("got %v want no error", err) 164 | } 165 | actualBytes := make([]uint8, tt.expected) 166 | r.Read(actualBytes) 167 | for i, a := range tt.output { 168 | if actualBytes[i] != a { 169 | t.Errorf("got %v(position %d) wants %v", actualBytes[i], i, a) 170 | } 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package androidbinary_test 2 | 3 | import ( 4 | "encoding/xml" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/shogo82148/androidbinary" 9 | "github.com/shogo82148/androidbinary/apk" 10 | ) 11 | 12 | func ExampleNewXMLFile() { 13 | f, _ := os.Open("testdata/AndroidManifest.xml") 14 | xmlFile, err := androidbinary.NewXMLFile(f) 15 | if err != nil { 16 | panic(err) 17 | } 18 | 19 | var v apk.Manifest 20 | dec := xml.NewDecoder(xmlFile.Reader()) 21 | dec.Decode(&v) 22 | 23 | enc := xml.NewEncoder(os.Stdout) 24 | enc.Indent("", "\t") 25 | enc.Encode(v) 26 | 27 | // Output: 28 | // 29 | // 30 | // 31 | // 32 | // 33 | // 34 | // 35 | // 36 | // 37 | // 38 | // 39 | // 40 | // 41 | // 42 | // 43 | // 44 | // 45 | // 46 | // 47 | // 48 | // 49 | } 50 | 51 | func ExampleNewTableFile() { 52 | f, err := os.Open("testdata/resources.arsc") 53 | if err != nil { 54 | panic(err) 55 | } 56 | tableFile, err := androidbinary.NewTableFile(f) 57 | if err != nil { 58 | panic(err) 59 | } 60 | 61 | val, err := tableFile.GetResource(0x7f040000, &androidbinary.ResTableConfig{}) 62 | if err != nil { 63 | panic(err) 64 | } 65 | fmt.Println(val) 66 | // Output: 67 | // FireworksMeasure 68 | } 69 | -------------------------------------------------------------------------------- /fuzz_test.go: -------------------------------------------------------------------------------- 1 | //go:build go1.18 2 | // +build go1.18 3 | 4 | package androidbinary 5 | 6 | import ( 7 | "bytes" 8 | "os" 9 | "testing" 10 | ) 11 | 12 | func FuzzNewXMLFile(f *testing.F) { 13 | data, err := os.ReadFile("testdata/AndroidManifest.xml") 14 | if err != nil { 15 | f.Fatal(err) 16 | } 17 | f.Add(data) 18 | 19 | f.Fuzz(func(t *testing.T, data []byte) { 20 | _, err := NewXMLFile(bytes.NewReader(data)) 21 | if err != nil { 22 | t.Skip(err) 23 | } 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/shogo82148/androidbinary 2 | 3 | go 1.17 4 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shogo82148/androidbinary/565dc46e2be54110ebcdb9c453f49cd4d98cc96d/go.sum -------------------------------------------------------------------------------- /table.go: -------------------------------------------------------------------------------- 1 | package androidbinary 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "io" 7 | "strconv" 8 | "strings" 9 | "unsafe" 10 | ) 11 | 12 | // ResID is ID for resources. 13 | type ResID uint32 14 | 15 | // TableFile is a resource table file. 16 | type TableFile struct { 17 | stringPool *ResStringPool 18 | tablePackages map[uint32]*TablePackage 19 | } 20 | 21 | // ResTableHeader is a header of TableFile. 22 | type ResTableHeader struct { 23 | Header ResChunkHeader 24 | PackageCount uint32 25 | } 26 | 27 | // ResTablePackage is a header of table packages. 28 | type ResTablePackage struct { 29 | Header ResChunkHeader 30 | ID uint32 31 | Name [128]uint16 32 | TypeStrings uint32 33 | LastPublicType uint32 34 | KeyStrings uint32 35 | LastPublicKey uint32 36 | } 37 | 38 | // TablePackage is a table package. 39 | type TablePackage struct { 40 | Header ResTablePackage 41 | TypeStrings *ResStringPool 42 | KeyStrings *ResStringPool 43 | TableTypes []*TableType 44 | } 45 | 46 | // ResTableType is a type of a table. 47 | type ResTableType struct { 48 | Header ResChunkHeader 49 | ID uint8 50 | Res0 uint8 51 | Res1 uint16 52 | EntryCount uint32 53 | EntriesStart uint32 54 | Config ResTableConfig 55 | } 56 | 57 | // ScreenLayout describes screen layout. 58 | type ScreenLayout uint8 59 | 60 | // ScreenLayout bits 61 | const ( 62 | MaskScreenSize ScreenLayout = 0x0f 63 | ScreenSizeAny ScreenLayout = 0x01 64 | ScreenSizeSmall ScreenLayout = 0x02 65 | ScreenSizeNormal ScreenLayout = 0x03 66 | ScreenSizeLarge ScreenLayout = 0x04 67 | ScreenSizeXLarge ScreenLayout = 0x05 68 | 69 | MaskScreenLong ScreenLayout = 0x30 70 | ShiftScreenLong = 4 71 | ScreenLongAny ScreenLayout = 0x00 72 | ScreenLongNo ScreenLayout = 0x10 73 | ScreenLongYes ScreenLayout = 0x20 74 | 75 | MaskLayoutDir ScreenLayout = 0xC0 76 | ShiftLayoutDir = 6 77 | LayoutDirAny ScreenLayout = 0x00 78 | LayoutDirLTR ScreenLayout = 0x40 79 | LayoutDirRTL ScreenLayout = 0x80 80 | ) 81 | 82 | // UIMode describes UI mode. 83 | type UIMode uint8 84 | 85 | // UIMode bits 86 | const ( 87 | MaskUIModeType UIMode = 0x0f 88 | UIModeTypeAny UIMode = 0x01 89 | UIModeTypeNormal UIMode = 0x02 90 | UIModeTypeDesk UIMode = 0x03 91 | UIModeTypeCar UIMode = 0x04 92 | 93 | MaskUIModeNight UIMode = 0x30 94 | ShiftUIModeNight = 4 95 | UIModeNightAny UIMode = 0x00 96 | UIModeNightNo UIMode = 0x10 97 | UIModeNightYes UIMode = 0x20 98 | ) 99 | 100 | // InputFlags are input flags. 101 | type InputFlags uint8 102 | 103 | // input flags 104 | const ( 105 | MaskKeysHidden InputFlags = 0x03 106 | KeysHiddenAny InputFlags = 0x00 107 | KeysHiddenNo InputFlags = 0x01 108 | KeysHiddenYes InputFlags = 0x02 109 | KeysHiddenSoft InputFlags = 0x03 110 | 111 | MaskNavHidden InputFlags = 0x0c 112 | NavHiddenAny InputFlags = 0x00 113 | NavHiddenNo InputFlags = 0x04 114 | NavHiddenYes InputFlags = 0x08 115 | ) 116 | 117 | // ResTableConfig is a configuration of a table. 118 | type ResTableConfig struct { 119 | Size uint32 120 | // imsi 121 | Mcc uint16 122 | Mnc uint16 123 | 124 | // locale 125 | Language [2]uint8 126 | Country [2]uint8 127 | 128 | // screen type 129 | Orientation uint8 130 | Touchscreen uint8 131 | Density uint16 132 | 133 | // inout 134 | Keyboard uint8 135 | Navigation uint8 136 | InputFlags InputFlags 137 | InputPad0 uint8 138 | 139 | // screen size 140 | ScreenWidth uint16 141 | ScreenHeight uint16 142 | 143 | // version 144 | SDKVersion uint16 145 | MinorVersion uint16 146 | 147 | // screen config 148 | ScreenLayout ScreenLayout 149 | UIMode UIMode 150 | SmallestScreenWidthDp uint16 151 | 152 | // screen size dp 153 | ScreenWidthDp uint16 154 | ScreenHeightDp uint16 155 | } 156 | 157 | // TableType is a collection of resource entries for a particular resource data type. 158 | type TableType struct { 159 | Header *ResTableType 160 | Entries []TableEntry 161 | } 162 | 163 | // ResTableEntry is the beginning of information about an entry in the resource table. 164 | type ResTableEntry struct { 165 | Size uint16 166 | Flags uint16 167 | Key ResStringPoolRef 168 | } 169 | 170 | // TableEntry is a entry in a resource table. 171 | type TableEntry struct { 172 | Key *ResTableEntry 173 | Value *ResValue 174 | Flags uint32 175 | } 176 | 177 | // ResTableTypeSpec is specification of the resources defined by a particular type. 178 | type ResTableTypeSpec struct { 179 | Header ResChunkHeader 180 | ID uint8 181 | Res0 uint8 182 | Res1 uint16 183 | EntryCount uint32 184 | } 185 | 186 | // IsResID returns whether s is ResId. 187 | func IsResID(s string) bool { 188 | return strings.HasPrefix(s, "@0x") 189 | } 190 | 191 | // ParseResID parses ResId. 192 | func ParseResID(s string) (ResID, error) { 193 | if !IsResID(s) { 194 | return 0, fmt.Errorf("androidbinary: %s is not ResID", s) 195 | } 196 | id, err := strconv.ParseUint(s[3:], 16, 32) 197 | if err != nil { 198 | return 0, err 199 | } 200 | return ResID(id), nil 201 | } 202 | 203 | func (id ResID) String() string { 204 | return fmt.Sprintf("@0x%08X", uint32(id)) 205 | } 206 | 207 | // Package returns the package index of id. 208 | func (id ResID) Package() uint32 { 209 | return uint32(id) >> 24 210 | } 211 | 212 | // Type returns the type index of id. 213 | func (id ResID) Type() int { 214 | return (int(id) >> 16) & 0xFF 215 | } 216 | 217 | // Entry returns the entry index of id. 218 | func (id ResID) Entry() int { 219 | return int(id) & 0xFFFF 220 | } 221 | 222 | // NewTableFile returns new TableFile. 223 | func NewTableFile(r io.ReaderAt) (*TableFile, error) { 224 | f := new(TableFile) 225 | sr := io.NewSectionReader(r, 0, 1<<63-1) 226 | 227 | header := new(ResTableHeader) 228 | binary.Read(sr, binary.LittleEndian, header) 229 | f.tablePackages = make(map[uint32]*TablePackage) 230 | 231 | offset := int64(header.Header.HeaderSize) 232 | for offset < int64(header.Header.Size) { 233 | chunkHeader, err := f.readChunk(sr, offset) 234 | if err != nil { 235 | return nil, err 236 | } 237 | offset += int64(chunkHeader.Size) 238 | } 239 | return f, nil 240 | } 241 | 242 | func (f *TableFile) findPackage(id uint32) *TablePackage { 243 | if f == nil { 244 | return nil 245 | } 246 | return f.tablePackages[id] 247 | } 248 | 249 | func (p *TablePackage) findEntry(typeIndex, entryIndex int, config *ResTableConfig) TableEntry { 250 | var best *TableType 251 | for _, t := range p.TableTypes { 252 | switch { 253 | case int(t.Header.ID) != typeIndex: 254 | // nothing to do 255 | case !t.Header.Config.Match(config): 256 | // nothing to do 257 | case entryIndex >= len(t.Entries): 258 | // nothing to do 259 | case t.Entries[entryIndex].Value == nil: 260 | // nothing to do 261 | case best == nil || t.Header.Config.IsBetterThan(&best.Header.Config, config): 262 | best = t 263 | } 264 | } 265 | if best == nil || entryIndex >= len(best.Entries) { 266 | return TableEntry{} 267 | } 268 | return best.Entries[entryIndex] 269 | } 270 | 271 | // GetResource returns a resource referenced by id. 272 | func (f *TableFile) GetResource(id ResID, config *ResTableConfig) (interface{}, error) { 273 | p := f.findPackage(id.Package()) 274 | if p == nil { 275 | return nil, fmt.Errorf("androidbinary: package 0x%02X not found", id.Package()) 276 | } 277 | e := p.findEntry(id.Type(), id.Entry(), config) 278 | v := e.Value 279 | if v == nil { 280 | return nil, fmt.Errorf("androidbinary: entry 0x%04X not found", id.Entry()) 281 | } 282 | switch v.DataType { 283 | case TypeNull: 284 | return nil, nil 285 | case TypeString: 286 | return f.GetString(ResStringPoolRef(v.Data)), nil 287 | case TypeIntDec: 288 | return v.Data, nil 289 | case TypeIntHex: 290 | return v.Data, nil 291 | case TypeIntBoolean: 292 | return v.Data != 0, nil 293 | } 294 | return v.Data, nil 295 | } 296 | 297 | // GetString returns a string referenced by ref. 298 | func (f *TableFile) GetString(ref ResStringPoolRef) string { 299 | return f.stringPool.GetString(ref) 300 | } 301 | 302 | func (f *TableFile) readChunk(r io.ReaderAt, offset int64) (*ResChunkHeader, error) { 303 | sr := io.NewSectionReader(r, offset, 1<<63-1-offset) 304 | chunkHeader := &ResChunkHeader{} 305 | if _, err := sr.Seek(0, io.SeekStart); err != nil { 306 | return nil, err 307 | } 308 | if err := binary.Read(sr, binary.LittleEndian, chunkHeader); err != nil { 309 | return nil, err 310 | } 311 | 312 | var err error 313 | if _, err := sr.Seek(0, io.SeekStart); err != nil { 314 | return nil, err 315 | } 316 | switch chunkHeader.Type { 317 | case ResStringPoolChunkType: 318 | f.stringPool, err = readStringPool(sr) 319 | case ResTablePackageType: 320 | var tablePackage *TablePackage 321 | tablePackage, err = readTablePackage(sr) 322 | f.tablePackages[tablePackage.Header.ID] = tablePackage 323 | } 324 | if err != nil { 325 | return nil, err 326 | } 327 | 328 | return chunkHeader, nil 329 | } 330 | 331 | func readTablePackage(sr *io.SectionReader) (*TablePackage, error) { 332 | tablePackage := new(TablePackage) 333 | header := new(ResTablePackage) 334 | if err := binary.Read(sr, binary.LittleEndian, header); err != nil { 335 | return nil, err 336 | } 337 | tablePackage.Header = *header 338 | 339 | srTypes := io.NewSectionReader(sr, int64(header.TypeStrings), int64(header.Header.Size-header.TypeStrings)) 340 | if typeStrings, err := readStringPool(srTypes); err == nil { 341 | tablePackage.TypeStrings = typeStrings 342 | } else { 343 | return nil, err 344 | } 345 | 346 | srKeys := io.NewSectionReader(sr, int64(header.KeyStrings), int64(header.Header.Size-header.KeyStrings)) 347 | if keyStrings, err := readStringPool(srKeys); err == nil { 348 | tablePackage.KeyStrings = keyStrings 349 | } else { 350 | return nil, err 351 | } 352 | 353 | offset := int64(header.Header.HeaderSize) 354 | for offset < int64(header.Header.Size) { 355 | chunkHeader := &ResChunkHeader{} 356 | if _, err := sr.Seek(offset, io.SeekStart); err != nil { 357 | return nil, err 358 | } 359 | if err := binary.Read(sr, binary.LittleEndian, chunkHeader); err != nil { 360 | return nil, err 361 | } 362 | 363 | var err error 364 | chunkReader := io.NewSectionReader(sr, offset, int64(chunkHeader.Size)) 365 | if _, err := sr.Seek(offset, io.SeekStart); err != nil { 366 | return nil, err 367 | } 368 | switch chunkHeader.Type { 369 | case ResTableTypeType: 370 | var tableType *TableType 371 | tableType, err = readTableType(chunkHeader, chunkReader) 372 | tablePackage.TableTypes = append(tablePackage.TableTypes, tableType) 373 | case ResTableTypeSpecType: 374 | _, err = readTableTypeSpec(chunkReader) 375 | } 376 | if err != nil { 377 | return nil, err 378 | } 379 | offset += int64(chunkHeader.Size) 380 | } 381 | 382 | return tablePackage, nil 383 | } 384 | 385 | func readTableType(chunkHeader *ResChunkHeader, sr *io.SectionReader) (*TableType, error) { 386 | // TableType header may be omitted 387 | header := new(ResTableType) 388 | if _, err := sr.Seek(0, io.SeekStart); err != nil { 389 | return nil, err 390 | } 391 | buf, err := newZeroFilledReader(sr, int64(chunkHeader.HeaderSize), int64(unsafe.Sizeof(*header))) 392 | if err != nil { 393 | return nil, err 394 | } 395 | if err := binary.Read(buf, binary.LittleEndian, header); err != nil { 396 | return nil, err 397 | } 398 | 399 | entryIndexes := make([]uint32, header.EntryCount) 400 | if _, err := sr.Seek(int64(header.Header.HeaderSize), io.SeekStart); err != nil { 401 | return nil, err 402 | } 403 | if err := binary.Read(sr, binary.LittleEndian, entryIndexes); err != nil { 404 | return nil, err 405 | } 406 | 407 | entries := make([]TableEntry, header.EntryCount) 408 | for i, index := range entryIndexes { 409 | if index == 0xFFFFFFFF { 410 | continue 411 | } 412 | if _, err := sr.Seek(int64(header.EntriesStart+index), io.SeekStart); err != nil { 413 | return nil, err 414 | } 415 | var key ResTableEntry 416 | binary.Read(sr, binary.LittleEndian, &key) 417 | entries[i].Key = &key 418 | 419 | var val ResValue 420 | binary.Read(sr, binary.LittleEndian, &val) 421 | entries[i].Value = &val 422 | } 423 | return &TableType{ 424 | header, 425 | entries, 426 | }, nil 427 | } 428 | 429 | func readTableTypeSpec(sr *io.SectionReader) ([]uint32, error) { 430 | header := new(ResTableTypeSpec) 431 | if err := binary.Read(sr, binary.LittleEndian, header); err != nil { 432 | return nil, err 433 | } 434 | 435 | flags := make([]uint32, header.EntryCount) 436 | if _, err := sr.Seek(int64(header.Header.HeaderSize), io.SeekStart); err != nil { 437 | return nil, err 438 | } 439 | if err := binary.Read(sr, binary.LittleEndian, flags); err != nil { 440 | return nil, err 441 | } 442 | return flags, nil 443 | } 444 | 445 | // IsMoreSpecificThan returns true if c is more specific than o. 446 | func (c *ResTableConfig) IsMoreSpecificThan(o *ResTableConfig) bool { 447 | // nil ResTableConfig is never more specific than any ResTableConfig 448 | if c == nil { 449 | return false 450 | } 451 | if o == nil { 452 | return false 453 | } 454 | 455 | // imsi 456 | if c.Mcc != o.Mcc { 457 | if c.Mcc == 0 { 458 | return false 459 | } 460 | if o.Mnc == 0 { 461 | return true 462 | } 463 | } 464 | if c.Mnc != o.Mnc { 465 | if c.Mnc == 0 { 466 | return false 467 | } 468 | if o.Mnc == 0 { 469 | return true 470 | } 471 | } 472 | 473 | // locale 474 | if diff := c.IsLocaleMoreSpecificThan(o); diff < 0 { 475 | return false 476 | } else if diff > 0 { 477 | return true 478 | } 479 | 480 | // screen layout 481 | if c.ScreenLayout != 0 || o.ScreenLayout != 0 { 482 | if ((c.ScreenLayout ^ o.ScreenLayout) & MaskLayoutDir) != 0 { 483 | if (c.ScreenLayout & MaskLayoutDir) == 0 { 484 | return false 485 | } 486 | if (o.ScreenLayout & MaskLayoutDir) == 0 { 487 | return true 488 | } 489 | } 490 | } 491 | 492 | // smallest screen width dp 493 | if c.SmallestScreenWidthDp != 0 || o.SmallestScreenWidthDp != 0 { 494 | if c.SmallestScreenWidthDp != o.SmallestScreenWidthDp { 495 | if c.SmallestScreenWidthDp == 0 { 496 | return false 497 | } 498 | if o.SmallestScreenWidthDp == 0 { 499 | return true 500 | } 501 | } 502 | } 503 | 504 | // screen size dp 505 | if c.ScreenWidthDp != 0 || o.ScreenWidthDp != 0 || 506 | c.ScreenHeightDp != 0 || o.ScreenHeightDp != 0 { 507 | if c.ScreenWidthDp != o.ScreenWidthDp { 508 | if c.ScreenWidthDp == 0 { 509 | return false 510 | } 511 | if o.ScreenWidthDp == 0 { 512 | return true 513 | } 514 | } 515 | if c.ScreenHeightDp != o.ScreenHeightDp { 516 | if c.ScreenHeightDp == 0 { 517 | return false 518 | } 519 | if o.ScreenHeightDp == 0 { 520 | return true 521 | } 522 | } 523 | } 524 | 525 | // screen layout 526 | if c.ScreenLayout != 0 || o.ScreenLayout != 0 { 527 | if ((c.ScreenLayout ^ o.ScreenLayout) & MaskScreenSize) != 0 { 528 | if (c.ScreenLayout & MaskScreenSize) == 0 { 529 | return false 530 | } 531 | if (o.ScreenLayout & MaskScreenSize) == 0 { 532 | return true 533 | } 534 | } 535 | if ((c.ScreenLayout ^ o.ScreenLayout) & MaskScreenLong) != 0 { 536 | if (c.ScreenLayout & MaskScreenLong) == 0 { 537 | return false 538 | } 539 | if (o.ScreenLayout & MaskScreenLong) == 0 { 540 | return true 541 | } 542 | } 543 | } 544 | 545 | // orientation 546 | if c.Orientation != o.Orientation { 547 | if c.Orientation == 0 { 548 | return false 549 | } 550 | if o.Orientation == 0 { 551 | return true 552 | } 553 | } 554 | 555 | // uimode 556 | if c.UIMode != 0 || o.UIMode != 0 { 557 | diff := c.UIMode ^ o.UIMode 558 | if (diff & MaskUIModeType) != 0 { 559 | if (c.UIMode & MaskUIModeType) == 0 { 560 | return false 561 | } 562 | if (o.UIMode & MaskUIModeType) == 0 { 563 | return true 564 | } 565 | } 566 | if (diff & MaskUIModeNight) != 0 { 567 | if (c.UIMode & MaskUIModeNight) == 0 { 568 | return false 569 | } 570 | if (o.UIMode & MaskUIModeNight) == 0 { 571 | return true 572 | } 573 | } 574 | } 575 | 576 | // touchscreen 577 | if c.Touchscreen != o.Touchscreen { 578 | if c.Touchscreen == 0 { 579 | return false 580 | } 581 | if o.Touchscreen == 0 { 582 | return true 583 | } 584 | } 585 | 586 | // input 587 | if c.InputFlags != 0 || o.InputFlags != 0 { 588 | myKeysHidden := c.InputFlags & MaskKeysHidden 589 | oKeysHidden := o.InputFlags & MaskKeysHidden 590 | if (myKeysHidden ^ oKeysHidden) != 0 { 591 | if myKeysHidden == 0 { 592 | return false 593 | } 594 | if oKeysHidden == 0 { 595 | return true 596 | } 597 | } 598 | myNavHidden := c.InputFlags & MaskNavHidden 599 | oNavHidden := o.InputFlags & MaskNavHidden 600 | if (myNavHidden ^ oNavHidden) != 0 { 601 | if myNavHidden == 0 { 602 | return false 603 | } 604 | if oNavHidden == 0 { 605 | return true 606 | } 607 | } 608 | } 609 | 610 | if c.Keyboard != o.Keyboard { 611 | if c.Keyboard == 0 { 612 | return false 613 | } 614 | if o.Keyboard == 0 { 615 | return true 616 | } 617 | } 618 | 619 | if c.Navigation != o.Navigation { 620 | if c.Navigation == 0 { 621 | return false 622 | } 623 | if o.Navigation == 0 { 624 | return true 625 | } 626 | } 627 | 628 | // screen size 629 | if c.ScreenWidth != 0 || o.ScreenWidth != 0 || 630 | c.ScreenHeight != 0 || o.ScreenHeight != 0 { 631 | if c.ScreenWidth != o.ScreenWidth { 632 | if c.ScreenWidth == 0 { 633 | return false 634 | } 635 | if o.ScreenWidth == 0 { 636 | return true 637 | } 638 | } 639 | if c.ScreenHeight != o.ScreenHeight { 640 | if c.ScreenHeight == 0 { 641 | return false 642 | } 643 | if o.ScreenHeight == 0 { 644 | return true 645 | } 646 | } 647 | } 648 | 649 | //version 650 | if c.SDKVersion != o.SDKVersion { 651 | if c.SDKVersion == 0 { 652 | return false 653 | } 654 | if o.SDKVersion == 0 { 655 | return true 656 | } 657 | } 658 | if c.MinorVersion != o.MinorVersion { 659 | if c.MinorVersion == 0 { 660 | return false 661 | } 662 | if o.MinorVersion == 0 { 663 | return true 664 | } 665 | } 666 | 667 | return false 668 | } 669 | 670 | // IsBetterThan returns true if c is better than o for the r configuration. 671 | func (c *ResTableConfig) IsBetterThan(o *ResTableConfig, r *ResTableConfig) bool { 672 | if r == nil { 673 | return c.IsMoreSpecificThan(o) 674 | } 675 | 676 | // nil ResTableConfig is never better than any ResTableConfig 677 | if c == nil { 678 | return false 679 | } 680 | if o == nil { 681 | return false 682 | } 683 | 684 | // imsi 685 | if c.Mcc != 0 || c.Mnc != 0 || o.Mcc != 0 || o.Mnc != 0 { 686 | if c.Mcc != o.Mcc && r.Mcc != 0 { 687 | return c.Mcc != 0 688 | } 689 | if c.Mnc != o.Mnc && r.Mnc != 0 { 690 | return c.Mnc != 0 691 | } 692 | } 693 | 694 | // locale 695 | if c.IsLocaleBetterThan(o, r) { 696 | return true 697 | } 698 | 699 | // screen layout 700 | if c.ScreenLayout != 0 || o.ScreenLayout != 0 { 701 | myLayoutdir := c.ScreenLayout & MaskLayoutDir 702 | oLayoutdir := o.ScreenLayout & MaskLayoutDir 703 | if (myLayoutdir^oLayoutdir) != 0 && (r.ScreenLayout&MaskLayoutDir) != 0 { 704 | return myLayoutdir > oLayoutdir 705 | } 706 | } 707 | 708 | // smallest screen width dp 709 | if c.SmallestScreenWidthDp != 0 || o.SmallestScreenWidthDp != 0 { 710 | if c.SmallestScreenWidthDp != o.SmallestScreenWidthDp { 711 | return c.SmallestScreenWidthDp > o.SmallestScreenWidthDp 712 | } 713 | } 714 | 715 | // screen size dp 716 | if c.ScreenWidthDp != 0 || c.ScreenHeightDp != 0 || o.ScreenWidthDp != 0 || o.ScreenHeightDp != 0 { 717 | myDelta := 0 718 | otherDelta := 0 719 | if r.ScreenWidthDp != 0 { 720 | myDelta += int(r.ScreenWidthDp) - int(c.ScreenWidthDp) 721 | otherDelta += int(r.ScreenWidthDp) - int(o.ScreenWidthDp) 722 | } 723 | if r.ScreenHeightDp != 0 { 724 | myDelta += int(r.ScreenHeightDp) - int(c.ScreenHeightDp) 725 | otherDelta += int(r.ScreenHeightDp) - int(o.ScreenHeightDp) 726 | } 727 | if myDelta != otherDelta { 728 | return myDelta < otherDelta 729 | } 730 | } 731 | 732 | // screen layout 733 | if c.ScreenLayout != 0 || o.ScreenLayout != 0 { 734 | mySL := c.ScreenLayout & MaskScreenSize 735 | oSL := o.ScreenLayout & MaskScreenSize 736 | if (mySL^oSL) != 0 && (r.ScreenLayout&MaskScreenSize) != 0 { 737 | fixedMySL := mySL 738 | fixedOSL := oSL 739 | if (r.ScreenLayout & MaskScreenSize) >= ScreenSizeNormal { 740 | if fixedMySL == 0 { 741 | fixedMySL = ScreenSizeNormal 742 | } 743 | if fixedOSL == 0 { 744 | fixedOSL = ScreenSizeNormal 745 | } 746 | } 747 | if fixedMySL == fixedOSL { 748 | return mySL != 0 749 | } 750 | return fixedMySL > fixedOSL 751 | } 752 | 753 | if ((c.ScreenLayout^o.ScreenLayout)&MaskScreenLong) != 0 && 754 | (r.ScreenLayout&MaskScreenLong) != 0 { 755 | return (c.ScreenLayout & MaskScreenLong) != 0 756 | } 757 | } 758 | 759 | // orientation 760 | if c.Orientation != o.Orientation && r.Orientation != 0 { 761 | return c.Orientation != 0 762 | } 763 | 764 | // uimode 765 | if c.UIMode != 0 || o.UIMode != 0 { 766 | diff := c.UIMode ^ o.UIMode 767 | if (diff&MaskUIModeType) != 0 && (r.UIMode&MaskUIModeType) != 0 { 768 | return (c.UIMode & MaskUIModeType) != 0 769 | } 770 | if (diff&MaskUIModeNight) != 0 && (r.UIMode&MaskUIModeNight) != 0 { 771 | return (c.UIMode & MaskUIModeNight) != 0 772 | } 773 | } 774 | 775 | // screen type 776 | if c.Density != o.Density { 777 | h := int(c.Density) 778 | if h == 0 { 779 | h = 160 780 | } 781 | l := int(o.Density) 782 | if l == 0 { 783 | l = 160 784 | } 785 | blmBigger := true 786 | if l > h { 787 | h, l = l, h 788 | blmBigger = false 789 | } 790 | 791 | reqValue := int(r.Density) 792 | if reqValue == 0 { 793 | reqValue = 160 794 | } 795 | if reqValue >= h { 796 | return blmBigger 797 | } 798 | if l >= reqValue { 799 | return !blmBigger 800 | } 801 | if (2*l-reqValue)*h > reqValue*reqValue { 802 | return !blmBigger 803 | } 804 | return blmBigger 805 | } 806 | if c.Touchscreen != o.Touchscreen && r.Touchscreen != 0 { 807 | return c.Touchscreen != 0 808 | } 809 | 810 | // input 811 | if c.InputFlags != 0 || o.InputFlags != 0 { 812 | myKeysHidden := c.InputFlags & MaskKeysHidden 813 | oKeysHidden := o.InputFlags & MaskKeysHidden 814 | reqKeysHidden := r.InputFlags & MaskKeysHidden 815 | if myKeysHidden != oKeysHidden && reqKeysHidden != 0 { 816 | switch { 817 | case myKeysHidden == 0: 818 | return false 819 | case oKeysHidden == 0: 820 | return true 821 | case reqKeysHidden == myKeysHidden: 822 | return true 823 | case reqKeysHidden == oKeysHidden: 824 | return false 825 | } 826 | } 827 | myNavHidden := c.InputFlags & MaskNavHidden 828 | oNavHidden := o.InputFlags & MaskNavHidden 829 | reqNavHidden := r.InputFlags & MaskNavHidden 830 | if myNavHidden != oNavHidden && reqNavHidden != 0 { 831 | switch { 832 | case myNavHidden == 0: 833 | return false 834 | case oNavHidden == 0: 835 | return true 836 | } 837 | } 838 | } 839 | if c.Keyboard != o.Keyboard && r.Keyboard != 0 { 840 | return c.Keyboard != 0 841 | } 842 | if c.Navigation != o.Navigation && r.Navigation != 0 { 843 | return c.Navigation != 0 844 | } 845 | 846 | // screen size 847 | if c.ScreenWidth != 0 || c.ScreenHeight != 0 || o.ScreenWidth != 0 || o.ScreenHeight != 0 { 848 | myDelta := 0 849 | otherDelta := 0 850 | if r.ScreenWidth != 0 { 851 | myDelta += int(r.ScreenWidth) - int(c.ScreenWidth) 852 | otherDelta += int(r.ScreenWidth) - int(o.ScreenWidth) 853 | } 854 | if r.ScreenHeight != 0 { 855 | myDelta += int(r.ScreenHeight) - int(c.ScreenHeight) 856 | otherDelta += int(r.ScreenHeight) - int(o.ScreenHeight) 857 | } 858 | if myDelta != otherDelta { 859 | return myDelta < otherDelta 860 | } 861 | } 862 | 863 | // version 864 | if c.SDKVersion != 0 || o.MinorVersion != 0 { 865 | if c.SDKVersion != o.SDKVersion && r.SDKVersion != 0 { 866 | return c.SDKVersion > o.SDKVersion 867 | } 868 | if c.MinorVersion != o.MinorVersion && r.MinorVersion != 0 { 869 | return c.MinorVersion != 0 870 | } 871 | } 872 | 873 | return false 874 | } 875 | 876 | // IsLocaleMoreSpecificThan a positive integer if this config is more specific than o, 877 | // a negative integer if |o| is more specific 878 | // and 0 if they're equally specific. 879 | func (c *ResTableConfig) IsLocaleMoreSpecificThan(o *ResTableConfig) int { 880 | if (c.Language != [2]uint8{} || c.Country != [2]uint8{}) || (o.Language != [2]uint8{} || o.Country != [2]uint8{}) { 881 | if c.Language != o.Language { 882 | if c.Language == [2]uint8{} { 883 | return -1 884 | } 885 | if o.Language == [2]uint8{} { 886 | return 1 887 | } 888 | } 889 | 890 | if c.Country != o.Country { 891 | if c.Country == [2]uint8{} { 892 | return -1 893 | } 894 | if o.Country == [2]uint8{} { 895 | return 1 896 | } 897 | } 898 | } 899 | return 0 900 | } 901 | 902 | // IsLocaleBetterThan returns true if c is a better locale match than o for the r configuration. 903 | func (c *ResTableConfig) IsLocaleBetterThan(o *ResTableConfig, r *ResTableConfig) bool { 904 | if r.Language == [2]uint8{} && r.Country == [2]uint8{} { 905 | // The request doesn't have a locale, so no resource is better 906 | // than the other. 907 | return false 908 | } 909 | 910 | if c.Language == [2]uint8{} && c.Country == [2]uint8{} && o.Language == [2]uint8{} && o.Country == [2]uint8{} { 911 | // The locales parts of both resources are empty, so no one is better 912 | // than the other. 913 | return false 914 | } 915 | 916 | if c.Language != o.Language { 917 | // The languages of the two resources are not the same. 918 | 919 | // the US English resource have traditionally lived for most apps. 920 | if r.Language == [2]uint8{'e', 'n'} { 921 | if r.Country == [2]uint8{'U', 'S'} { 922 | if c.Language != [2]uint8{} { 923 | return c.Country == [2]uint8{} || c.Country == [2]uint8{'U', 'S'} 924 | } 925 | return !(c.Country == [2]uint8{} || c.Country == [2]uint8{'U', 'S'}) 926 | } 927 | } 928 | return c.Language != [2]uint8{} 929 | } 930 | 931 | if c.Country != o.Country { 932 | return c.Country != [2]uint8{} 933 | } 934 | 935 | return false 936 | } 937 | 938 | // Match returns true if c can be considered a match for the parameters in settings. 939 | func (c *ResTableConfig) Match(settings *ResTableConfig) bool { 940 | // nil ResTableConfig always matches. 941 | if settings == nil { 942 | return true 943 | } else if c == nil { 944 | return *settings == ResTableConfig{} 945 | } 946 | 947 | // match imsi 948 | if settings.Mcc == 0 { 949 | if c.Mcc != 0 { 950 | return false 951 | } 952 | } else { 953 | if c.Mcc != 0 && c.Mcc != settings.Mcc { 954 | return false 955 | } 956 | } 957 | if settings.Mnc == 0 { 958 | if c.Mnc != 0 { 959 | return false 960 | } 961 | } else { 962 | if c.Mnc != 0 && c.Mnc != settings.Mnc { 963 | return false 964 | } 965 | } 966 | 967 | // match locale 968 | if c.Language != [2]uint8{0, 0} { 969 | // Don't consider country and variants when deciding matches. 970 | // If two configs differ only in their country and variant, 971 | // they can be weeded out in the isMoreSpecificThan test. 972 | if c.Language != settings.Language { 973 | return false 974 | } 975 | 976 | if c.Country != [2]uint8{0, 0} { 977 | if c.Country != settings.Country { 978 | return false 979 | } 980 | } 981 | } 982 | 983 | // screen layout 984 | layoutDir := c.ScreenLayout & MaskLayoutDir 985 | setLayoutDir := settings.ScreenLayout & MaskLayoutDir 986 | if layoutDir != 0 && layoutDir != setLayoutDir { 987 | return false 988 | } 989 | 990 | screenSize := c.ScreenLayout & MaskScreenSize 991 | setScreenSize := settings.ScreenLayout & MaskScreenSize 992 | if screenSize != 0 && screenSize > setScreenSize { 993 | return false 994 | } 995 | 996 | screenLong := c.ScreenLayout & MaskScreenLong 997 | setScreenLong := settings.ScreenLayout & MaskScreenLong 998 | if screenLong != 0 && screenLong != setScreenLong { 999 | return false 1000 | } 1001 | 1002 | // ui mode 1003 | uiModeType := c.UIMode & MaskUIModeType 1004 | setUIModeType := settings.UIMode & MaskUIModeType 1005 | if uiModeType != 0 && uiModeType != setUIModeType { 1006 | return false 1007 | } 1008 | 1009 | uiModeNight := c.UIMode & MaskUIModeNight 1010 | setUIModeNight := settings.UIMode & MaskUIModeNight 1011 | if uiModeNight != 0 && uiModeNight != setUIModeNight { 1012 | return false 1013 | } 1014 | 1015 | // smallest screen width dp 1016 | if c.SmallestScreenWidthDp != 0 && 1017 | c.SmallestScreenWidthDp > settings.SmallestScreenWidthDp { 1018 | return false 1019 | } 1020 | 1021 | // screen size dp 1022 | if c.ScreenWidthDp != 0 && 1023 | c.ScreenWidthDp > settings.ScreenWidthDp { 1024 | return false 1025 | } 1026 | if c.ScreenHeightDp != 0 && 1027 | c.ScreenHeightDp > settings.ScreenHeightDp { 1028 | return false 1029 | } 1030 | 1031 | // screen type 1032 | if c.Orientation != 0 && c.Orientation != settings.Orientation { 1033 | return false 1034 | } 1035 | if c.Touchscreen != 0 && c.Touchscreen != settings.Touchscreen { 1036 | return false 1037 | } 1038 | 1039 | // input 1040 | if c.InputFlags != 0 { 1041 | myKeysHidden := c.InputFlags & MaskKeysHidden 1042 | oKeysHidden := settings.InputFlags & MaskKeysHidden 1043 | if myKeysHidden != 0 && myKeysHidden != oKeysHidden { 1044 | if myKeysHidden != KeysHiddenNo || oKeysHidden != KeysHiddenSoft { 1045 | return false 1046 | } 1047 | } 1048 | myNavHidden := c.InputFlags & MaskNavHidden 1049 | oNavHidden := settings.InputFlags & MaskNavHidden 1050 | if myNavHidden != 0 && myNavHidden != oNavHidden { 1051 | return false 1052 | } 1053 | } 1054 | if c.Keyboard != 0 && c.Keyboard != settings.Keyboard { 1055 | return false 1056 | } 1057 | if c.Navigation != 0 && c.Navigation != settings.Navigation { 1058 | return false 1059 | } 1060 | 1061 | // screen size 1062 | if c.ScreenWidth != 0 && 1063 | c.ScreenWidth > settings.ScreenWidth { 1064 | return false 1065 | } 1066 | if c.ScreenHeight != 0 && 1067 | c.ScreenHeight > settings.ScreenHeight { 1068 | return false 1069 | } 1070 | 1071 | // version 1072 | if settings.SDKVersion != 0 && c.SDKVersion != 0 && 1073 | c.SDKVersion > settings.SDKVersion { 1074 | return false 1075 | } 1076 | if settings.MinorVersion != 0 && c.MinorVersion != 0 && 1077 | c.MinorVersion != settings.MinorVersion { 1078 | return false 1079 | } 1080 | 1081 | return true 1082 | } 1083 | 1084 | // Locale returns the locale of the configuration. 1085 | func (c *ResTableConfig) Locale() string { 1086 | if c.Language[0] == 0 { 1087 | return "" 1088 | } 1089 | if c.Country[0] == 0 { 1090 | return fmt.Sprintf("%c%c", c.Language[0], c.Language[1]) 1091 | } 1092 | return fmt.Sprintf("%c%c-%c%c", c.Language[0], c.Language[1], c.Country[0], c.Country[1]) 1093 | } 1094 | -------------------------------------------------------------------------------- /table_test.go: -------------------------------------------------------------------------------- 1 | package androidbinary 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestIsResId(t *testing.T) { 9 | cases := []struct { 10 | input string 11 | want bool 12 | }{ 13 | {"@0x00", true}, 14 | {"foo", false}, 15 | } 16 | for _, c := range cases { 17 | if got := IsResID(c.input); got != c.want { 18 | t.Errorf("%s: want %v, got %v", c.input, got, c.want) 19 | } 20 | } 21 | } 22 | 23 | func TestParseResId(t *testing.T) { 24 | id, err := ParseResID("@0x12345678") 25 | if err != nil { 26 | t.Error(err) 27 | } 28 | if id != 0x12345678 { 29 | t.Errorf("want 0x12345678, got %X", id) 30 | } 31 | } 32 | 33 | func loadTestData() *TableFile { 34 | f, _ := os.Open("testdata/resources.arsc") 35 | tableFile, _ := NewTableFile(f) 36 | return tableFile 37 | } 38 | 39 | func TestFindPackage(t *testing.T) { 40 | tableFile := loadTestData() 41 | p := tableFile.findPackage(0x7F) 42 | if p == nil { 43 | t.Error("got nil want package(id: 0x7F)") 44 | t.Errorf("%v", tableFile.tablePackages) 45 | } 46 | } 47 | 48 | func TestGetResourceNil(t *testing.T) { 49 | tableFile := loadTestData() 50 | val, _ := tableFile.GetResource(ResID(0x7f040000), nil) 51 | if val != "花火距離計算" { 52 | t.Errorf(`got %v want "花火距離計算"`, val) 53 | } 54 | } 55 | 56 | func TestGetResourceDefault(t *testing.T) { 57 | tableFile := loadTestData() 58 | val, _ := tableFile.GetResource(ResID(0x7f040000), &ResTableConfig{}) 59 | if val != "FireworksMeasure" { 60 | t.Errorf(`got %v want "FireworksMeasure"`, val) 61 | } 62 | } 63 | 64 | func TestGetResourceJA(t *testing.T) { 65 | tableFile := loadTestData() 66 | config := &ResTableConfig{ 67 | Language: [2]uint8{'j', 'a'}, 68 | } 69 | val, _ := tableFile.GetResource(ResID(0x7f040000), config) 70 | if val != "花火距離計算" { 71 | t.Errorf(`got %v want "花火距離計算"`, val) 72 | } 73 | } 74 | 75 | func TestGetResourceEN(t *testing.T) { 76 | tableFile := loadTestData() 77 | config := &ResTableConfig{ 78 | Language: [2]uint8{'e', 'n'}, 79 | } 80 | val, _ := tableFile.GetResource(ResID(0x7f040000), config) 81 | if val != "FireworksMeasure" { 82 | t.Errorf(`got %v want "FireworksMeasure"`, val) 83 | } 84 | } 85 | 86 | var isMoreSpecificThanTests = []struct { 87 | me *ResTableConfig 88 | other *ResTableConfig 89 | expected bool 90 | }{ 91 | { 92 | me: nil, 93 | other: nil, 94 | expected: false, 95 | }, 96 | { 97 | me: nil, 98 | other: &ResTableConfig{}, 99 | expected: false, 100 | }, 101 | { 102 | me: &ResTableConfig{}, 103 | other: nil, 104 | expected: false, 105 | }, 106 | { 107 | me: &ResTableConfig{}, 108 | other: &ResTableConfig{}, 109 | expected: false, 110 | }, 111 | { 112 | me: &ResTableConfig{Mcc: 1}, 113 | other: &ResTableConfig{}, 114 | expected: true, 115 | }, 116 | { 117 | me: &ResTableConfig{Mcc: 1, Mnc: 1}, 118 | other: &ResTableConfig{Mcc: 1}, 119 | expected: true, 120 | }, 121 | { 122 | me: &ResTableConfig{ 123 | Language: [2]uint8{'j', 'a'}, 124 | }, 125 | other: &ResTableConfig{}, 126 | expected: true, 127 | }, 128 | { 129 | me: &ResTableConfig{ 130 | Language: [2]uint8{'j', 'a'}, 131 | Country: [2]uint8{'J', 'P'}, 132 | }, 133 | other: &ResTableConfig{ 134 | Language: [2]uint8{'j', 'a'}, 135 | }, 136 | expected: true, 137 | }, 138 | { 139 | me: &ResTableConfig{ScreenLayout: ScreenSizeNormal}, 140 | other: &ResTableConfig{}, 141 | expected: true, 142 | }, 143 | { 144 | me: &ResTableConfig{ScreenLayout: ScreenLongYes}, 145 | other: &ResTableConfig{}, 146 | expected: true, 147 | }, 148 | { 149 | me: &ResTableConfig{ScreenLayout: LayoutDirLTR}, 150 | other: &ResTableConfig{ScreenLayout: LayoutDirAny}, 151 | expected: true, 152 | }, 153 | { 154 | me: &ResTableConfig{SmallestScreenWidthDp: 72}, 155 | other: &ResTableConfig{}, 156 | expected: true, 157 | }, 158 | { 159 | me: &ResTableConfig{ScreenWidthDp: 100}, 160 | other: &ResTableConfig{}, 161 | expected: true, 162 | }, 163 | { 164 | me: &ResTableConfig{ScreenHeightDp: 100}, 165 | other: &ResTableConfig{}, 166 | expected: true, 167 | }, 168 | { 169 | me: &ResTableConfig{Orientation: 1}, 170 | other: &ResTableConfig{}, 171 | expected: true, 172 | }, 173 | { 174 | me: &ResTableConfig{UIMode: UIModeTypeAny}, 175 | other: &ResTableConfig{}, 176 | expected: true, 177 | }, 178 | { 179 | me: &ResTableConfig{UIMode: UIModeNightYes}, 180 | other: &ResTableConfig{UIMode: UIModeNightAny}, 181 | expected: true, 182 | }, 183 | { 184 | me: &ResTableConfig{Keyboard: 1}, 185 | other: &ResTableConfig{}, 186 | expected: true, 187 | }, 188 | { 189 | me: &ResTableConfig{Navigation: 1}, 190 | other: &ResTableConfig{}, 191 | expected: true, 192 | }, 193 | { 194 | me: &ResTableConfig{UIMode: UIModeTypeAny}, 195 | other: &ResTableConfig{}, 196 | expected: true, 197 | }, 198 | { 199 | me: &ResTableConfig{Touchscreen: 1}, 200 | other: &ResTableConfig{}, 201 | expected: true, 202 | }, 203 | { 204 | me: &ResTableConfig{ScreenWidth: 100}, 205 | other: &ResTableConfig{}, 206 | expected: true, 207 | }, 208 | { 209 | me: &ResTableConfig{ScreenHeight: 100}, 210 | other: &ResTableConfig{}, 211 | expected: true, 212 | }, 213 | { 214 | me: &ResTableConfig{SDKVersion: 1}, 215 | other: &ResTableConfig{}, 216 | expected: true, 217 | }, 218 | { 219 | me: &ResTableConfig{SDKVersion: 1, MinorVersion: 1}, 220 | other: &ResTableConfig{SDKVersion: 1}, 221 | expected: true, 222 | }, 223 | } 224 | 225 | func TestIsMoreSpecificThan(t *testing.T) { 226 | for _, tt := range isMoreSpecificThanTests { 227 | actual := tt.me.IsMoreSpecificThan(tt.other) 228 | if actual != tt.expected { 229 | if tt.expected { 230 | t.Errorf("%+v is more specific than %+v, but get false", tt.me, tt.other) 231 | } else { 232 | t.Errorf("%+v is not more specific than %+v, but get true", tt.me, tt.other) 233 | } 234 | } 235 | 236 | if tt.expected { 237 | // If 'me' is more specific than 'other', 'other' is not more specific than 'me' 238 | if tt.other.IsMoreSpecificThan(tt.me) { 239 | t.Errorf("%+v is not more specific than %+v, but get true", tt.other, tt.me) 240 | } 241 | } 242 | } 243 | } 244 | 245 | func TestIsBetterThan_request_is_nil(t *testing.T) { 246 | // a.IsBetterThan(b, nil) is same as a.IsMoreSpecificThan(b) 247 | for _, tt := range isMoreSpecificThanTests { 248 | actual := tt.me.IsBetterThan(tt.other, nil) 249 | if actual != tt.expected { 250 | if tt.expected { 251 | t.Errorf("%v is better than %v, but get false", tt.me, tt.other) 252 | } else { 253 | t.Errorf("%v is better than %v, but get true", tt.me, tt.other) 254 | } 255 | } 256 | 257 | if tt.expected { 258 | // If 'me' is more specific than 'other', 'other' is not more specific than 'me' 259 | if tt.other.IsBetterThan(tt.me, nil) { 260 | t.Errorf("%v is better than %v, but get true", tt.other, tt.me) 261 | } 262 | } 263 | } 264 | } 265 | 266 | var isBetterThanTests = []struct { 267 | me *ResTableConfig 268 | other *ResTableConfig 269 | require *ResTableConfig 270 | expected bool 271 | }{ 272 | { 273 | me: &ResTableConfig{}, 274 | other: &ResTableConfig{}, 275 | require: &ResTableConfig{}, 276 | expected: false, 277 | }, 278 | { 279 | me: &ResTableConfig{Mcc: 1}, 280 | other: &ResTableConfig{}, 281 | require: &ResTableConfig{}, 282 | expected: false, 283 | }, 284 | { 285 | me: &ResTableConfig{Mcc: 1}, 286 | other: &ResTableConfig{}, 287 | require: &ResTableConfig{Mcc: 1}, 288 | expected: true, 289 | }, 290 | { 291 | me: &ResTableConfig{Mcc: 1, Mnc: 1}, 292 | other: &ResTableConfig{Mcc: 1}, 293 | require: &ResTableConfig{}, 294 | expected: false, 295 | }, 296 | { 297 | me: &ResTableConfig{Language: [2]byte{'j', 'a'}}, 298 | other: &ResTableConfig{}, 299 | require: &ResTableConfig{}, 300 | expected: false, 301 | }, 302 | { 303 | me: &ResTableConfig{Language: [2]byte{'j', 'a'}}, 304 | other: &ResTableConfig{}, 305 | require: &ResTableConfig{Language: [2]byte{'j', 'a'}}, 306 | expected: true, 307 | }, 308 | { 309 | me: &ResTableConfig{ 310 | Language: [2]uint8{'j', 'a'}, 311 | Country: [2]uint8{'J', 'P'}, 312 | }, 313 | other: &ResTableConfig{ 314 | Language: [2]uint8{'j', 'a'}, 315 | }, 316 | require: &ResTableConfig{}, 317 | expected: false, 318 | }, 319 | { 320 | me: &ResTableConfig{ 321 | Language: [2]uint8{'j', 'a'}, 322 | Country: [2]uint8{'J', 'P'}, 323 | }, 324 | other: &ResTableConfig{ 325 | Language: [2]uint8{'j', 'a'}, 326 | }, 327 | require: &ResTableConfig{ 328 | Language: [2]uint8{'j', 'a'}, 329 | Country: [2]uint8{'J', 'P'}, 330 | }, 331 | expected: true, 332 | }, 333 | { 334 | me: &ResTableConfig{ScreenLayout: ScreenSizeNormal}, 335 | other: &ResTableConfig{}, 336 | require: &ResTableConfig{}, 337 | expected: false, 338 | }, 339 | { 340 | me: &ResTableConfig{ScreenLayout: ScreenSizeNormal}, 341 | other: &ResTableConfig{}, 342 | require: &ResTableConfig{ScreenLayout: ScreenSizeNormal}, 343 | expected: true, 344 | }, 345 | { 346 | me: &ResTableConfig{}, 347 | other: &ResTableConfig{ScreenLayout: ScreenSizeSmall}, 348 | require: &ResTableConfig{ScreenLayout: ScreenSizeXLarge}, 349 | expected: true, 350 | }, 351 | { 352 | me: &ResTableConfig{ScreenLayout: ScreenSizeSmall}, 353 | other: &ResTableConfig{}, 354 | require: &ResTableConfig{ScreenLayout: ScreenSizeSmall}, 355 | expected: true, 356 | }, 357 | { 358 | me: &ResTableConfig{ScreenLayout: ScreenLongYes}, 359 | other: &ResTableConfig{}, 360 | require: &ResTableConfig{}, 361 | expected: false, 362 | }, 363 | { 364 | me: &ResTableConfig{ScreenLayout: ScreenLongYes}, 365 | other: &ResTableConfig{}, 366 | require: &ResTableConfig{ScreenLayout: ScreenLongYes}, 367 | expected: true, 368 | }, 369 | { 370 | me: &ResTableConfig{ScreenLayout: LayoutDirLTR}, 371 | other: &ResTableConfig{ScreenLayout: LayoutDirAny}, 372 | expected: true, 373 | }, 374 | { 375 | me: &ResTableConfig{SmallestScreenWidthDp: 72}, 376 | other: &ResTableConfig{SmallestScreenWidthDp: 71}, 377 | require: &ResTableConfig{SmallestScreenWidthDp: 72}, 378 | expected: true, 379 | }, 380 | { 381 | me: &ResTableConfig{ScreenWidthDp: 100}, 382 | other: &ResTableConfig{ScreenWidthDp: 99}, 383 | require: &ResTableConfig{ScreenWidthDp: 100}, 384 | expected: true, 385 | }, 386 | { 387 | me: &ResTableConfig{ScreenHeightDp: 100}, 388 | other: &ResTableConfig{ScreenHeightDp: 99}, 389 | require: &ResTableConfig{ScreenHeightDp: 100}, 390 | expected: true, 391 | }, 392 | { 393 | me: &ResTableConfig{Orientation: 1}, 394 | other: &ResTableConfig{}, 395 | require: &ResTableConfig{}, 396 | expected: false, 397 | }, 398 | { 399 | me: &ResTableConfig{Orientation: 1}, 400 | other: &ResTableConfig{}, 401 | require: &ResTableConfig{Orientation: 1}, 402 | expected: true, 403 | }, 404 | { 405 | me: &ResTableConfig{ScreenWidth: 100}, 406 | other: &ResTableConfig{ScreenWidth: 99}, 407 | require: &ResTableConfig{ScreenWidth: 100}, 408 | expected: true, 409 | }, 410 | { 411 | me: &ResTableConfig{ScreenHeight: 100}, 412 | other: &ResTableConfig{ScreenHeight: 99}, 413 | require: &ResTableConfig{ScreenHeight: 100}, 414 | expected: true, 415 | }, 416 | { 417 | me: &ResTableConfig{SDKVersion: 2}, 418 | other: &ResTableConfig{SDKVersion: 1}, 419 | require: &ResTableConfig{}, 420 | expected: false, 421 | }, 422 | { 423 | me: &ResTableConfig{SDKVersion: 2}, 424 | other: &ResTableConfig{SDKVersion: 1}, 425 | require: &ResTableConfig{SDKVersion: 1}, 426 | expected: true, 427 | }, 428 | { 429 | me: &ResTableConfig{SDKVersion: 1, MinorVersion: 1}, 430 | other: &ResTableConfig{SDKVersion: 1}, 431 | require: &ResTableConfig{SDKVersion: 1}, 432 | expected: false, 433 | }, 434 | { 435 | me: &ResTableConfig{SDKVersion: 1, MinorVersion: 1}, 436 | other: &ResTableConfig{SDKVersion: 1}, 437 | require: &ResTableConfig{SDKVersion: 1, MinorVersion: 1}, 438 | expected: true, 439 | }, 440 | } 441 | 442 | func TestIsBetterThan(t *testing.T) { 443 | for _, tt := range isBetterThanTests { 444 | actual := tt.me.IsBetterThan(tt.other, tt.require) 445 | if actual != tt.expected { 446 | if tt.expected { 447 | t.Errorf("%+v is better than %+v, but get false (%+v)", tt.me, tt.other, tt.require) 448 | } else { 449 | t.Errorf("%+v is not better than %+v, but get true (%+v)", tt.me, tt.other, tt.require) 450 | } 451 | } 452 | 453 | if tt.expected { 454 | // If 'me' is better than 'other', 'other' is not better than 'me' 455 | if tt.other.IsBetterThan(tt.me, tt.require) { 456 | t.Errorf("%v is not better than %+v, but get true (%+v)", tt.other, tt.me, tt.require) 457 | } 458 | } 459 | } 460 | } 461 | -------------------------------------------------------------------------------- /testdata/AndroidManifest.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shogo82148/androidbinary/565dc46e2be54110ebcdb9c453f49cd4d98cc96d/testdata/AndroidManifest.xml -------------------------------------------------------------------------------- /testdata/MyApplication/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | -------------------------------------------------------------------------------- /testdata/MyApplication/AndroidManifest.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shogo82148/androidbinary/565dc46e2be54110ebcdb9c453f49cd4d98cc96d/testdata/MyApplication/AndroidManifest.xml -------------------------------------------------------------------------------- /testdata/MyApplication/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /testdata/MyApplication/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | android { 8 | compileSdkVersion 28 9 | defaultConfig { 10 | applicationId "com.shogo82148.androidbinary.myapplication" 11 | minSdkVersion 26 12 | targetSdkVersion 28 13 | versionCode 1 14 | versionName "1.0" 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | implementation fileTree(dir: 'libs', include: ['*.jar']) 27 | implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 28 | implementation 'androidx.appcompat:appcompat:1.0.2' 29 | implementation 'androidx.core:core-ktx:1.0.2' 30 | testImplementation 'junit:junit:4.12' 31 | androidTestImplementation 'androidx.test:runner:1.1.1' 32 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 33 | } 34 | -------------------------------------------------------------------------------- /testdata/MyApplication/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /testdata/MyApplication/app/src/androidTest/java/com/shogo82148/androidbinary/myapplication/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.shogo82148.androidbinary.myapplication 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.shogo82148.androidbinary.myapplication", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /testdata/MyApplication/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /testdata/MyApplication/app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /testdata/MyApplication/app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /testdata/MyApplication/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /testdata/MyApplication/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /testdata/MyApplication/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shogo82148/androidbinary/565dc46e2be54110ebcdb9c453f49cd4d98cc96d/testdata/MyApplication/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /testdata/MyApplication/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shogo82148/androidbinary/565dc46e2be54110ebcdb9c453f49cd4d98cc96d/testdata/MyApplication/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /testdata/MyApplication/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shogo82148/androidbinary/565dc46e2be54110ebcdb9c453f49cd4d98cc96d/testdata/MyApplication/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /testdata/MyApplication/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shogo82148/androidbinary/565dc46e2be54110ebcdb9c453f49cd4d98cc96d/testdata/MyApplication/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /testdata/MyApplication/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shogo82148/androidbinary/565dc46e2be54110ebcdb9c453f49cd4d98cc96d/testdata/MyApplication/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /testdata/MyApplication/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shogo82148/androidbinary/565dc46e2be54110ebcdb9c453f49cd4d98cc96d/testdata/MyApplication/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /testdata/MyApplication/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shogo82148/androidbinary/565dc46e2be54110ebcdb9c453f49cd4d98cc96d/testdata/MyApplication/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /testdata/MyApplication/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shogo82148/androidbinary/565dc46e2be54110ebcdb9c453f49cd4d98cc96d/testdata/MyApplication/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /testdata/MyApplication/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shogo82148/androidbinary/565dc46e2be54110ebcdb9c453f49cd4d98cc96d/testdata/MyApplication/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /testdata/MyApplication/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shogo82148/androidbinary/565dc46e2be54110ebcdb9c453f49cd4d98cc96d/testdata/MyApplication/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /testdata/MyApplication/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /testdata/MyApplication/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | My Application 3 | 4 | -------------------------------------------------------------------------------- /testdata/MyApplication/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /testdata/MyApplication/app/src/main/res/values/values.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | false 5 | 6 | -42 7 | 8 | foobar 9 | 10 | -------------------------------------------------------------------------------- /testdata/MyApplication/app/src/test/java/com/shogo82148/androidbinary/myapplication/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.shogo82148.androidbinary.myapplication 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /testdata/MyApplication/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = '1.3.50' 5 | repositories { 6 | google() 7 | jcenter() 8 | 9 | } 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.5.0' 12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | jcenter() 22 | 23 | } 24 | } 25 | 26 | task clean(type: Delete) { 27 | delete rootProject.buildDir 28 | } 29 | -------------------------------------------------------------------------------- /testdata/MyApplication/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ROOT=$(cd "$(dirname "$0")" && pwd) 4 | 5 | set -uex 6 | 7 | cd "$ROOT" 8 | ./gradlew build 9 | 10 | cd "$ROOT/app/build/outputs/apk/debug/" 11 | rm -rf app-debug 12 | mkdir app-debug 13 | cd app-debug 14 | unzip ../app-debug.apk 15 | 16 | cp AndroidManifest.xml "$ROOT" 17 | cp resources.arsc "$ROOT" 18 | -------------------------------------------------------------------------------- /testdata/MyApplication/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | 19 | # AndroidX package structure to make it clearer which packages are bundled with the 20 | # Android operating system, and which are packaged with your app's APK 21 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 22 | android.useAndroidX=true 23 | # Automatically convert third-party libraries to use AndroidX 24 | android.enableJetifier=true 25 | 26 | # Kotlin code style for this project: "official" or "obsolete": 27 | kotlin.code.style=official 28 | -------------------------------------------------------------------------------- /testdata/MyApplication/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shogo82148/androidbinary/565dc46e2be54110ebcdb9c453f49cd4d98cc96d/testdata/MyApplication/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /testdata/MyApplication/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Aug 25 17:49:53 JST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip 7 | -------------------------------------------------------------------------------- /testdata/MyApplication/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /testdata/MyApplication/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /testdata/MyApplication/resources.arsc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shogo82148/androidbinary/565dc46e2be54110ebcdb9c453f49cd4d98cc96d/testdata/MyApplication/resources.arsc -------------------------------------------------------------------------------- /testdata/MyApplication/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | rootProject.name='My Application' 3 | -------------------------------------------------------------------------------- /testdata/fuzz/FuzzNewXMLFile/4082a8258c184ace: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("00\x1e\x0000000000000000000000000000\x02\x01\x00\x000000000000000000") 3 | -------------------------------------------------------------------------------- /testdata/fuzz/FuzzNewXMLFile/48339f138db8f5a2: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("\x03\x00\b\x00T\v\x00\x00\x01\x00\x1c\x00\x94\x05\x00\x00\"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x00\x00\x004\x00\x00\x00@\x00\x00\x00N\x00\x00\x00Z\x00\x00\x00r\x00\x00\x00\x98\x00\x00\x00\xaa\x00\x00\x00\x02\x01\x00\x00\x06\x01\x00\x00\x18\x01\x00\x00,\x01\x00\x00h\x01\x00\x00t\x01\x00\x00\x96\x01\x00\x00\xcc\x01\x00\x00\b\x02\x00\x00Z\x02\x00\x00\x94\x02\x00\x00\xe6\x02\x00\x00(\x03\x00\x00B\x03\x00\x00^\x03\x00\x00\x90\x03\x00\x00\xa4\x03\x00\x00\xca\x03\x00\x00\xe8\x03\x00\x00\xf8\x03\x00\x000\x04\x00\x00D\x04\x00\x00\x88\x04\x00\x00\xa2\x04\x00\x00\xc4\x04\x00\x00\v\x00v\x00e\x00r\x00s\x00i\x00o\x00n\x00N\x00a\x00m\x00e\x00\x00\x00\v\x00v\x00e\x00r\x00s\x00i\x00o\x00n\x00C\x00o\x00d\x00e\x00\x00\x00\x04\x00n\x00a\x00m\x00e\x00\x00\x00\x05\x00l\x00a\x00b\x00e\x00l\x00\x00\x00\x04\x00i\x00c\x00o\x00n\x00\x00\x00\n\x00d\x00e\x00b\x00u\x00g\x00g\x00a\x00b\x00l\x00e\x00\x00\x00\x11\x00s\x00c\x00r\x00e\x00e\x00n\x00O\x00r\x00i\x00e\x00n\x00t\x00a\x00t\x00i\x00o\x00n\x00\x00\x00\a\x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00\x00\x00*\x00h\x00t\x00t\x00p\x00:\x00/\x00/\x00s\x00c\x00h\x00e\x00m\x00a\x00s\x00.\x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00.\x00c\x00o\x00m\x00/\x00a\x00p\x00k\x00/\x00r\x00e\x00s\x00/\x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00\x00\x00\x00\x00\x00\x00\a\x00p\x00a\x00c\x00k\x00a\x00g\x00e\x00\x00\x00\b\x00m\x00a\x00n\x00i\x00f\x00e\x00s\x00t\x00\x00\x00\x1c\x00n\x00e\x00t\x00.\x00s\x00o\x00r\x00a\x00b\x00l\x00u\x00e\x00.\x00s\x00h\x00o\x00g\x00o\x00.\x00F\x00W\x00M\x00e\x00a\x00s\x00u\x00r\x00e\x00\x00\x00\x04\x00\xc60\xb90\xc80Hr\x00\x00\x0f\x00u\x00s\x00e\x00s\x00-\x00p\x00e\x00r\x00m\x00i\x00s\x00s\x00i\x00o\x00n\x00\x00\x00\x19\x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00.\x00p\x00e\x00r\x00m\x00i\x00s\x00s\x00i\x00o\x00n\x00.\x00C\x00A\x00M\x00E\x00R\x00A\x00\x00\x00\x1c\x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00.\x00p\x00e\x00r\x00m\x00i\x00s\x00s\x00i\x00o\x00n\x00.\x00W\x00A\x00K\x00E\x00_\x00L\x00O\x00C\x00K\x00\x00\x00'\x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00.\x00p\x00e\x00r\x00m\x00i\x00s\x00s\x00i\x00o\x00n\x00.\x00A\x00C\x00C\x00E\x00S\x00S\x00_\x00F\x00I\x00N\x00E\x00_\x00L\x00O\x00C\x00A\x00T\x00I\x00O\x00N\x00\x00\x00\x1b\x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00.\x00p\x00e\x00r\x00m\x00i\x00s\x00s\x00i\x00o\x00n\x00.\x00I\x00N\x00T\x00E\x00R\x00N\x00E\x00T\x00\x00\x00'\x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00.\x00p\x00e\x00r\x00m\x00i\x00s\x00s\x00i\x00o\x00n\x00.\x00A\x00C\x00C\x00E\x00S\x00S\x00_\x00M\x00O\x00C\x00K\x00_\x00L\x00O\x00C\x00A\x00T\x00I\x00O\x00N\x00\x00\x00\x1f\x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00.\x00p\x00e\x00r\x00m\x00i\x00s\x00s\x00i\x00o\x00n\x00.\x00R\x00E\x00C\x00O\x00R\x00D\x00_\x00A\x00U\x00D\x00I\x00O\x00\x00\x00\v\x00a\x00p\x00p\x00l\x00i\x00c\x00a\x00t\x00i\x00o\x00n\x00\x00\x00\f\x00u\x00s\x00e\x00s\x00-\x00l\x00i\x00b\x00r\x00a\x00r\x00y\x00\x00\x00\x17\x00c\x00o\x00m\x00.\x00g\x00o\x00o\x00g\x00l\x00e\x00.\x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00.\x00m\x00a\x00p\x00s\x00\x00\x00\b\x00a\x00c\x00t\x00i\x00v\x00i\x00t\x00y\x00\x00\x00\x11\x00F\x00W\x00M\x00e\x00a\x00s\x00u\x00r\x00e\x00A\x00c\x00t\x00i\x00v\x00i\x00t\x00y\x00\x00\x00\r\x00i\x00n\x00t\x00e\x00n\x00t\x00-\x00f\x00i\x00l\x00t\x00e\x00r\x00\x00\x00\x06\x00a\x00c\x00t\x00i\x00o\x00n\x00\x00\x00\x1a\x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00.\x00i\x00n\x00t\x00e\x00n\x00t\x00.\x00a\x00c\x00t\x00i\x00o\x00n\x00.\x00M\x00A\x00I\x00N\x00\x00\x00\b\x00c\x00a\x00t\x00e\x00g\x00o\x00r\x00y\x00\x00\x00 \x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00.\x00i\x00n\x00t\x00e\x00n\x00t\x00.\x00c\x00a\x00t\x00e\x00g\x00o\x00r\x00y\x00.\x00L\x00A\x00U\x00N\x00C\x00H\x00E\x00R\x00\x00\x00\v\x00M\x00a\x00p\x00A\x00c\x00t\x00i\x00v\x00i\x00t\x00y\x00\x00\x00\x0f\x00S\x00e\x00t\x00t\x00i\x00n\x00g\x00A\x00c\x00t\x00i\x00v\x00i\x00t\x00y\x00\x00\x00\x14\x00P\x00l\x00a\x00c\x00e\x00S\x00e\x00t\x00t\x00i\x00n\x00g\x00A\x00c\x00t\x00i\x00v\x00i\x00t\x00y\x00\x00\x00\x80\x01\b\x00$\x00\x00\x00\x1c\x02\x01\x01\x1b\x02\x01\x01\x03\x00\x01\x01\x01\x00\x01\x01\x02\x00\x01\x01\x0f\x00\x01\x01\x1e\x00\x01\x01\x00\x01\x10\x00\x18\x00\x00\x00\x02\x00\x00\x00\xff\xff\xff\xff\a\x00\x00\x00\b\x00\x00\x00\x02\x01\x10\x00`\x00\x00\x00\x02\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\v\x00\x00\x00\x14\x00\x14\x00\x03\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x01\x00\x00\x00\xff\xff\xff\xff\b\x00\x00\x10\x01\x00\x00\x00\b\x00\x00\x00\x00\x00\x00\x00\r\x00\x00\x00\b\x00\x00\x03\r\x00\x00\x00\xff\xff\xff\xff\n\x00\x00\x00\f\x00\x00\x00\b\x00\x00\x03\f\x00\x00\x00\x02\x01\x10\x008\x00\x00\x00\x04\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0e\x00\x00\x00\x14\x00\x14\x00\x01\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x02\x00\x00\x00\x0f\x00\x00\x00\b\x00\x00\x03\x0f\x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\x04\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0e\x00\x00\x00\x02\x01\x10\x008\x00\x00\x00\x05\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0e\x00\x00\x00\x14\x00\x14\x00\x01\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x02\x00\x00\x00\x10\x00\x00\x00\b\x00\x00\x03\x10\x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\x05\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0e\x00\x00\x00\x02\x01\x10\x008\x00\x00\x00\x06\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0e\x00\x00\x00\x14\x00\x14\x00\x01\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x02\x00\x00\x00\x11\x00\x00\x00\b\x00\x00\x03\x11\x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\x06\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0e\x00\x00\x00\x02\x01\x10\x008\x00\x00\x00\a\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0e\x00\x00\x00\x14\x00\x14\x00\x01\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x02\x00\x00\x00\x12\x00\x00\x00\b\x00\x00\x03\x12\x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\a\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0e\x00\x00\x00\x02\x01\x10\x008\x00\x00\x00\b\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0e\x00\x00\x00\x14\x00\x14\x00\x01\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x02\x00\x00\x00\x13\x00\x00\x00\b\x00\x00\x03\x13\x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\b\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0e\x00\x00\x00\x02\x01\x10\x008\x00\x00\x00\t\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0e\x00\x00\x00\x14\x00\x14\x00\x01\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x02\x00\x00\x00\x14\x00\x00\x00\b\x00\x00\x03\x14\x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\t\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0e\x00\x00\x00\x02\x01\x10\x00`\x00\x00\x00\v\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x15\x00\x00\x00\x14\x00\x14\x00\x03\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x03\x00\x00\x00\xff\xff\xff\xff\b\x00\x00\x01\x00\x00\x04\x7f\b\x00\x00\x00\x04\x00\x00\x00\xff\xff\xff\xff\b\x00\x00\x01\x00\x00\x02\x7f\b\x00\x00\x00\x05\x00\x00\x00\xff\xff\xff\xff\b)\x19\xff#&\xc4\x0f\xc4\x00\x00\x12\x00\x00\x00\x00\x02\x01\x10\x008\x00\x00\x00\f\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x16\x00\x00\x00\x14\x00\x14\x00\x01\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x02\x00\x00\x00\x17\x00\x00\x00\b\x00\x00\x03\x17\x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\f\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x16\x00\x00\x00\x02\x01\x10\x00L\x00\x00\x00\r\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x18\x00\x00\x00\x14\x00\x14\x00\x02\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x02\x00\x00\x00\x19\x00\x00\x00\b\x00\x00\x03\x19\x00\x00\x00\b\x00\x00\x00\x06\x00\x00\x00\xff\xff\xff\xff\b\x00\x00\x10\x00\x00\x00\x00\x02\x01\x10\x00$\x00\x00\x00\x0f\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x1a\x00\x00\x00\x14\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x01\x10\x008\x00\x00\x00\x10\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x1b\x00\x00\x00\x14\x00\x14\x00\x01\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x02\x00\x00\x00\x1c\x00\x00\x00\b\x00\x00\x03\x1c\x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\x10\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x1b\x00\x00\x00\x02\x01\x10\x008\x00\x00\x00\x11\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x1d\x00\x00\x00\x14\x00\x14\x00\x01\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x02\x00\x00\x00\x1e\x00\x00\x00\b\x00\x00\x03\x1e\x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\x11\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x1d\x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\x12\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x1a\x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\x13\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x18\x00\x00\x00\x02\x01\x10\x00L\x00\x00\x00\x14\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x18\x00\x00\x00\x14\x00\x14\x00\x02\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x02\x00\x00\x00\x1f\x00\x00\x00\b\x00\x00\x03\x1f\x00\x00\x00\b\x00\x00\x00\x06\x00\x00\x00\xff\xff\xff\xff\b\x00\x00\x10\x00\x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\x15\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x18\x00\x00\x00\x02\x01\x10\x008\x00\x00\x00\x16\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x18\x00\x00\x00\x14\x00\x14\x00\x01\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x02\x00\x00\x00 \x00\x00\x00\b\x00\x00\x03 \x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\x17\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x18\x00\x00\x00\x02\x01\x10\x008\x00\x00\x00\x18\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x18\x00\x00\x00\x14\x00\x14\x00\x01\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x02\x00\x00\x00!\x00\x00\x00\b\x00\x00\x03!\x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\x19\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x18\x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\x1a\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x15\x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\x1c\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\v\x00\x00\x00\x01\x01\x10\x00\x18\x00\x00\x00\x1c\x00\x00\x00\xff\xff\xff\xff\a\x00\x00\x00\b\x00\x00\x00") 3 | -------------------------------------------------------------------------------- /testdata/fuzz/FuzzNewXMLFile/edccc9a94336dee2: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("\x03\x00\b\x00T\v\x00\x00\x01\x00\x1c\x00\x94\x05\x00\x00\"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x00\x00\x004\x00\x00\x00@\x00\x00\x00N\x00\x00\x00Z\x00\x00\x00r\x00\x00\x00\x98\x00\x00\x00\xaa\x00\x00\x00\x02\x01\x00\x00\x06\x01\x00\x00\x18\x01\x00\x00,\x01\x00\x00h\x01\x00\x00t\x01\x00\x00\x96\x01\x00\x00\xcc\x01\x00\x00\b\x02\x00\x00Z\x02\x00\x00\x94\x02\x00\x00\xe6\x02\x00\x00(\x03\x00\x00B\x03\x00\x00^\x03\x00\x00\x90\x03\x00\x00\xa4\x03\x00\x00\xca\x03\x00\x00\xe8\x03\x00\x00\xf8\x03\x00\x000\x04\x00\x00D\x04\x00\x00\x88\x04\x00\x00\xa2\x04\x00\x00\xc4\x04\x00\x00\v\x00v\x00e\x00r\x00s\x00i\x00o\x00n\x00N\x00a\x00m\x00e\x00\x00\x00\v\x00v\x00e\x00r\x00s\x00i\x00o\x00n\x00C\x00o\x00d\x00e\x00\x00\x00\x04\x00n\x00a\x00m\x00e\x00\x00\x00\x05\x00l\x00a\x00b\x00e\x00l\x00\x00\x00\x04\x00i\x00c\x00o\x00n\x00\x00\x00\n\x00d\x00e\x00b\x00u\x00g\x00g\x00a\x00b\x00l\x00e\x00\x00\x00\x11\x00s\x00c\x00r\x00e\x00e\x00n\x00O\x00r\x00i\x00e\x00n\x00t\x00a\x00t\x00i\x00o\x00n\x00\x00\x00\a\x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00\x00\x00*\x00h\x00t\x00t\x00p\x00:\x00/\x00/\x00s\x00c\x00h\x00e\x00m\x00a\x00s\x00.\x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00.\x00c\x00o\x00m\x00/\x00a\x00p\x00k\x00/\x00r\x00e\x00s\x00/\x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00\x00\x00\x00\x00\x00\x00\a\x00p\x00a\x00c\x00k\x00a\x00g\x00e\x00\x00\x00\b\x00m\x00a\x00n\x00i\x00f\x00e\x00s\x00t\x00\x00\x00\x1c\x00n\x00\x00t\x00.\x00s\x00o\x00r\x00a\x00b\x00l\x00u\x00e\x00.\x00s\x00h\x00o\x00g\x00o\x00.\x00F\x00W\x00M\x00e\x00a\x00s\x00u\x00r\x00e\x00\x00\x00\x04\x00\xc60\xb90\xc80Hr\x00\x00\x0f\x00u\x00s\x00e\x00s\x00-\x00p\x00e\x00r\x00m\x00i\x00s\x00s\x00i\x00o\x00n\x00\x00\x00\x19\x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00.\x00p\x00e\x00r\x00m\x00i\x00s\x00s\x00i\x00o\x00n\x00.\x00C\x00A\x00M\x00E\x00R\x00A\x00\x00\x00\x1c\x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00.\x00p\x00e\x00r\x00m\x00i\x00s\x00s\x00i\x00o\x00n\x00.\x00W\x00A\x00K\x00E\x00_\x00L\x00O\x00C\x00K\x00\x00\x00'\x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00.\x00p\x00e\x00r\x00m\x00i\x00s\x00s\x00i\x00o\x00n\x00.\x00A\x00C\x00C\x00E\x00S\x00S\x00_\x00F\x00I\x00N\x00E\x00_\x00L\x00O\x00C\x00A\x00T\x00I\x00O\x00N\x00\x00\x00\x1b\x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00.\x00p\x00e\x00r\x00m\x00i\x00s\x00s\x00i\x00o\x00n\x00.\x00I\x00N\x00T\x00E\x00R\x00N\x00E\x00T\x00\x00\x00'\x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00.\x00p\x00e\x00r\x00m\x00i\x00s\x00s\x00i\x00o\x00n\x00.\x00A\x00C\x00C\x00E\x00S\x00S\x00_\x00M\x00O\x00C\x00K\x00_\x00L\x00O\x00C\x00A\x00T\x00I\x00O\x00N\x00\x00\x00\x1f\x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00.\x00p\x00e\x00r\x00m\x00i\x00s\x00s\x00i\x00o\x00n\x00.\x00R\x00E\x00C\x00O\x00R\x00D\x00_\x00A\x00U\x00D\x00I\x00O\x00\x00\x00\v\x00a\x00p\x00p\x00l\x00i\x00c\x00a\x00t\x00i\x00o\x00n\x00\x00\x00\f\x00u\x00s\x00e\x00s\x00-\x00l\x00i\x00b\x00r\x00a\x00r\x00y\x00\x00\x00\x17\x00c\x00o\x00m\x00.\x00g\x00o\x00o\x00g\x00l\x00e\x00.\x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00.\x00m\x00a\x00p\x00s\x00\x00\x00\b\x00a\x00c\x00t\x00i\x00v\x00i\x00t\x00y\x00\x00\x00\x11\x00F\x00W\x00M\x00e\x00a\x00s\x00u\x00r\x00e\x00A\x00c\x00t\x00i\x00v\x00i\x00t\x00y\x00\x00\x00\r\x00i\x00n\x00t\x00e\x00n\x00t\x00-\x00f\x00i\x00l\x00t\x00e\x00r\x00\x00\x00\x06\x00a\x00c\x00t\x00i\x00o\x00n\x00\x00\x00\x1a\x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00.\x00i\x00n\x00t\x00e\x00n\x00t\x00.\x00a\x00c\x00t\x00i\x00o\x00n\x00.\x00M\x00A\x00I\x00N\x00\x00\x00\b\x00c\x00a\x00t\x00e\x00g\x00o\x00r\x00y\x00\x00\x00 \x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00.\x00i\x00n\x00t\x00e\x00n\x00t\x00.\x00c\x00a\x00t\x00e\x00g\x00o\x00r\x00y\x00.\x00L\x00A\x00U\x00N\x00C\x00H\x00E\x00R\x00\x00\x00\v\x00M\x00a\x00p\x00A\x00c\x00t\x00i\x00v\x00i\x00t\x00y\x00\x00\x00\x0f\x00S\x00e\x00t\x00t\x00i\x00n\x00g\x00A\x00c\x00t\x00i\x00v\x00i\x00t\x00y\x00\x00\x00\x14\x00P\x00l\x00a\x00c\x00e\x00S\x00e\x00t\x00t\x00i\x00n\x00g\x00A\x00c\x00t\x00i\x00v\x00i\x00t\x00y\x00\x00\x00\x80\x01\b\x00$\x00\x00\x00\x1c\x02\x01\x01\x1b\x02\x01\x01\x03\x00\x01\x01\x01\x00\x01\x01\x02\x00\x01\x01\x0f\x00\x01\x01\x1e\x00\x01\x01\x00\x01\x10\x00\x18\x00\x00\x00\x02\x00\x00\x00\xff\xff\xff\xff\a\x00\x00\x00\b\x00\x00\x00\x02\x01\x10\x00`\x00\x00\x00\x02\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\v\x00\x00\x00\x14\x00\x14\x00\x03\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x01\x00\x00\x00\xff\xff\xff\xff\b\x00\x00\x10\x01\x00\x00\x00\b\x00\x00\x00\x00\x00\x00\x00\r\x00\x00\x00\b\x00\x00\x03\r\x00\x00\x00\xff\xff\xff\xff\n\x00\x00\x00\f\x00\x00\x00\b\x00\x00\x03\f\x00\x00\x00\x02\x01\x10\x008\x00\x00\x00\x04\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0e\x00\x00\x00\x14\x00\x14\x00\x01\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x02\x00\x00\x00\x0f\x00\x00\x00\b\x00\x00\x03\x0f\x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\x04\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0e\x00\x00\x00\x02\x01\x10\x008\x00\x00\x00\x05\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0e\x00\x00\x00\x14\x00\x14\x00\x01\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x02\x00\x00\x00\x10\x00\x00\x00\b\x00\x00\x03\x10\x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\x05\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0e\x00\x00\x00\x02\x01\x10\x008\x00\x00\x00\x06\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0e\x00\x00\x00\x14\x00\x14\x00\x01\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x02\x00\x00\x00\x11\x00\x00\x00\b\x00\x00\x03\x11\x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\x06\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0e\x00\x00\x00\x02\x01\x10\x008\x00\x00\x00\a\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0e\x00\x00\x00\x14\x00\x14\x00\x01\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x02\x00\x00\x00\x12\x00\x00\x00\b\x00\x00\x03\x12\x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\a\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0e\x00\x00\x00\x02\x01\x10\x008\x00\x00\x00\b\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0e\x00\x00\x00\x14\x00\x14\x00\x01\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x02\x00\x00\x00\x13\x00\x00\x00\b\x00\x00\x03\x13\x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\b\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0e\x00\x00\x00\x02\x01\x10\x008\x00\x00\x00\t\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0e\x00\x00\x00\x14\x00\x14\x00\x01\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x02\x00\x00\x00\x14\x00\x00\x00\b\x00\x00\x03\x14\x00\x00\x00\x03\x01\x10\x00HHHHHH\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0e\x00\x00\x00") 3 | -------------------------------------------------------------------------------- /testdata/fuzz/FuzzNewXMLFile/ff4e319721ef52a0: -------------------------------------------------------------------------------- 1 | go test fuzz v1 2 | []byte("\x03\x00\b\x00T\v\x00\x00\x01\x00\x1c\x00\x94\x05\x00\x00\"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\x00\x00\x004\x00\x00\x00@\x00\x00\x00N\x00\x00\x00Z\x00\x00\x00r\x00\x00\x00\x98\x00\x00\x00\xaa\x00\x00\x00\x02\x01\x00\x00\x06\x01\x00\x00\x18\x01\x00\x00,\x01\x00\x00h\x01\x00\x00t\x01\x00\x00\x96\x01\x00\x00\xcc\x01\x00\x00\b\x02\x00\x00Z\x02\x00\x00\x94\x02\x00\x00\xe6\x02\x00\x00(\x03\x00\x00B\x03\x00\x00^\x03\x00\x00\x90\x03\x00\x00\xa4\x03\x00\x00\xca\x03\x00\x00\xe8\x03\x00\x00\xf8\x03\x00\x000\x04\x00\x00D\x04\x00\x00\x88\x04\x00\x00\xa2\x04\x00\x00\xc4\x04\x00\x00\v\x00v\x00e\x00r\x00s\x00i\x00o\x00n\x00N\x00a\x00m\x00e\x00\x00\x00\v\x00v\x00e\x00r\x00s\x00i\x00o\x00n\x00C\x00o\x00d\x00e\x00\x00\x00\x04\x00n\x00a\x00m\x00e\x00\x00\x00\x05\x00l\x00a\x00b\x00e\x00l\x00\x00\x00\x04\x00i\x00c\x00o\x00n\x00\x00\x00\n\x00d\x00e\x00b\x00u\x00g\x00g\x00a\x00b\x00l\x00e\x00\x00\x00\x11\x00s\x00c\x00r\x00e\x00e\x00n\x00O\x00r\x00i\x00e\x00n\x00t\x00a\x00t\x00i\x00o\x00n\x00\x00\x00\a\x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00\x00\x00*\x00h\x00t\x00t\x00p\x00:\x00/\x00/\x00s\x00c\x00h\x00e\x00m\x00a\x00s\x00.\x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00.\x00c\x00o\x00m\x00/\x00a\x00p\x00k\x00/\x00r\x00e\x00s\x00/\x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00\x00\x00\x00\x00\x00\x00\a\x00p\x00a\x00c\x00k\x00a\x00g\x00e\x00\x00\x00\b\x00m\x00a\x00n\x00i\x00f\x00e\x00s\x00t\x00\x00\x00\x1c\x00n\x00e\x00t\x00.\x00s\x00o\x00r\x00a\x00b\x00l\x00u\x00e\x00.\x00s\x00h\x00o\x00g\x00o\x00.\x00F\x00W\x00M\x00e\x00a\x00s\x00u\x00r\x00e\x00\x00\x00\x04\x00\xc60\xb90\xc80Hr\x00\x00\x0f\x00u\x00s\x00e\x00s\x00-\x00p\x00e\x00r\x00m\x00i\x00s\x00s\x00i\x00o\x00n\x00\x00\x00\x19\x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00.\x00p\x00e\x00r\x00m\x00i\x00s\x00s\x00i\x00o\x00n\x00.\x00C\x00A\x00M\x00E\x00R\x00A\x00\x00\x00\x1c\x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00.\x00p\x00e\x00r\x00m\x00i\x00s\x00s\x00i\x00o\x00n\x00.\x00W\x00A\x00K\x00E\x00_\x00L\x00O\x00C\x00K\x00\x00\x00'\x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00.\x00p\x00e\x00r\x00m\x00i\x00s\x00s\x00i\x00o\x00n\x00.\x00A\x00C\x00C\x00E\x00S\x00S\x00_\x00F\x00I\x00N\x00E\x00_\x00L\x00O\x00C\x00A\x00T\x00I\x00O\x00N\x00\x00\x00\x1b\x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00.\x00p\x00e\x00r\x00m\x00i\x00s\x00s\x00i\x00o\x00n\x00.\x00I\x00N\x00T\x00E\x00R\x00N\x00E\x00T\x00\x00\x00'\x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00.\x00p\x00e\x00r\x00m\x00i\x00s\x00s\x00i\x00o\x00n\x00.\x00A\x00C\x00C\x00E\x00S\x00S\x00_\x00M\x00O\x00C\x00K\x00_\x00L\x00O\x00C\x00A\x00T\x00I\x00O\x00N\x00\x00\x00\x1f\x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00.\x00p\x00e\x00r\x00m\x00i\x00s\x00s\x00i\x00o\x00n\x00.\x00R\x00E\x00C\x00O\x00R\x00D\x00_\x00A\x00U\x00D\x00I\x00O\x00\x00\x00\v\x00a\x00p\x00p\x00l\x00i\x00c\x00a\x00t\x00i\x00o\x00n\x00\x00\x00\f\x00u\x00s\x00e\x00s\x00-\x00l\x00i\x00b\x00r\x00a\x00r\x00y\x00\x00\x00\x17\x00c\x00o\x00m\x00.\x00g\x00o\x00o\x00g\x00l\x00e\x00.\x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00.\x00m\x00a\x00p\x00s\x00\x00\x00\b\x00a\x00c\x00t\x00i\x00v\x00i\x00t\x00y\x00\x00\x00\x11\x00F\x00W\x00M\x00e\x00a\x00s\x00u\x00r\x00e\x00A\x00c\x00t\x00i\x00v\x00i\x00t\x00y\x00\x00\x00\r\x00i\x00n\x00t\x00e\x00n\x00t\x00-\x00f\x00i\x00l\x00t\x00e\x00r\x00\x00\x00\x06\x00a\x00c\x00t\x00i\x00o\x00n\x00\x00\x00\x1a\x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00.\x00i\x00n\x00t\x00e\x00n\x00t\x00.\x00a\x00c\x00t\x00i\x00o\x00n\x00.\x00M\x00A\x00I\x00N\x00\x00\x00\b\x00c\x00a\x00t\x00e\x00g\x00o\x00r\x00y\x00\x00\x00 \x00a\x00n\x00d\x00r\x00o\x00i\x00d\x00.\x00i\x00n\x00t\x00e\x00n\x00t\x00.\x00c\x00a\x00t\x00e\x00g\x00o\x00r\x00y\x00.\x00L\x00A\x00U\x00N\x00C\x00H\x00E\x00R\x00\x00\x00\v\x00M\x00a\x00p\x00A\x00c\x00t\x00i\x00v\x00i\x00t\x00y\x00\x00\x00\x0f\x00S\x00e\x00t\x00t\x00i\x00n\x00g\x00A\x00c\x00t\x00i\x00v\x00i\x00t\x00y\x00\x00\x00\x14\x00P\x00l\x00a\x00c\x00e\x00S\x00e\x00t\x00t\x00i\x00n\x00g\x00A\x00c\x00t\x00i\x00v\x00i\x00t\x00y\x00\x00\x00\x80\x01\b\x00$\x00\x00\x00\x1c\x02\x01\x01\x1b\x02\x01\x01\x03\x00\x01\x01\x01\x00\x01\x01\x02\x00\x01\x01\x0f\x00\x01\x01\x1e\x00\x01\x01\x00\x01\x10\x00\x18\x00\x00\x00\x02\x00\x00\x00\xff\xff\xff\xff\a\x00\x00\x00\b\x00\x00\x00\x02\x01\x10\x00`\x00\x00\x00\x02\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\v\x00\x00\x00\x14\x00\x14\x00\x03\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x01\x00\x00\x00\xff\xff\xff\xff\b\x00\x00\x10\x01\x00\x00\x00\b\x00\x00\x00\x00\x00\x00\x00\r\x00\x00\x00\b\x00\x00\x03\r\x00\x00\x00\xff\xff\xff\xff\n\x00\x00\x00\f\x00\x00\x00\b\x00\x00\x03\f\x00\x00\x00\x02\x01\x10\x008\x00\x00\x00\x04\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0e\x00\x00\x00\x14\x00\x14\x00\x01\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x02\x00\x00\x00\x0f\x00\x00\x00\b\x00\x00\x03\x0f\x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\x04\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0e\x00\x00\x00\x02\x01\x10\x008\x00\x00\x00\x05\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0e\x00\x00\x00\x14\x00\x14\x00\x01\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x02\x00\x00\x00\x10\x00\x00\x00\b\x00\x00\x03\x10\x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\x05\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0e\x00\x00\x00\x02\x01\x10\x008\x00\x00\x00\x06\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0e\x00\x00\x00\x14\x00\x14\x00\x01\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x02\x00\x00\x00\x11\x00\x00\x00\b\x00\x00\x03\x11\x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\x06\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0e\x00\x00\x00\x02\x01\x10\x008\x00\x00\x00\a\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0e\x00\x00\x00\x14\x00\x14\x00\x01\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x02\x00\x00\x00\x12\x00\x00\x00\b\x00\x00\x03\x12\x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\a\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0e\x00\x00\x00\x02\x01\x10\x008\x00\x00\x00\b\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0e\x00\x00\x00\x14\x00\x14\x00\x01\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x02\x00\x00\x00\x13\x00\x00\x00\b\x00\x00\x03\x13\x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\b\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0e\x00\x00\x00\x02\x01\x10\x008\x00\x00\x00\t\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0e\x00\x00\x00\x14\x00\x14\x00\x01\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x02\x00\x00\x00\x14\x00\x00\x00\b\x00\x00\x03\x14\x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\t\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x0e\x00\x00\x00\x02\x01\x10\x00`\x00\x00\x00\v\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x15\x00\x00\x00\x14\x00\x14\x00\x03\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x03\x00\x00\x00\xff\xff\xff\xff\b\x00\x00\x01\x00\x00\x04\x7f\b\x00\x00\x00\x04\x00\x00\x00\xff\xff\xff\xff\b\x00\x00\x01\x00\x00\x02\x7f\b\x00\x00\x00\x05\x00\x00\x00\xff\xff\xff\xff\b\x00\x00\x12\x00\x00\x00\x00\x02\x01\x10\x008\x00\x00\x00\f\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x16\x00\x00\x00\x14\x00\x14\x00\x01\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x02\x00\x00\x00\x17\x00\x00\x00\b\x00\x00\x03\x17\x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\f\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x16\x00\x00\x00\x02\x01\x10\x00L\x00\x00\x00\r\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x18\x00\x00\x00\x14\x00\x14\x00\x02\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x02\x00\x00\x00\x19\x00\x00\x00\b\x00\x00\x03\x19\x00\x00\x00\b\x00\x00\x00\x06\x00\x00\x00\xff\xff\xff\xff\b\x00\x00\x10\x00\x00\x00\x00\x02\x01\x10\x00$\x00\x00\x00\x0f\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x1a\x00\x00\x00\x14\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x01\x10\x008\x00\x00\x00\x10\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x1b\x00\x00\x00\x14\x00\x14\x00\x01\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x02\x00\x00\x00\x1c\x00\x00\x00\b\x00\x00\x03\x1c\x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\x10\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x1b\x00\x00\x00\x02\x01\x10\x008\x00\x00\x00\x11\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x1d\x00\x00\x00\x14\x00\x14\x00\x01\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x02\x00\x00\x00\x1e\x00\x00\x00\b\x00\x00\x03\x1e\x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\x11\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x1d\x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\x12\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x1a\x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\x13\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x18\x00\x00\x00\x02\x01\x10\x00L\x00\x00\x00\x14\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x18\x00\x00\x00\x14\x00\x14\x00\x02\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x02\x00\x00\x00\x1f\x00\x00\x00\b\x00\x00\x03\x1f\x00\x00\x00\b\x00\x00\x00\x06\x00\x00\x05!\x05\x00\xff\xff\xff\xff\b\x00\x00\x10\x00\x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\x15\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x18\x00\x00\x00\x02\x01\x10\x008\x00\x00\x00\x16\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x18\x00\x00\x00\x14\x00\x14\x00\x01\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x02\x00\x00\x00 \x00\x00\x00\b\x00\x00\x03 \x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\x17\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x18\x00\x00\x00\x02\x01\x10\x008\x00\x00\x00\x18\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x18\x00\x00\x00\x14\x00\x14\x00\x01\x00\x00\x00\x00\x00\x00\x00\b\x00\x00\x00\x02\x00\x00\x00!\x00\x00\x00\b\x00\x00\x03!\x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\x19\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x18\x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\x1a\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x15\x00\x00\x00\x03\x01\x10\x00\x18\x00\x00\x00\x1c\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\v\x00\x00\x00\x01\x01\x10\x00\x18\x00\x00\x00\x1c\x00\x00\x00\xff\xff\xff\xff\a\x00\x00\x00\b\x00\x00\x00") 3 | -------------------------------------------------------------------------------- /testdata/resources.arsc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shogo82148/androidbinary/565dc46e2be54110ebcdb9c453f49cd4d98cc96d/testdata/resources.arsc -------------------------------------------------------------------------------- /type.go: -------------------------------------------------------------------------------- 1 | package androidbinary 2 | 3 | import ( 4 | "encoding/xml" 5 | "fmt" 6 | "reflect" 7 | "strconv" 8 | ) 9 | 10 | type injector interface { 11 | inject(table *TableFile, config *ResTableConfig) 12 | } 13 | 14 | var injectorType = reflect.TypeOf((*injector)(nil)).Elem() 15 | 16 | func inject(val reflect.Value, table *TableFile, config *ResTableConfig) { 17 | if val.Kind() == reflect.Ptr { 18 | if val.IsNil() { 19 | return 20 | } 21 | val = val.Elem() 22 | } 23 | if val.CanInterface() && val.Type().Implements(injectorType) { 24 | val.Interface().(injector).inject(table, config) 25 | return 26 | } 27 | if val.CanAddr() { 28 | pv := val.Addr() 29 | if pv.CanInterface() && pv.Type().Implements(injectorType) { 30 | pv.Interface().(injector).inject(table, config) 31 | return 32 | } 33 | } 34 | 35 | switch val.Kind() { 36 | default: 37 | // ignore other types 38 | return 39 | case reflect.Slice, reflect.Array: 40 | l := val.Len() 41 | for i := 0; i < l; i++ { 42 | inject(val.Index(i), table, config) 43 | } 44 | return 45 | case reflect.Struct: 46 | l := val.NumField() 47 | for i := 0; i < l; i++ { 48 | inject(val.Field(i), table, config) 49 | } 50 | } 51 | } 52 | 53 | // Bool is a boolean value in XML file. 54 | // It may be an immediate value or a reference. 55 | type Bool struct { 56 | value string 57 | table *TableFile 58 | config *ResTableConfig 59 | } 60 | 61 | // WithTableFile ties TableFile to the Bool. 62 | func (v Bool) WithTableFile(table *TableFile) Bool { 63 | return Bool{ 64 | value: v.value, 65 | table: table, 66 | config: v.config, 67 | } 68 | } 69 | 70 | // WithResTableConfig ties ResTableConfig to the Bool. 71 | func (v Bool) WithResTableConfig(config *ResTableConfig) Bool { 72 | return Bool{ 73 | value: v.value, 74 | table: v.table, 75 | config: config, 76 | } 77 | } 78 | 79 | func (v *Bool) inject(table *TableFile, config *ResTableConfig) { 80 | v.table = table 81 | v.config = config 82 | } 83 | 84 | // SetBool sets a boolean value. 85 | func (v *Bool) SetBool(value bool) { 86 | v.value = strconv.FormatBool(value) 87 | } 88 | 89 | // SetResID sets a boolean value with the resource id. 90 | func (v *Bool) SetResID(resID ResID) { 91 | v.value = resID.String() 92 | } 93 | 94 | // UnmarshalXMLAttr implements xml.UnmarshalerAttr. 95 | func (v *Bool) UnmarshalXMLAttr(attr xml.Attr) error { 96 | v.value = attr.Value 97 | return nil 98 | } 99 | 100 | // MarshalXMLAttr implements xml.MarshalerAttr. 101 | func (v Bool) MarshalXMLAttr(name xml.Name) (xml.Attr, error) { 102 | if v.value == "" { 103 | // return the zero value of bool 104 | return xml.Attr{ 105 | Name: name, 106 | Value: "false", 107 | }, nil 108 | } 109 | return xml.Attr{ 110 | Name: name, 111 | Value: v.value, 112 | }, nil 113 | } 114 | 115 | // Bool returns the boolean value. 116 | // It resolves the reference if needed. 117 | func (v Bool) Bool() (bool, error) { 118 | if v.value == "" { 119 | return false, nil 120 | } 121 | if !IsResID(v.value) { 122 | return strconv.ParseBool(v.value) 123 | } 124 | id, err := ParseResID(v.value) 125 | if err != nil { 126 | return false, err 127 | } 128 | value, err := v.table.GetResource(id, v.config) 129 | if err != nil { 130 | return false, err 131 | } 132 | ret, ok := value.(bool) 133 | if !ok { 134 | return false, fmt.Errorf("invalid type: %T", value) 135 | } 136 | return ret, nil 137 | } 138 | 139 | // MustBool is same as Bool, but it panics if it fails to parse the value. 140 | func (v Bool) MustBool() bool { 141 | ret, err := v.Bool() 142 | if err != nil { 143 | panic(err) 144 | } 145 | return ret 146 | } 147 | 148 | // Int32 is an integer value in XML file. 149 | // It may be an immediate value or a reference. 150 | type Int32 struct { 151 | value string 152 | table *TableFile 153 | config *ResTableConfig 154 | } 155 | 156 | // WithTableFile ties TableFile to the Bool. 157 | func (v Int32) WithTableFile(table *TableFile) Int32 { 158 | return Int32{ 159 | value: v.value, 160 | table: table, 161 | config: v.config, 162 | } 163 | } 164 | 165 | // WithResTableConfig ties ResTableConfig to the Bool. 166 | func (v Int32) WithResTableConfig(config *ResTableConfig) Bool { 167 | return Bool{ 168 | value: v.value, 169 | table: v.table, 170 | config: config, 171 | } 172 | } 173 | 174 | func (v *Int32) inject(table *TableFile, config *ResTableConfig) { 175 | v.table = table 176 | v.config = config 177 | } 178 | 179 | // SetInt32 sets an integer value. 180 | func (v *Int32) SetInt32(value int32) { 181 | v.value = strconv.FormatInt(int64(value), 10) 182 | } 183 | 184 | // SetResID sets a boolean value with the resource id. 185 | func (v *Int32) SetResID(resID ResID) { 186 | v.value = resID.String() 187 | } 188 | 189 | // UnmarshalXMLAttr implements xml.UnmarshalerAttr. 190 | func (v *Int32) UnmarshalXMLAttr(attr xml.Attr) error { 191 | v.value = attr.Value 192 | return nil 193 | } 194 | 195 | // MarshalXMLAttr implements xml.MarshalerAttr. 196 | func (v Int32) MarshalXMLAttr(name xml.Name) (xml.Attr, error) { 197 | if v.value == "" { 198 | // return the zero value of int32 199 | return xml.Attr{ 200 | Name: name, 201 | Value: "0", 202 | }, nil 203 | } 204 | return xml.Attr{ 205 | Name: name, 206 | Value: v.value, 207 | }, nil 208 | } 209 | 210 | // Int32 returns the integer value. 211 | // It resolves the reference if needed. 212 | func (v Int32) Int32() (int32, error) { 213 | if v.value == "" { 214 | return 0, nil 215 | } 216 | if !IsResID(v.value) { 217 | v, err := strconv.ParseInt(v.value, 10, 32) 218 | return int32(v), err 219 | } 220 | id, err := ParseResID(v.value) 221 | if err != nil { 222 | return 0, err 223 | } 224 | value, err := v.table.GetResource(id, v.config) 225 | if err != nil { 226 | return 0, err 227 | } 228 | ret, ok := value.(uint32) 229 | if !ok { 230 | return 0, fmt.Errorf("invalid type: %T", value) 231 | } 232 | return int32(ret), nil 233 | } 234 | 235 | // MustInt32 is same as Int32, but it panics if it fails to parse the value. 236 | func (v Int32) MustInt32() int32 { 237 | ret, err := v.Int32() 238 | if err != nil { 239 | panic(err) 240 | } 241 | return ret 242 | } 243 | 244 | // String is a boolean value in XML file. 245 | // It may be an immediate value or a reference. 246 | type String struct { 247 | value string 248 | table *TableFile 249 | config *ResTableConfig 250 | } 251 | 252 | // WithTableFile ties TableFile to the Bool. 253 | func (v String) WithTableFile(table *TableFile) String { 254 | return String{ 255 | value: v.value, 256 | table: table, 257 | config: v.config, 258 | } 259 | } 260 | 261 | // WithResTableConfig ties ResTableConfig to the Bool. 262 | func (v String) WithResTableConfig(config *ResTableConfig) String { 263 | return String{ 264 | value: v.value, 265 | table: v.table, 266 | config: config, 267 | } 268 | } 269 | 270 | func (v *String) inject(table *TableFile, config *ResTableConfig) { 271 | v.table = table 272 | v.config = config 273 | } 274 | 275 | // SetString sets a string value. 276 | func (v *String) SetString(value string) { 277 | v.value = value 278 | } 279 | 280 | // SetResID sets a boolean value with the resource id. 281 | func (v *String) SetResID(resID ResID) { 282 | v.value = resID.String() 283 | } 284 | 285 | // UnmarshalXMLAttr implements xml.UnmarshalerAttr. 286 | func (v *String) UnmarshalXMLAttr(attr xml.Attr) error { 287 | v.value = attr.Value 288 | return nil 289 | } 290 | 291 | // MarshalXMLAttr implements xml.MarshalerAttr. 292 | func (v String) MarshalXMLAttr(name xml.Name) (xml.Attr, error) { 293 | return xml.Attr{ 294 | Name: name, 295 | Value: v.value, 296 | }, nil 297 | } 298 | 299 | // String returns the string value. 300 | // It resolves the reference if needed. 301 | func (v String) String() (string, error) { 302 | if !IsResID(v.value) { 303 | return v.value, nil 304 | } 305 | id, err := ParseResID(v.value) 306 | if err != nil { 307 | return "", err 308 | } 309 | value, err := v.table.GetResource(id, v.config) 310 | if err != nil { 311 | return "", err 312 | } 313 | ret, ok := value.(string) 314 | if !ok { 315 | return "", fmt.Errorf("invalid type: %T", value) 316 | } 317 | return ret, nil 318 | } 319 | 320 | // MustString is same as String, but it panics if it fails to parse the value. 321 | func (v String) MustString() string { 322 | ret, err := v.String() 323 | if err != nil { 324 | panic(err) 325 | } 326 | return ret 327 | } 328 | -------------------------------------------------------------------------------- /type_test.go: -------------------------------------------------------------------------------- 1 | package androidbinary 2 | 3 | import ( 4 | "encoding/xml" 5 | "os" 6 | "testing" 7 | ) 8 | 9 | func TestBool(t *testing.T) { 10 | type myMetaData struct { 11 | Name string `xml:"http://schemas.android.com/apk/res/android name,attr"` 12 | Value Bool `xml:"http://schemas.android.com/apk/res/android value,attr"` 13 | } 14 | type myXMLApplication struct { 15 | XMLName xml.Name `xml:"application"` 16 | MetaData []myMetaData `xml:"meta-data"` 17 | } 18 | type myXMLManifest struct { 19 | XMLName xml.Name `xml:"manifest"` 20 | Application myXMLApplication `xml:"application"` 21 | } 22 | 23 | f, err := os.Open("testdata/MyApplication/AndroidManifest.xml") 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | xmlFile, err := NewXMLFile(f) 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | 32 | arscFile, err := os.Open("testdata/MyApplication/resources.arsc") 33 | if err != nil { 34 | t.Fatal(err) 35 | } 36 | arsc, err := NewTableFile(arscFile) 37 | if err != nil { 38 | t.Fatal(err) 39 | } 40 | 41 | xmlManifest := new(myXMLManifest) 42 | err = xmlFile.Decode(xmlManifest, arsc, nil) 43 | if err != nil { 44 | t.Errorf("got %v want no error", err) 45 | } 46 | 47 | for _, data := range xmlManifest.Application.MetaData { 48 | switch data.Name { 49 | case "bool_test_true": 50 | v, err := data.Value.Bool() 51 | if err != nil { 52 | t.Error(err) 53 | } 54 | if !v { 55 | t.Errorf("unexpected value: %v", v) 56 | } 57 | case "bool_test_false": 58 | v, err := data.Value.Bool() 59 | if err != nil { 60 | t.Error(err) 61 | } 62 | if v { 63 | t.Errorf("unexpected value: %v", v) 64 | } 65 | case "bool_test_true_arsc": 66 | v, err := data.Value.Bool() 67 | if err != nil { 68 | t.Error(err) 69 | } 70 | if !v { 71 | t.Errorf("unexpected value: %v", v) 72 | } 73 | case "bool_test_false_arsc": 74 | v, err := data.Value.Bool() 75 | if err != nil { 76 | t.Error(err) 77 | } 78 | if v { 79 | t.Errorf("unexpected value: %v", v) 80 | } 81 | } 82 | } 83 | } 84 | 85 | func TestInt32(t *testing.T) { 86 | type myMetaData struct { 87 | Name string `xml:"http://schemas.android.com/apk/res/android name,attr"` 88 | Value Int32 `xml:"http://schemas.android.com/apk/res/android value,attr"` 89 | } 90 | type myXMLApplication struct { 91 | XMLName xml.Name `xml:"application"` 92 | MetaData []myMetaData `xml:"meta-data"` 93 | } 94 | type myXMLManifest struct { 95 | XMLName xml.Name `xml:"manifest"` 96 | Application myXMLApplication `xml:"application"` 97 | } 98 | 99 | f, err := os.Open("testdata/MyApplication/AndroidManifest.xml") 100 | if err != nil { 101 | t.Fatal(err) 102 | } 103 | xmlFile, err := NewXMLFile(f) 104 | if err != nil { 105 | t.Fatal(err) 106 | } 107 | 108 | arscFile, err := os.Open("testdata/MyApplication/resources.arsc") 109 | if err != nil { 110 | t.Fatal(err) 111 | } 112 | arsc, err := NewTableFile(arscFile) 113 | if err != nil { 114 | t.Fatal(err) 115 | } 116 | 117 | xmlManifest := new(myXMLManifest) 118 | err = xmlFile.Decode(xmlManifest, arsc, nil) 119 | if err != nil { 120 | t.Errorf("got %v want no error", err) 121 | } 122 | 123 | for _, data := range xmlManifest.Application.MetaData { 124 | switch data.Name { 125 | case "int_test": 126 | v, err := data.Value.Int32() 127 | if err != nil { 128 | t.Error(err) 129 | } 130 | if v != 42 { 131 | t.Errorf("unexpected value: %v", v) 132 | } 133 | case "int_test_arsc": 134 | v, err := data.Value.Int32() 135 | if err != nil { 136 | t.Error(err) 137 | } 138 | if v != -42 { 139 | t.Errorf("unexpected value: %v", v) 140 | } 141 | } 142 | } 143 | } 144 | 145 | func TestString(t *testing.T) { 146 | type myMetaData struct { 147 | Name string `xml:"http://schemas.android.com/apk/res/android name,attr"` 148 | Value String `xml:"http://schemas.android.com/apk/res/android value,attr"` 149 | } 150 | type myXMLApplication struct { 151 | XMLName xml.Name `xml:"application"` 152 | MetaData []myMetaData `xml:"meta-data"` 153 | } 154 | type myXMLManifest struct { 155 | XMLName xml.Name `xml:"manifest"` 156 | Application myXMLApplication `xml:"application"` 157 | } 158 | 159 | f, err := os.Open("testdata/MyApplication/AndroidManifest.xml") 160 | if err != nil { 161 | t.Fatal(err) 162 | } 163 | xmlFile, err := NewXMLFile(f) 164 | if err != nil { 165 | t.Fatal(err) 166 | } 167 | 168 | arscFile, err := os.Open("testdata/MyApplication/resources.arsc") 169 | if err != nil { 170 | t.Fatal(err) 171 | } 172 | arsc, err := NewTableFile(arscFile) 173 | if err != nil { 174 | t.Fatal(err) 175 | } 176 | 177 | xmlManifest := new(myXMLManifest) 178 | err = xmlFile.Decode(xmlManifest, arsc, nil) 179 | if err != nil { 180 | t.Errorf("got %v want no error", err) 181 | } 182 | 183 | for _, data := range xmlManifest.Application.MetaData { 184 | switch data.Name { 185 | case "string_test": 186 | v, err := data.Value.String() 187 | if err != nil { 188 | t.Error(err) 189 | } 190 | if v != "hogefuga" { 191 | t.Errorf("unexpected value: %v", v) 192 | } 193 | case "string_test_arsc": 194 | v, err := data.Value.String() 195 | if err != nil { 196 | t.Error(err) 197 | } 198 | if v != "foobar" { 199 | t.Errorf("unexpected value: %v", v) 200 | } 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /xml.go: -------------------------------------------------------------------------------- 1 | package androidbinary 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "encoding/xml" 7 | "fmt" 8 | "io" 9 | "reflect" 10 | ) 11 | 12 | // XMLFile is an XML file expressed in binary format. 13 | type XMLFile struct { 14 | stringPool *ResStringPool 15 | notPrecessedNS map[ResStringPoolRef]ResStringPoolRef 16 | namespaces xmlNamespaces 17 | xmlBuffer bytes.Buffer 18 | } 19 | 20 | type InvalidReferenceError struct { 21 | Ref ResStringPoolRef 22 | } 23 | 24 | func (e *InvalidReferenceError) Error() string { 25 | return fmt.Sprintf("androidbinary: invalid reference: 0x%08X", e.Ref) 26 | } 27 | 28 | type ( 29 | xmlNamespaces struct { 30 | l []namespaceVal 31 | } 32 | namespaceVal struct { 33 | key ResStringPoolRef 34 | value ResStringPoolRef 35 | } 36 | ) 37 | 38 | func (x *xmlNamespaces) add(key ResStringPoolRef, value ResStringPoolRef) { 39 | x.l = append(x.l, namespaceVal{key: key, value: value}) 40 | } 41 | 42 | func (x *xmlNamespaces) remove(key ResStringPoolRef) { 43 | for i := len(x.l) - 1; i >= 0; i-- { 44 | if x.l[i].key == key { 45 | var newList = append(x.l[:i], x.l[i+1:]...) 46 | x.l = newList 47 | return 48 | } 49 | } 50 | } 51 | 52 | func (x *xmlNamespaces) get(key ResStringPoolRef) ResStringPoolRef { 53 | for i := len(x.l) - 1; i >= 0; i-- { 54 | if x.l[i].key == key { 55 | return x.l[i].value 56 | } 57 | } 58 | return ResStringPoolRef(0) 59 | } 60 | 61 | // ResXMLTreeNode is basic XML tree node. 62 | type ResXMLTreeNode struct { 63 | Header ResChunkHeader 64 | LineNumber uint32 65 | Comment ResStringPoolRef 66 | } 67 | 68 | // ResXMLTreeNamespaceExt is extended XML tree node for namespace start/end nodes. 69 | type ResXMLTreeNamespaceExt struct { 70 | Prefix ResStringPoolRef 71 | URI ResStringPoolRef 72 | } 73 | 74 | // ResXMLTreeAttrExt is extended XML tree node for start tags -- includes attribute. 75 | type ResXMLTreeAttrExt struct { 76 | NS ResStringPoolRef 77 | Name ResStringPoolRef 78 | AttributeStart uint16 79 | AttributeSize uint16 80 | AttributeCount uint16 81 | IDIndex uint16 82 | ClassIndex uint16 83 | StyleIndex uint16 84 | } 85 | 86 | // ResXMLTreeAttribute is an attribute of start tags. 87 | type ResXMLTreeAttribute struct { 88 | NS ResStringPoolRef 89 | Name ResStringPoolRef 90 | RawValue ResStringPoolRef 91 | TypedValue ResValue 92 | } 93 | 94 | // ResXMLTreeEndElementExt is extended XML tree node for element start/end nodes. 95 | type ResXMLTreeEndElementExt struct { 96 | NS ResStringPoolRef 97 | Name ResStringPoolRef 98 | } 99 | 100 | // NewXMLFile returns a new XMLFile. 101 | func NewXMLFile(r io.ReaderAt) (*XMLFile, error) { 102 | f := new(XMLFile) 103 | sr := io.NewSectionReader(r, 0, 1<<63-1) 104 | 105 | fmt.Fprintf(&f.xmlBuffer, xml.Header) 106 | 107 | header := new(ResChunkHeader) 108 | if err := binary.Read(sr, binary.LittleEndian, header); err != nil { 109 | return nil, err 110 | } 111 | offset := int64(header.HeaderSize) 112 | for offset < int64(header.Size) { 113 | chunkHeader, err := f.readChunk(r, offset) 114 | if err != nil { 115 | return nil, err 116 | } 117 | offset += int64(chunkHeader.Size) 118 | } 119 | return f, nil 120 | } 121 | 122 | // Reader returns a reader of XML file expressed in text format. 123 | func (f *XMLFile) Reader() *bytes.Reader { 124 | return bytes.NewReader(f.xmlBuffer.Bytes()) 125 | } 126 | 127 | // Decode decodes XML file and stores the result in the value pointed to by v. 128 | // To resolve the resource references, Decode also stores default TableFile and ResTableConfig in the value pointed to by v. 129 | func (f *XMLFile) Decode(v interface{}, table *TableFile, config *ResTableConfig) error { 130 | decoder := xml.NewDecoder(f.Reader()) 131 | if err := decoder.Decode(v); err != nil { 132 | return err 133 | } 134 | inject(reflect.ValueOf(v), table, config) 135 | return nil 136 | } 137 | 138 | func (f *XMLFile) readChunk(r io.ReaderAt, offset int64) (*ResChunkHeader, error) { 139 | sr := io.NewSectionReader(r, offset, 1<<63-1-offset) 140 | chunkHeader := &ResChunkHeader{} 141 | if _, err := sr.Seek(0, io.SeekStart); err != nil { 142 | return nil, err 143 | } 144 | if err := binary.Read(sr, binary.LittleEndian, chunkHeader); err != nil { 145 | return nil, err 146 | } 147 | if chunkHeader.HeaderSize < uint16(binary.Size(chunkHeader)) { 148 | return nil, fmt.Errorf("androidbinary: invalid chunk header size: %d", chunkHeader.HeaderSize) 149 | } 150 | if chunkHeader.Size < uint32(chunkHeader.HeaderSize) { 151 | return nil, fmt.Errorf("androidbinary: invalid chunk size: %d", chunkHeader.Size) 152 | } 153 | 154 | var err error 155 | if _, err := sr.Seek(0, io.SeekStart); err != nil { 156 | return nil, err 157 | } 158 | switch chunkHeader.Type { 159 | case ResStringPoolChunkType: 160 | f.stringPool, err = readStringPool(sr) 161 | case ResXMLStartNamespaceType: 162 | err = f.readStartNamespace(sr) 163 | case ResXMLEndNamespaceType: 164 | err = f.readEndNamespace(sr) 165 | case ResXMLStartElementType: 166 | err = f.readStartElement(sr) 167 | case ResXMLEndElementType: 168 | err = f.readEndElement(sr) 169 | } 170 | if err != nil { 171 | return nil, err 172 | } 173 | 174 | return chunkHeader, nil 175 | } 176 | 177 | // GetString returns a string referenced by ref. 178 | // It panics if the pool doesn't contain ref. 179 | func (f *XMLFile) GetString(ref ResStringPoolRef) string { 180 | return f.stringPool.GetString(ref) 181 | } 182 | 183 | func (f *XMLFile) HasString(ref ResStringPoolRef) bool { 184 | return f.stringPool.HasString(ref) 185 | } 186 | 187 | func (f *XMLFile) readStartNamespace(sr *io.SectionReader) error { 188 | header := new(ResXMLTreeNode) 189 | if err := binary.Read(sr, binary.LittleEndian, header); err != nil { 190 | return err 191 | } 192 | 193 | if _, err := sr.Seek(int64(header.Header.HeaderSize), io.SeekStart); err != nil { 194 | return err 195 | } 196 | namespace := new(ResXMLTreeNamespaceExt) 197 | if err := binary.Read(sr, binary.LittleEndian, namespace); err != nil { 198 | return err 199 | } 200 | 201 | if f.notPrecessedNS == nil { 202 | f.notPrecessedNS = make(map[ResStringPoolRef]ResStringPoolRef) 203 | } 204 | f.notPrecessedNS[namespace.URI] = namespace.Prefix 205 | f.namespaces.add(namespace.URI, namespace.Prefix) 206 | return nil 207 | } 208 | 209 | func (f *XMLFile) readEndNamespace(sr *io.SectionReader) error { 210 | header := new(ResXMLTreeNode) 211 | if err := binary.Read(sr, binary.LittleEndian, header); err != nil { 212 | return err 213 | } 214 | 215 | if _, err := sr.Seek(int64(header.Header.HeaderSize), io.SeekStart); err != nil { 216 | return err 217 | } 218 | namespace := new(ResXMLTreeNamespaceExt) 219 | if err := binary.Read(sr, binary.LittleEndian, namespace); err != nil { 220 | return err 221 | } 222 | f.namespaces.remove(namespace.URI) 223 | return nil 224 | } 225 | 226 | func (f *XMLFile) addNamespacePrefix(ns, name ResStringPoolRef) (string, error) { 227 | if !f.HasString(name) { 228 | return "", &InvalidReferenceError{Ref: name} 229 | } 230 | 231 | if ns != NilResStringPoolRef { 232 | ref := f.namespaces.get(ns) 233 | if ref == 0 { 234 | return "", &InvalidReferenceError{Ref: ns} 235 | } 236 | if !f.HasString(ref) { 237 | return "", &InvalidReferenceError{Ref: ref} 238 | } 239 | prefix := f.GetString(ref) 240 | 241 | return fmt.Sprintf("%s:%s", prefix, f.GetString(name)), nil 242 | } 243 | return f.GetString(name), nil 244 | } 245 | 246 | func (f *XMLFile) readStartElement(sr *io.SectionReader) error { 247 | header := new(ResXMLTreeNode) 248 | if err := binary.Read(sr, binary.LittleEndian, header); err != nil { 249 | return err 250 | } 251 | 252 | if _, err := sr.Seek(int64(header.Header.HeaderSize), io.SeekStart); err != nil { 253 | return err 254 | } 255 | ext := new(ResXMLTreeAttrExt) 256 | if err := binary.Read(sr, binary.LittleEndian, ext); err != nil { 257 | return nil 258 | } 259 | 260 | tag, err := f.addNamespacePrefix(ext.NS, ext.Name) 261 | if err != nil { 262 | return err 263 | } 264 | f.xmlBuffer.WriteString("<") 265 | f.xmlBuffer.WriteString(tag) 266 | 267 | // output XML namespaces 268 | if f.notPrecessedNS != nil { 269 | for uri, prefix := range f.notPrecessedNS { 270 | if !f.HasString(uri) { 271 | return &InvalidReferenceError{Ref: uri} 272 | } 273 | if !f.HasString(prefix) { 274 | return &InvalidReferenceError{Ref: prefix} 275 | } 276 | fmt.Fprintf(&f.xmlBuffer, " xmlns:%s=\"", f.GetString(prefix)) 277 | xml.Escape(&f.xmlBuffer, []byte(f.GetString(uri))) 278 | fmt.Fprint(&f.xmlBuffer, "\"") 279 | } 280 | f.notPrecessedNS = nil 281 | } 282 | 283 | // process attributes 284 | offset := int64(ext.AttributeStart + header.Header.HeaderSize) 285 | for i := 0; i < int(ext.AttributeCount); i++ { 286 | if _, err := sr.Seek(offset, io.SeekStart); err != nil { 287 | return err 288 | } 289 | attr := new(ResXMLTreeAttribute) 290 | binary.Read(sr, binary.LittleEndian, attr) 291 | 292 | var value string 293 | if attr.RawValue != NilResStringPoolRef { 294 | if !f.HasString(attr.RawValue) { 295 | return &InvalidReferenceError{Ref: attr.RawValue} 296 | } 297 | value = f.GetString(attr.RawValue) 298 | } else { 299 | data := attr.TypedValue.Data 300 | switch attr.TypedValue.DataType { 301 | case TypeNull: 302 | value = "" 303 | case TypeReference: 304 | value = fmt.Sprintf("@0x%08X", data) 305 | case TypeIntDec: 306 | value = fmt.Sprintf("%d", data) 307 | case TypeIntHex: 308 | value = fmt.Sprintf("0x%08X", data) 309 | case TypeIntBoolean: 310 | if data != 0 { 311 | value = "true" 312 | } else { 313 | value = "false" 314 | } 315 | default: 316 | value = fmt.Sprintf("@0x%08X", data) 317 | } 318 | } 319 | 320 | name, err := f.addNamespacePrefix(attr.NS, attr.Name) 321 | if err != nil { 322 | return err 323 | } 324 | fmt.Fprintf(&f.xmlBuffer, " %s=\"", name) 325 | xml.Escape(&f.xmlBuffer, []byte(value)) 326 | fmt.Fprint(&f.xmlBuffer, "\"") 327 | offset += int64(ext.AttributeSize) 328 | } 329 | fmt.Fprint(&f.xmlBuffer, ">") 330 | return nil 331 | } 332 | 333 | func (f *XMLFile) readEndElement(sr *io.SectionReader) error { 334 | header := new(ResXMLTreeNode) 335 | if err := binary.Read(sr, binary.LittleEndian, header); err != nil { 336 | return err 337 | } 338 | if _, err := sr.Seek(int64(header.Header.HeaderSize), io.SeekStart); err != nil { 339 | return err 340 | } 341 | ext := new(ResXMLTreeEndElementExt) 342 | if err := binary.Read(sr, binary.LittleEndian, ext); err != nil { 343 | return err 344 | } 345 | tag, err := f.addNamespacePrefix(ext.NS, ext.Name) 346 | if err != nil { 347 | return err 348 | } 349 | fmt.Fprintf(&f.xmlBuffer, "", tag) 350 | return nil 351 | } 352 | -------------------------------------------------------------------------------- /xml_test.go: -------------------------------------------------------------------------------- 1 | package androidbinary 2 | 3 | import ( 4 | "bytes" 5 | "encoding/xml" 6 | "io" 7 | "os" 8 | "reflect" 9 | "testing" 10 | ) 11 | 12 | type XMLManifest struct { 13 | XMLName xml.Name `xml:"manifest"` 14 | VersionName string `xml:"http://schemas.android.com/apk/res/android versionName,attr"` 15 | VersionCode string `xml:"http://schemas.android.com/apk/res/android versionCode,attr"` 16 | Package string `xml:"package,attr"` 17 | UsesPermissions []*XMLUsesPermission `xml:"uses-permission"` 18 | Applications []*XMLApplication `xml:"application"` 19 | } 20 | 21 | type XMLUsesPermission struct { 22 | XMLName xml.Name `xml:"uses-permission"` 23 | Name string `xml:"http://schemas.android.com/apk/res/android name,attr"` 24 | } 25 | 26 | type XMLApplication struct { 27 | XMLName xml.Name `xml:"application"` 28 | Label string `xml:"http://schemas.android.com/apk/res/android label,attr"` 29 | Icon string `xml:"http://schemas.android.com/apk/res/android icon,attr"` 30 | Debuggable string `xml:"http://schemas.android.com/apk/res/android debuggable,attr"` 31 | UsesLibraries []*XMLUsesLibrary `xml:"uses-library"` 32 | Activities []*XMLActivity `xml:"activity"` 33 | } 34 | 35 | type XMLUsesLibrary struct { 36 | XMLName xml.Name `xml:"uses-library"` 37 | Name string `xml:"http://schemas.android.com/apk/res/android name,attr"` 38 | } 39 | 40 | type XMLActivity struct { 41 | XMLName xml.Name `xml:"activity"` 42 | Name string `xml:"http://schemas.android.com/apk/res/android name,attr"` 43 | ScreenOrientation string `xml:"http://schemas.android.com/apk/res/android screenOrientation,attr"` 44 | } 45 | 46 | func TestNewXMLFile(t *testing.T) { 47 | f, _ := os.Open("testdata/AndroidManifest.xml") 48 | xmlFile, err := NewXMLFile(f) 49 | if err != nil { 50 | t.Errorf("got %v want no error", err) 51 | } 52 | decoder := xml.NewDecoder(xmlFile.Reader()) 53 | xmlManifest := &XMLManifest{} 54 | err = decoder.Decode(xmlManifest) 55 | if err != nil { 56 | t.Errorf("got %v want no error", err) 57 | } 58 | expected := &XMLManifest{ 59 | XMLName: xml.Name{Local: "manifest"}, 60 | VersionName: "テスト版", 61 | VersionCode: "1", 62 | Package: "net.sorablue.shogo.FWMeasure", 63 | UsesPermissions: []*XMLUsesPermission{ 64 | {XMLName: xml.Name{Local: "uses-permission"}, Name: "android.permission.CAMERA"}, 65 | {XMLName: xml.Name{Local: "uses-permission"}, Name: "android.permission.WAKE_LOCK"}, 66 | {XMLName: xml.Name{Local: "uses-permission"}, Name: "android.permission.ACCESS_FINE_LOCATION"}, 67 | {XMLName: xml.Name{Local: "uses-permission"}, Name: "android.permission.INTERNET"}, 68 | {XMLName: xml.Name{Local: "uses-permission"}, Name: "android.permission.ACCESS_MOCK_LOCATION"}, 69 | {XMLName: xml.Name{Local: "uses-permission"}, Name: "android.permission.RECORD_AUDIO"}, 70 | }, 71 | Applications: []*XMLApplication{ 72 | { 73 | XMLName: xml.Name{Local: "application"}, 74 | Label: "@0x7F040000", 75 | Icon: "@0x7F020000", 76 | Debuggable: "false", 77 | UsesLibraries: []*XMLUsesLibrary{ 78 | {XMLName: xml.Name{Local: "uses-library"}, Name: "com.google.android.maps"}, 79 | }, 80 | Activities: []*XMLActivity{ 81 | { 82 | XMLName: xml.Name{Local: "activity"}, 83 | ScreenOrientation: "0", 84 | Name: "FWMeasureActivity", 85 | }, 86 | { 87 | XMLName: xml.Name{Local: "activity"}, 88 | ScreenOrientation: "0", 89 | Name: "MapActivity", 90 | }, 91 | { 92 | XMLName: xml.Name{Local: "activity"}, 93 | Name: "SettingActivity", 94 | }, 95 | { 96 | XMLName: xml.Name{Local: "activity"}, 97 | Name: "PlaceSettingActivity", 98 | }, 99 | }, 100 | }, 101 | }, 102 | } 103 | if !reflect.DeepEqual(xmlManifest, expected) { 104 | t.Errorf("got %v want %v", xmlManifest, expected) 105 | } 106 | } 107 | 108 | func TestReadStartNamespace(t *testing.T) { 109 | input := []uint8{ 110 | 0x00, 0x01, // Type = RES_XML_START_NAMESPACE_TYPE 111 | 0x10, 0x00, // HeadSize = 16 bytes 112 | 0x18, 0x00, 0x00, 0x00, // Size 113 | 0x01, 0x00, 0x00, 0x00, // LineNumber = 1 114 | 0xFF, 0xFF, 0xFF, 0xFF, // Comment is none 115 | 0x02, 0x00, 0x00, 0x00, // Prefix = 2 116 | 0x01, 0x00, 0x00, 0x00, // Uri = 1 117 | } 118 | buf := bytes.NewReader(input) 119 | sr := io.NewSectionReader(buf, 0, int64(len(input))) 120 | f := new(XMLFile) 121 | err := f.readStartNamespace(sr) 122 | if err != nil { 123 | t.Errorf("got %v want no error", err) 124 | } 125 | if f.notPrecessedNS[ResStringPoolRef(1)] != ResStringPoolRef(2) { 126 | t.Errorf("got %v want %v", f.notPrecessedNS[ResStringPoolRef(1)], ResStringPoolRef(2)) 127 | } 128 | if f.namespaces.get(ResStringPoolRef(1)) != ResStringPoolRef(2) { 129 | t.Errorf("got %v want %v", f.namespaces.get(ResStringPoolRef(1)), ResStringPoolRef(2)) 130 | } 131 | } 132 | 133 | func TestReadEndNamespace(t *testing.T) { 134 | input := []uint8{ 135 | 0x01, 0x01, // Type = RES_XML_END_NAMESPACE_TYPE 136 | 0x10, 0x00, // HeadSize = 16 bytes 137 | 0x18, 0x00, 0x00, 0x00, // Size 138 | 0x01, 0x00, 0x00, 0x00, // LineNumber = 1 139 | 0xFF, 0xFF, 0xFF, 0xFF, // Comment is none 140 | 0x02, 0x00, 0x00, 0x00, // Prefix = 2 141 | 0x01, 0x00, 0x00, 0x00, // Uri = 1 142 | } 143 | buf := bytes.NewReader(input) 144 | sr := io.NewSectionReader(buf, 0, int64(len(input))) 145 | 146 | f := new(XMLFile) 147 | f.namespaces.add(ResStringPoolRef(1), ResStringPoolRef(2)) 148 | err := f.readEndNamespace(sr) 149 | 150 | if err != nil { 151 | t.Errorf("got %v want no error", err) 152 | } 153 | } 154 | 155 | func TestAddNamespacePrefix(t *testing.T) { 156 | nameRef := ResStringPoolRef(1) 157 | prefixRef := ResStringPoolRef(2) 158 | uriRef := ResStringPoolRef(3) 159 | 160 | f := new(XMLFile) 161 | f.namespaces.add(uriRef, prefixRef) 162 | f.stringPool = new(ResStringPool) 163 | f.stringPool.Strings = []string{"", "name", "prefix", "http://example.com"} 164 | 165 | got, err := f.addNamespacePrefix(NilResStringPoolRef, nameRef) 166 | if err != nil { 167 | t.Errorf("got %v want no error", err) 168 | } 169 | if got != "name" { 170 | t.Errorf("got %v want name", got) 171 | } 172 | 173 | got, err = f.addNamespacePrefix(uriRef, nameRef) 174 | if err != nil { 175 | t.Errorf("got %v want no error", err) 176 | } 177 | if got != "prefix:name" { 178 | t.Errorf("got %v want prefix:name", got) 179 | } 180 | } 181 | 182 | func TestReadStartElement(t *testing.T) { 183 | input := []uint8{ 184 | 0x02, 0x01, // Type = RES_XML_START_ELEMENT_TYPE 185 | 0x10, 0x00, // HeadSize = 16 bytes 186 | 0x3C, 0x00, 0x00, 0x00, // Size = 60 bytes 187 | 0x01, 0x00, 0x00, 0x00, // LineNumber = 1 188 | 0xFF, 0xFF, 0xFF, 0xFF, // Comment is none 189 | 0xFF, 0xFF, 0xFF, 0xFF, // Namespace is none 190 | 0x01, 0x00, 0x00, 0x00, // Name = 1 191 | 0x14, 0x00, // AttributeStart = 20 bytes 192 | 0x14, 0x00, // AttributeSize = 20 bytes 193 | 0x01, 0x00, // AttributeCount = 1 194 | 0x00, 0x00, // IdIndex = 0 195 | 0x00, 0x00, // ClassIndex = 0 196 | 0x00, 0x00, // StyleIndex = 0 197 | 198 | // Attributes 199 | 0xFF, 0xFF, 0xFF, 0xFF, // Namespace is none 200 | 0x04, 0x00, 0x00, 0x00, // Name is 'attr' 201 | 0x05, 0x00, 0x00, 0x00, // RawValue is 'value' 202 | 0x08, 0x00, // size = 8 203 | 0x00, // padding 204 | 0x03, // data type is TYPE_STRING 205 | 0x05, 0x00, 0x00, 0x00, // data 206 | } 207 | buf := bytes.NewReader(input) 208 | sr := io.NewSectionReader(buf, 0, int64(len(input))) 209 | 210 | prefixRef := ResStringPoolRef(2) 211 | uriRef := ResStringPoolRef(3) 212 | 213 | f := new(XMLFile) 214 | f.notPrecessedNS = make(map[ResStringPoolRef]ResStringPoolRef) 215 | f.notPrecessedNS[uriRef] = prefixRef 216 | f.namespaces.add(uriRef, prefixRef) 217 | f.stringPool = new(ResStringPool) 218 | f.stringPool.Strings = []string{"", "name", "prefix", "http://example.com", "attr", "value"} 219 | err := f.readStartElement(sr) 220 | 221 | if err != nil { 222 | t.Errorf("got %v want no error", err) 223 | } 224 | 225 | if actual := f.xmlBuffer.String(); actual != "" { 226 | t.Errorf("got %v want ", actual) 227 | } 228 | } 229 | 230 | func TestReadEndElement(t *testing.T) { 231 | input := []uint8{ 232 | 0x03, 0x01, // Type = RES_XML_END_ELEMENT_TYPE 233 | 0x10, 0x00, // HeadSize = 16 bytes 234 | 0x18, 0x00, 0x00, 0x00, // Size 235 | 0x01, 0x00, 0x00, 0x00, // LineNumber = 1 236 | 0xFF, 0xFF, 0xFF, 0xFF, // Comment is none 237 | 0xFF, 0xFF, 0xFF, 0xFF, // Namespace is none 238 | 0x01, 0x00, 0x00, 0x00, // Name = 1 239 | } 240 | buf := bytes.NewReader(input) 241 | sr := io.NewSectionReader(buf, 0, int64(len(input))) 242 | 243 | f := new(XMLFile) 244 | f.stringPool = new(ResStringPool) 245 | f.stringPool.Strings = []string{"", "name"} 246 | err := f.readEndElement(sr) 247 | 248 | if err != nil { 249 | t.Errorf("got %v want no error", err) 250 | } 251 | 252 | if actual := f.xmlBuffer.String(); actual != "" { 253 | t.Errorf("got %v want ", actual) 254 | } 255 | } 256 | --------------------------------------------------------------------------------