├── .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 | [](https://github.com/shogo82148/androidbinary/actions)
5 | [](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, "%s>", 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 |
--------------------------------------------------------------------------------