apkbox 2.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- apkbox-2.1.0/PKG-INFO +174 -0
- apkbox-2.1.0/README.md +167 -0
- apkbox-2.1.0/apkbox/__init__.py +23 -0
- apkbox-2.1.0/apkbox/__main__.py +5 -0
- apkbox-2.1.0/apkbox/cli.py +209 -0
- apkbox-2.1.0/apkbox/core.py +774 -0
- apkbox-2.1.0/apkbox/decompile.py +139 -0
- apkbox-2.1.0/apkbox/errors.py +3 -0
- apkbox-2.1.0/apkbox/fetch.py +315 -0
- apkbox-2.1.0/apkbox/i18n.py +286 -0
- apkbox-2.1.0/apkbox/locate.py +291 -0
- apkbox-2.1.0/apkbox/util.py +42 -0
- apkbox-2.1.0/apkbox/version.py +1 -0
- apkbox-2.1.0/apkbox.egg-info/PKG-INFO +174 -0
- apkbox-2.1.0/apkbox.egg-info/SOURCES.txt +18 -0
- apkbox-2.1.0/apkbox.egg-info/dependency_links.txt +1 -0
- apkbox-2.1.0/apkbox.egg-info/entry_points.txt +2 -0
- apkbox-2.1.0/apkbox.egg-info/top_level.txt +1 -0
- apkbox-2.1.0/pyproject.toml +20 -0
- apkbox-2.1.0/setup.cfg +4 -0
apkbox-2.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: apkbox
|
|
3
|
+
Version: 2.1.0
|
|
4
|
+
Summary: APK 工具集: 转成可引入的 AAR, 或反编译出 .java(工具自动下载)
|
|
5
|
+
Requires-Python: >=3.9
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
|
|
8
|
+
# apkbox
|
|
9
|
+
|
|
10
|
+
APK 工具箱:把 APK 转成能直接引入工程的 AAR,或反编译出 `.java`。
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
apkbox app.apk # app.apk → app.aar(aar 是默认子命令)
|
|
14
|
+
apkbox java app.apk -o src/ # 整个 APK 反编译成 .java
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## 依赖
|
|
18
|
+
|
|
19
|
+
- Python 3.9+
|
|
20
|
+
- JDK 8+(`java` 要在 `PATH` 里)
|
|
21
|
+
- 可选:Android SDK —— 提供 `android.jar` 做 AAR 的资源 link 自检,也是 aapt2 的首选来源(见下文)
|
|
22
|
+
|
|
23
|
+
就这些。wheel 只有约 40 KB、Python 侧无第三方依赖;apktool / dex-tools / aapt2 /
|
|
24
|
+
jadx 这些工具**不在包里**,系统装过就直接用,没有的话首次用到时自动下载到缓存
|
|
25
|
+
(约 110 MB,一次性)。
|
|
26
|
+
|
|
27
|
+
## 安装
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install apkbox # 从 PyPI 装
|
|
31
|
+
|
|
32
|
+
# 或从源码 / wheel 装
|
|
33
|
+
pip install --break-system-packages .
|
|
34
|
+
pip install --break-system-packages dist/apkbox-2.1.0-py3-none-any.whl
|
|
35
|
+
|
|
36
|
+
# 不想安装,直接在源码目录跑
|
|
37
|
+
python3 -m apkbox --version
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
> Debian/Ubuntu 的系统 Python 是 externally-managed,所以要加 `--break-system-packages`;
|
|
41
|
+
> 在 venv 里装则不需要这个参数。
|
|
42
|
+
|
|
43
|
+
## 工具从哪来
|
|
44
|
+
|
|
45
|
+
每个工具按这个顺序解析,**找到就用,不重复造轮子**:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
命令行参数 > 环境变量 > 系统 PATH / Android SDK > apkbox 缓存 > 自动下载
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- 缓存目录默认 `~/.cache/apkbox`(遵循 `XDG_CACHE_HOME`),下载前先比对
|
|
52
|
+
sha256,装一次后续直接复用。
|
|
53
|
+
- 下载先走官方直链(GitHub releases、Google Maven),不通再换内置镜像;
|
|
54
|
+
设置 `APKBOX_MIRROR` 可以指定自己的镜像(此时镜像优先、直链兜底)。
|
|
55
|
+
- 首次运行会看到类似输出,之后都是 `✓`:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
tools : apktool ✓, dex2jar 已下载, aapt2 ✓
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- 想提前把工具备齐(离线机、CI 预热):
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
apkbox fetch # 全部:apktool / dex2jar / aapt2 / jadx
|
|
65
|
+
apkbox fetch jadx apktool # 指定几个
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
环境变量:
|
|
69
|
+
|
|
70
|
+
| 变量 | 作用 |
|
|
71
|
+
| --- | --- |
|
|
72
|
+
| `APKBOX_MIRROR` | 镜像前缀,逗号分隔,下载地址 = 前缀 + 官方 URL(与 gh-proxy.com 同规则);设为空则只走直链 |
|
|
73
|
+
| `APKBOX_OFFLINE=1` | 禁止联网,缺工具直接报错并给出手动下载地址 |
|
|
74
|
+
| `APKBOX_CACHE` | 缓存目录(默认 `~/.cache/apkbox`) |
|
|
75
|
+
| `APKBOX_NO_VERIFY=1` | 跳过 sha256 校验(慎用) |
|
|
76
|
+
| `APKBOX_APKTOOL` / `APKBOX_DEX2JAR` / `APKBOX_AAPT2` / `APKBOX_JADX` | 分别指定工具路径 |
|
|
77
|
+
| `ANDROID_HOME` / `ANDROID_SDK_ROOT` | 找 aapt2 和 android.jar |
|
|
78
|
+
| `APKBOX_LANG` | 界面语言 `zh` / `en` |
|
|
79
|
+
|
|
80
|
+
当前自动下载的版本(升级时改 `apkbox/fetch.py` 里的 `SPECS`):
|
|
81
|
+
|
|
82
|
+
| 工具 | 版本 | 来源 |
|
|
83
|
+
| --- | --- | --- |
|
|
84
|
+
| apktool | 2.7.0 | GitHub releases(单个 fat jar) |
|
|
85
|
+
| dex-tools | 2.4 | GitHub releases |
|
|
86
|
+
| jadx | 1.5.6 | GitHub releases |
|
|
87
|
+
| aapt2 | 9.4.1-15978811 | Google Maven(`dl.google.com`) |
|
|
88
|
+
|
|
89
|
+
## 转 AAR
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
apkbox app.apk # → app.aar(和输入同目录)
|
|
93
|
+
apkbox aar app.apk -o dist/app.aar --keep # 指定输出,保留中间文件
|
|
94
|
+
apkbox app.apk --code-only # 只要 classes.jar,不带 res/assets/jni
|
|
95
|
+
apkbox app.apk -w work/ --skip-decode # 复用 work/ 里已有的解包结果
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
流程:apktool 解包 → dex2jar 出 classes.jar → 组装 res/assets/jni(`public.xml` 钉死资源 id)→ 裁剪 AndroidManifest → aapt2 compile + link 自检 → 打包 `.aar`。
|
|
99
|
+
|
|
100
|
+
常用选项:
|
|
101
|
+
|
|
102
|
+
| 选项 | 说明 |
|
|
103
|
+
| --- | --- |
|
|
104
|
+
| `-o, --output` | 输出 `.aar` 路径(默认与 apk 同名) |
|
|
105
|
+
| `-w, --work-dir` | 中间文件目录(默认临时目录,指定后保留) |
|
|
106
|
+
| `--code-only` | 只要 classes.jar,不带 res/assets/jni |
|
|
107
|
+
| `--keep-app-attrs` | 保留 `<application>` 的属性(默认清空以免与宿主合并冲突) |
|
|
108
|
+
| `--skip-decode` | 复用 `-w` 目录里已有的 apktool 解包结果 |
|
|
109
|
+
| `--keep` | 保留临时目录 |
|
|
110
|
+
| `--apktool` / `--dex2jar` / `--aapt2` | 手动指定工具(默认按上面的顺序自动解析) |
|
|
111
|
+
| `--android-jar` | android.jar 路径(用于 link 自检,缺省自动找 SDK) |
|
|
112
|
+
| `-v, --verbose` | 打印每步实际执行的子命令 |
|
|
113
|
+
|
|
114
|
+
### 产物注意事项
|
|
115
|
+
|
|
116
|
+
- 打印的 `可能与宿主 Duplicate class: androidx …` 是提示:这些包宿主大概率已有,合并前先看一眼。
|
|
117
|
+
- `<application>` 的 `android:name/icon/label/theme` 默认被清空(避免和宿主合并冲突),图标、label、主题、Application 类要宿主自己设置;确实想带上用 `--keep-app-attrs`。
|
|
118
|
+
- 没找到 android.jar 时跳过 link 自检,`R.txt` 按原 APK 的资源 id 生成,`minCompileSdk` 取原 APK 的 `compileSdkVersion`;找到时会尝试各平台,取能通过的最低版本。
|
|
119
|
+
- native 库按 APK 原样携带:APK 里有哪些 ABI,AAR 的 `jni/` 就有哪些;只有 arm 库的话 x86 模拟器跑不起来(会打印警告)。
|
|
120
|
+
|
|
121
|
+
## 反编译成 .java
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
apkbox java app.apk -o src/ # APK → src/**/*.java + resources/
|
|
125
|
+
apkbox java classes.dex # 单个 dex → classes-java/
|
|
126
|
+
apkbox java ./smali_out/ -o src/ # 目录里挑 classes*.dex
|
|
127
|
+
apkbox java app.apk -r # --no-res:只要代码,不解资源
|
|
128
|
+
apkbox java app.apk -j 4 --deobf # 4 线程 + 反混淆
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
| 选项 | 说明 |
|
|
132
|
+
| --- | --- |
|
|
133
|
+
| `-o, --output` | 输出目录(默认 `<输入名>-java`) |
|
|
134
|
+
| `-r, --no-res` | 不反编译资源,只出 `.java` |
|
|
135
|
+
| `-j, --jobs` | 线程数(默认用 jadx 的) |
|
|
136
|
+
| `--deobf` | 反混淆:太短/太长的类名重新命名 |
|
|
137
|
+
| `-e, --export-gradle` | 输出成 Android/Java gradle 工程 |
|
|
138
|
+
| `--show-bad-code` | 反编译不出来的代码也照样输出(带注释标记) |
|
|
139
|
+
| `--jadx` | jadx 可执行文件/jar(默认自动解析/下载) |
|
|
140
|
+
|
|
141
|
+
- 有反编译错误时会在输出旁写一份 `<输出名>.log`(混淆过的 APK 出现几百处错误很常见)。
|
|
142
|
+
- 进度行只在终端里刷,重定向到文件时不会刷屏。
|
|
143
|
+
|
|
144
|
+
## 界面语言
|
|
145
|
+
|
|
146
|
+
按系统 locale 自动选中文/英文,也可以强制指定:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
apkbox --lang en java app.apk # --lang 放子命令前后都行
|
|
150
|
+
APKBOX_LANG=en apkbox app.apk # 环境变量
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Python API
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from apkbox import convert, decompile_to_java, ToolError
|
|
157
|
+
|
|
158
|
+
convert("app.apk") # → app.aar,返回退出码
|
|
159
|
+
convert("app.apk", "dist/app.aar", code_only=True) # 只要 classes.jar
|
|
160
|
+
info = decompile_to_java("app.apk", "src/") # 返回统计 dict
|
|
161
|
+
print(info["java_files"], info["errors"], info["log_file"])
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
参数与命令行同名(`convert` 的 `work_dir/code_only/keep/skip_decode/...`,
|
|
165
|
+
`decompile_to_java` 的 `no_res/deobf/jobs/show_bad_code/...`)。
|
|
166
|
+
工具级失败抛 `ToolError`;API 和命令行一样,缺工具会自动下载(`APKBOX_OFFLINE=1` 可禁止)。
|
|
167
|
+
|
|
168
|
+
## 退出码
|
|
169
|
+
|
|
170
|
+
| 码 | 含义 |
|
|
171
|
+
| --- | --- |
|
|
172
|
+
| 0 | 成功 |
|
|
173
|
+
| 1 | 工具执行失败(外部命令报错,已打印 `error: …`) |
|
|
174
|
+
| 2 | 参数/输入文件问题 |
|
apkbox-2.1.0/README.md
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# apkbox
|
|
2
|
+
|
|
3
|
+
APK 工具箱:把 APK 转成能直接引入工程的 AAR,或反编译出 `.java`。
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
apkbox app.apk # app.apk → app.aar(aar 是默认子命令)
|
|
7
|
+
apkbox java app.apk -o src/ # 整个 APK 反编译成 .java
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## 依赖
|
|
11
|
+
|
|
12
|
+
- Python 3.9+
|
|
13
|
+
- JDK 8+(`java` 要在 `PATH` 里)
|
|
14
|
+
- 可选:Android SDK —— 提供 `android.jar` 做 AAR 的资源 link 自检,也是 aapt2 的首选来源(见下文)
|
|
15
|
+
|
|
16
|
+
就这些。wheel 只有约 40 KB、Python 侧无第三方依赖;apktool / dex-tools / aapt2 /
|
|
17
|
+
jadx 这些工具**不在包里**,系统装过就直接用,没有的话首次用到时自动下载到缓存
|
|
18
|
+
(约 110 MB,一次性)。
|
|
19
|
+
|
|
20
|
+
## 安装
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install apkbox # 从 PyPI 装
|
|
24
|
+
|
|
25
|
+
# 或从源码 / wheel 装
|
|
26
|
+
pip install --break-system-packages .
|
|
27
|
+
pip install --break-system-packages dist/apkbox-2.1.0-py3-none-any.whl
|
|
28
|
+
|
|
29
|
+
# 不想安装,直接在源码目录跑
|
|
30
|
+
python3 -m apkbox --version
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
> Debian/Ubuntu 的系统 Python 是 externally-managed,所以要加 `--break-system-packages`;
|
|
34
|
+
> 在 venv 里装则不需要这个参数。
|
|
35
|
+
|
|
36
|
+
## 工具从哪来
|
|
37
|
+
|
|
38
|
+
每个工具按这个顺序解析,**找到就用,不重复造轮子**:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
命令行参数 > 环境变量 > 系统 PATH / Android SDK > apkbox 缓存 > 自动下载
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
- 缓存目录默认 `~/.cache/apkbox`(遵循 `XDG_CACHE_HOME`),下载前先比对
|
|
45
|
+
sha256,装一次后续直接复用。
|
|
46
|
+
- 下载先走官方直链(GitHub releases、Google Maven),不通再换内置镜像;
|
|
47
|
+
设置 `APKBOX_MIRROR` 可以指定自己的镜像(此时镜像优先、直链兜底)。
|
|
48
|
+
- 首次运行会看到类似输出,之后都是 `✓`:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
tools : apktool ✓, dex2jar 已下载, aapt2 ✓
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- 想提前把工具备齐(离线机、CI 预热):
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
apkbox fetch # 全部:apktool / dex2jar / aapt2 / jadx
|
|
58
|
+
apkbox fetch jadx apktool # 指定几个
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
环境变量:
|
|
62
|
+
|
|
63
|
+
| 变量 | 作用 |
|
|
64
|
+
| --- | --- |
|
|
65
|
+
| `APKBOX_MIRROR` | 镜像前缀,逗号分隔,下载地址 = 前缀 + 官方 URL(与 gh-proxy.com 同规则);设为空则只走直链 |
|
|
66
|
+
| `APKBOX_OFFLINE=1` | 禁止联网,缺工具直接报错并给出手动下载地址 |
|
|
67
|
+
| `APKBOX_CACHE` | 缓存目录(默认 `~/.cache/apkbox`) |
|
|
68
|
+
| `APKBOX_NO_VERIFY=1` | 跳过 sha256 校验(慎用) |
|
|
69
|
+
| `APKBOX_APKTOOL` / `APKBOX_DEX2JAR` / `APKBOX_AAPT2` / `APKBOX_JADX` | 分别指定工具路径 |
|
|
70
|
+
| `ANDROID_HOME` / `ANDROID_SDK_ROOT` | 找 aapt2 和 android.jar |
|
|
71
|
+
| `APKBOX_LANG` | 界面语言 `zh` / `en` |
|
|
72
|
+
|
|
73
|
+
当前自动下载的版本(升级时改 `apkbox/fetch.py` 里的 `SPECS`):
|
|
74
|
+
|
|
75
|
+
| 工具 | 版本 | 来源 |
|
|
76
|
+
| --- | --- | --- |
|
|
77
|
+
| apktool | 2.7.0 | GitHub releases(单个 fat jar) |
|
|
78
|
+
| dex-tools | 2.4 | GitHub releases |
|
|
79
|
+
| jadx | 1.5.6 | GitHub releases |
|
|
80
|
+
| aapt2 | 9.4.1-15978811 | Google Maven(`dl.google.com`) |
|
|
81
|
+
|
|
82
|
+
## 转 AAR
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
apkbox app.apk # → app.aar(和输入同目录)
|
|
86
|
+
apkbox aar app.apk -o dist/app.aar --keep # 指定输出,保留中间文件
|
|
87
|
+
apkbox app.apk --code-only # 只要 classes.jar,不带 res/assets/jni
|
|
88
|
+
apkbox app.apk -w work/ --skip-decode # 复用 work/ 里已有的解包结果
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
流程:apktool 解包 → dex2jar 出 classes.jar → 组装 res/assets/jni(`public.xml` 钉死资源 id)→ 裁剪 AndroidManifest → aapt2 compile + link 自检 → 打包 `.aar`。
|
|
92
|
+
|
|
93
|
+
常用选项:
|
|
94
|
+
|
|
95
|
+
| 选项 | 说明 |
|
|
96
|
+
| --- | --- |
|
|
97
|
+
| `-o, --output` | 输出 `.aar` 路径(默认与 apk 同名) |
|
|
98
|
+
| `-w, --work-dir` | 中间文件目录(默认临时目录,指定后保留) |
|
|
99
|
+
| `--code-only` | 只要 classes.jar,不带 res/assets/jni |
|
|
100
|
+
| `--keep-app-attrs` | 保留 `<application>` 的属性(默认清空以免与宿主合并冲突) |
|
|
101
|
+
| `--skip-decode` | 复用 `-w` 目录里已有的 apktool 解包结果 |
|
|
102
|
+
| `--keep` | 保留临时目录 |
|
|
103
|
+
| `--apktool` / `--dex2jar` / `--aapt2` | 手动指定工具(默认按上面的顺序自动解析) |
|
|
104
|
+
| `--android-jar` | android.jar 路径(用于 link 自检,缺省自动找 SDK) |
|
|
105
|
+
| `-v, --verbose` | 打印每步实际执行的子命令 |
|
|
106
|
+
|
|
107
|
+
### 产物注意事项
|
|
108
|
+
|
|
109
|
+
- 打印的 `可能与宿主 Duplicate class: androidx …` 是提示:这些包宿主大概率已有,合并前先看一眼。
|
|
110
|
+
- `<application>` 的 `android:name/icon/label/theme` 默认被清空(避免和宿主合并冲突),图标、label、主题、Application 类要宿主自己设置;确实想带上用 `--keep-app-attrs`。
|
|
111
|
+
- 没找到 android.jar 时跳过 link 自检,`R.txt` 按原 APK 的资源 id 生成,`minCompileSdk` 取原 APK 的 `compileSdkVersion`;找到时会尝试各平台,取能通过的最低版本。
|
|
112
|
+
- native 库按 APK 原样携带:APK 里有哪些 ABI,AAR 的 `jni/` 就有哪些;只有 arm 库的话 x86 模拟器跑不起来(会打印警告)。
|
|
113
|
+
|
|
114
|
+
## 反编译成 .java
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
apkbox java app.apk -o src/ # APK → src/**/*.java + resources/
|
|
118
|
+
apkbox java classes.dex # 单个 dex → classes-java/
|
|
119
|
+
apkbox java ./smali_out/ -o src/ # 目录里挑 classes*.dex
|
|
120
|
+
apkbox java app.apk -r # --no-res:只要代码,不解资源
|
|
121
|
+
apkbox java app.apk -j 4 --deobf # 4 线程 + 反混淆
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
| 选项 | 说明 |
|
|
125
|
+
| --- | --- |
|
|
126
|
+
| `-o, --output` | 输出目录(默认 `<输入名>-java`) |
|
|
127
|
+
| `-r, --no-res` | 不反编译资源,只出 `.java` |
|
|
128
|
+
| `-j, --jobs` | 线程数(默认用 jadx 的) |
|
|
129
|
+
| `--deobf` | 反混淆:太短/太长的类名重新命名 |
|
|
130
|
+
| `-e, --export-gradle` | 输出成 Android/Java gradle 工程 |
|
|
131
|
+
| `--show-bad-code` | 反编译不出来的代码也照样输出(带注释标记) |
|
|
132
|
+
| `--jadx` | jadx 可执行文件/jar(默认自动解析/下载) |
|
|
133
|
+
|
|
134
|
+
- 有反编译错误时会在输出旁写一份 `<输出名>.log`(混淆过的 APK 出现几百处错误很常见)。
|
|
135
|
+
- 进度行只在终端里刷,重定向到文件时不会刷屏。
|
|
136
|
+
|
|
137
|
+
## 界面语言
|
|
138
|
+
|
|
139
|
+
按系统 locale 自动选中文/英文,也可以强制指定:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
apkbox --lang en java app.apk # --lang 放子命令前后都行
|
|
143
|
+
APKBOX_LANG=en apkbox app.apk # 环境变量
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Python API
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
from apkbox import convert, decompile_to_java, ToolError
|
|
150
|
+
|
|
151
|
+
convert("app.apk") # → app.aar,返回退出码
|
|
152
|
+
convert("app.apk", "dist/app.aar", code_only=True) # 只要 classes.jar
|
|
153
|
+
info = decompile_to_java("app.apk", "src/") # 返回统计 dict
|
|
154
|
+
print(info["java_files"], info["errors"], info["log_file"])
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
参数与命令行同名(`convert` 的 `work_dir/code_only/keep/skip_decode/...`,
|
|
158
|
+
`decompile_to_java` 的 `no_res/deobf/jobs/show_bad_code/...`)。
|
|
159
|
+
工具级失败抛 `ToolError`;API 和命令行一样,缺工具会自动下载(`APKBOX_OFFLINE=1` 可禁止)。
|
|
160
|
+
|
|
161
|
+
## 退出码
|
|
162
|
+
|
|
163
|
+
| 码 | 含义 |
|
|
164
|
+
| --- | --- |
|
|
165
|
+
| 0 | 成功 |
|
|
166
|
+
| 1 | 工具执行失败(外部命令报错,已打印 `error: …`) |
|
|
167
|
+
| 2 | 参数/输入文件问题 |
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""apkbox — APK 工具集: 转成 AAR, 或反编译出 .java 源码。
|
|
2
|
+
|
|
3
|
+
from apkbox import convert, decompile_to_java
|
|
4
|
+
|
|
5
|
+
convert("app.apk") # → app.aar
|
|
6
|
+
convert("app.apk", "dist/app.aar", code_only=True) # 只要 classes.jar
|
|
7
|
+
decompile_to_java("app.apk", "src/") # → src/**/*.java
|
|
8
|
+
|
|
9
|
+
流程: apktool 解包 → dex2jar 出 classes.jar → 组装 res/assets/jni +
|
|
10
|
+
public.xml 固定资源 id → 裁剪 AndroidManifest → aapt2 自检 → 打包 .aar。
|
|
11
|
+
反编译走 jadx。
|
|
12
|
+
|
|
13
|
+
工具(apktool / dex-tools / aapt2 / jadx)按 显式参数 > 环境变量 > 系统
|
|
14
|
+
PATH/SDK > 缓存 > 自动下载 的顺序解析: 系统里装过就直接用, 没有的话首次
|
|
15
|
+
用到时下载到 ~/.cache/apkbox(sha256 校验; 镜像前缀用 APKBOX_MIRROR 配,
|
|
16
|
+
APKBOX_OFFLINE=1 禁止联网), 只需系统有 Java。
|
|
17
|
+
"""
|
|
18
|
+
from .core import convert
|
|
19
|
+
from .decompile import decompile_to_java
|
|
20
|
+
from .errors import ToolError
|
|
21
|
+
from .version import __version__
|
|
22
|
+
|
|
23
|
+
__all__ = ["convert", "decompile_to_java", "ToolError", "__version__"]
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"""命令行入口: apkbox {aar,java} ..."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
from . import fetch
|
|
9
|
+
from .core import convert
|
|
10
|
+
from .decompile import decompile_to_java, print_report
|
|
11
|
+
from .errors import ToolError
|
|
12
|
+
from .i18n import get_lang, set_lang, t
|
|
13
|
+
from .locate import find_jadx
|
|
14
|
+
from .version import __version__
|
|
15
|
+
|
|
16
|
+
ROOT_EPILOG_ZH = """示例:
|
|
17
|
+
apkbox app.apk # APK → AAR(aar 是默认子命令)
|
|
18
|
+
apkbox aar app.apk -o dist/app.aar --keep
|
|
19
|
+
apkbox java app.apk -o src/ # 整个 APK 反编译成 .java
|
|
20
|
+
apkbox java ./smali_out/ -o src/ # 目录里的 classes*.dex
|
|
21
|
+
apkbox java classes.dex -r # 只要代码, 不解资源
|
|
22
|
+
apkbox fetch # 预下载 apktool/dex2jar/aapt2/jadx
|
|
23
|
+
|
|
24
|
+
工具(apktool / dex-tools / aapt2 / jadx)系统里有就直接用, 没有则首次用到时
|
|
25
|
+
下载到 ~/.cache/apkbox(镜像用 APKBOX_MIRROR 配), 只需系统有 Java。
|
|
26
|
+
资源 link 自检需要 android.jar, 找不到就跳过(R.txt 按原 APK 的 id 生成)。
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
ROOT_EPILOG_EN = """examples:
|
|
30
|
+
apkbox app.apk # APK -> AAR (aar is the default subcommand)
|
|
31
|
+
apkbox aar app.apk -o dist/app.aar --keep
|
|
32
|
+
apkbox java app.apk -o src/ # decompile a whole APK into .java
|
|
33
|
+
apkbox java ./smali_out/ -o src/ # classes*.dex inside a directory
|
|
34
|
+
apkbox java classes.dex -r # code only, skip resources
|
|
35
|
+
apkbox fetch # pre-download apktool/dex2jar/aapt2/jadx
|
|
36
|
+
|
|
37
|
+
Tools (apktool / dex-tools / aapt2 / jadx): system installs are used as-is,
|
|
38
|
+
otherwise downloaded into ~/.cache/apkbox on first use (configure mirrors
|
|
39
|
+
with APKBOX_MIRROR). Only Java is required on the system.
|
|
40
|
+
The resource link check needs android.jar; it is skipped if not found
|
|
41
|
+
(R.txt is then generated from the ids of the original APK).
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _root_epilog() -> str:
|
|
46
|
+
return ROOT_EPILOG_EN if get_lang() == "en" else ROOT_EPILOG_ZH
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _add_aar_args(ap: argparse.ArgumentParser) -> None:
|
|
50
|
+
ap.add_argument("apk", help=t("输入的 .apk 文件"))
|
|
51
|
+
ap.add_argument("-o", "--output", help=t("输出 .aar 路径(默认与 apk 同名)"))
|
|
52
|
+
ap.add_argument("-w", "--work-dir",
|
|
53
|
+
help=t("中间文件目录(默认临时目录, 指定后保留)"))
|
|
54
|
+
ap.add_argument("--apktool", help=t("apktool 可执行文件/目录/jar(没有会自动下载)"))
|
|
55
|
+
ap.add_argument("--dex2jar",
|
|
56
|
+
help=t("dex-tools 目录(含 d2j-dex2jar.sh, 没有会自动下载)"))
|
|
57
|
+
ap.add_argument("--aapt2", help=t("aapt2 可执行文件(没有会自动下载)"))
|
|
58
|
+
ap.add_argument("--android-jar",
|
|
59
|
+
help=t("android.jar 路径(用于 link 自检, 缺省自动找 SDK)"))
|
|
60
|
+
ap.add_argument("--code-only", action="store_true",
|
|
61
|
+
help=t("只要 classes.jar, 不带 res/assets/jni"))
|
|
62
|
+
ap.add_argument("--keep-app-attrs", action="store_true",
|
|
63
|
+
help=t("保留 <application> 的属性(默认清空以免与宿主合并冲突)"))
|
|
64
|
+
ap.add_argument("--skip-decode", action="store_true",
|
|
65
|
+
help=t("复用 -w 目录里已有的 apktool 解包结果, 跳过解包"))
|
|
66
|
+
ap.add_argument("--keep", action="store_true", help=t("保留临时目录"))
|
|
67
|
+
ap.add_argument("-v", "--verbose", action="store_true", help=t("打印子命令"))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _add_java_args(ap: argparse.ArgumentParser) -> None:
|
|
71
|
+
ap.add_argument("input", help=t(".apk / .dex / .jar / .zip, 或含 classes*.dex 的目录"))
|
|
72
|
+
ap.add_argument("-o", "--output", help=t("输出目录(默认 <输入名>-java)"))
|
|
73
|
+
ap.add_argument("-r", "--no-res", action="store_true",
|
|
74
|
+
help=t("不反编译资源, 只出 .java"))
|
|
75
|
+
ap.add_argument("--deobf", action="store_true",
|
|
76
|
+
help=t("反混淆: 太短/太长的类名重新命名"))
|
|
77
|
+
ap.add_argument("-e", "--export-gradle", action="store_true",
|
|
78
|
+
help=t("输出成 Android/Java gradle 工程"))
|
|
79
|
+
ap.add_argument("--show-bad-code", action="store_true",
|
|
80
|
+
help=t("反编译不出来的代码也照样输出(带注释标记)"))
|
|
81
|
+
ap.add_argument("-j", "--jobs", type=int, help=t("线程数(默认用 jadx 的)"))
|
|
82
|
+
ap.add_argument("--jadx", help=t("jadx 可执行文件/jar(没有会自动下载)"))
|
|
83
|
+
ap.add_argument("-v", "--verbose", action="store_true", help=t("打印子命令"))
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
87
|
+
ap = argparse.ArgumentParser(
|
|
88
|
+
prog="apkbox",
|
|
89
|
+
description=t("APK 工具集: 转 AAR、反编译成 .java。"),
|
|
90
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
91
|
+
epilog=_root_epilog())
|
|
92
|
+
ap.add_argument("--version", action="version", version=f"apkbox {__version__}")
|
|
93
|
+
ap.add_argument("--lang", choices=("zh", "en"), default=argparse.SUPPRESS,
|
|
94
|
+
help=t("界面语言: zh / en(默认跟随系统)"))
|
|
95
|
+
sub = ap.add_subparsers(dest="cmd", metavar="{aar,java,fetch}")
|
|
96
|
+
pa = sub.add_parser("aar", help=t("APK → AAR(默认子命令)"))
|
|
97
|
+
pa.add_argument("--lang", choices=("zh", "en"), default=argparse.SUPPRESS,
|
|
98
|
+
help=t("界面语言: zh / en(默认跟随系统)"))
|
|
99
|
+
_add_aar_args(pa)
|
|
100
|
+
pj = sub.add_parser("java", help=t("dex/apk → .java 源码(自动下载 jadx)"))
|
|
101
|
+
pj.add_argument("--lang", choices=("zh", "en"), default=argparse.SUPPRESS,
|
|
102
|
+
help=t("界面语言: zh / en(默认跟随系统)"))
|
|
103
|
+
_add_java_args(pj)
|
|
104
|
+
pf = sub.add_parser("fetch", help=t("预下载工具到缓存(缺什么下什么)"))
|
|
105
|
+
pf.add_argument("--lang", choices=("zh", "en"), default=argparse.SUPPRESS,
|
|
106
|
+
help=t("界面语言: zh / en(默认跟随系统)"))
|
|
107
|
+
pf.add_argument("names", nargs="*", metavar="name", choices=fetch.NAMES,
|
|
108
|
+
help=t("工具名(缺省全部: {names})", names=", ".join(fetch.NAMES)))
|
|
109
|
+
return ap
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _extract_lang(argv: list[str]) -> tuple[list[str], str | None]:
|
|
113
|
+
"""把 --lang [zh|en] 从 argv 里摘出来: 语言要在建 parser(生成帮助文本)前定好。"""
|
|
114
|
+
out, val = [], None
|
|
115
|
+
i = 0
|
|
116
|
+
while i < len(argv):
|
|
117
|
+
a = argv[i]
|
|
118
|
+
if a == "--lang" and i + 1 < len(argv):
|
|
119
|
+
val = argv[i + 1]
|
|
120
|
+
i += 2
|
|
121
|
+
continue
|
|
122
|
+
if a.startswith("--lang="):
|
|
123
|
+
val = a.split("=", 1)[1]
|
|
124
|
+
i += 1
|
|
125
|
+
continue
|
|
126
|
+
out.append(a)
|
|
127
|
+
i += 1
|
|
128
|
+
return out, val
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _run_java(args) -> int:
|
|
132
|
+
print(f"apkbox java v{__version__}")
|
|
133
|
+
src = args.input
|
|
134
|
+
print(f" input : {src}")
|
|
135
|
+
print(f" output: {args.output or t('(默认 <输入名>-java)')}")
|
|
136
|
+
t0 = time.time()
|
|
137
|
+
try:
|
|
138
|
+
jadx_ready = bool(find_jadx(args.jadx, download=False))
|
|
139
|
+
find_jadx(args.jadx)
|
|
140
|
+
print(f" tools : jadx {'✓' if jadx_ready else t('已下载')}")
|
|
141
|
+
print("[1] " + t("jadx 反编译") + " ... ", end="", flush=True)
|
|
142
|
+
info = decompile_to_java(
|
|
143
|
+
src, args.output, no_res=args.no_res, deobf=args.deobf,
|
|
144
|
+
jobs=args.jobs, show_bad_code=args.show_bad_code,
|
|
145
|
+
export_gradle=args.export_gradle, jadx=args.jadx,
|
|
146
|
+
verbose=args.verbose)
|
|
147
|
+
except ToolError as e:
|
|
148
|
+
print(t("失败"))
|
|
149
|
+
sys.stdout.flush() # 免得重定向时 error 抢在 "失败" 前面
|
|
150
|
+
print(f"\nerror: {e}", file=sys.stderr)
|
|
151
|
+
return 1
|
|
152
|
+
print(f"ok ({t('{n} 个 .java', n=info['java_files'])}) "
|
|
153
|
+
f"[{time.time() - t0:.1f}s]")
|
|
154
|
+
print_report(info)
|
|
155
|
+
return 0
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _run_fetch(args) -> int:
|
|
159
|
+
try:
|
|
160
|
+
results = fetch.ensure_all(args.names)
|
|
161
|
+
except ToolError as e:
|
|
162
|
+
print(f"error: {e}", file=sys.stderr)
|
|
163
|
+
return 1
|
|
164
|
+
for name, path, downloaded in results:
|
|
165
|
+
print(f" {name}: {t('已下载') if downloaded else '✓'} {path}")
|
|
166
|
+
print(t("工具已就绪, 缓存目录: {d}", d=fetch.cache_root()))
|
|
167
|
+
return 0
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def main(argv=None) -> int:
|
|
171
|
+
argv = list(sys.argv[1:] if argv is None else list(argv))
|
|
172
|
+
argv, lang = _extract_lang(argv)
|
|
173
|
+
if lang is not None:
|
|
174
|
+
tag = lang.split(".")[0].split("-")[0].split("_")[0].lower()
|
|
175
|
+
if tag not in ("zh", "en"):
|
|
176
|
+
print(t("error: --lang 只支持 zh / en, 收到 {lang}",
|
|
177
|
+
lang=repr(lang)), file=sys.stderr)
|
|
178
|
+
return 2
|
|
179
|
+
lang = tag
|
|
180
|
+
set_lang(lang)
|
|
181
|
+
# 兼容老用法: apkbox app.apk 等价于 apkbox aar app.apk
|
|
182
|
+
if argv and argv[0] not in {"aar", "java", "fetch", "-h", "--help", "--version"}:
|
|
183
|
+
argv.insert(0, "aar")
|
|
184
|
+
ap = build_parser()
|
|
185
|
+
args = ap.parse_args(argv)
|
|
186
|
+
if not args.cmd:
|
|
187
|
+
ap.print_help()
|
|
188
|
+
return 2
|
|
189
|
+
if args.cmd == "fetch":
|
|
190
|
+
return _run_fetch(args)
|
|
191
|
+
if args.cmd == "java":
|
|
192
|
+
return _run_java(args)
|
|
193
|
+
return convert(
|
|
194
|
+
args.apk, args.output,
|
|
195
|
+
work_dir=args.work_dir,
|
|
196
|
+
code_only=args.code_only,
|
|
197
|
+
keep=args.keep,
|
|
198
|
+
keep_app_attrs=args.keep_app_attrs,
|
|
199
|
+
skip_decode=args.skip_decode,
|
|
200
|
+
verbose=args.verbose,
|
|
201
|
+
apktool=args.apktool,
|
|
202
|
+
dex2jar=args.dex2jar,
|
|
203
|
+
aapt2=args.aapt2,
|
|
204
|
+
android_jar=args.android_jar,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
if __name__ == "__main__":
|
|
209
|
+
sys.exit(main())
|