apkbox 2.1.0__py3-none-any.whl
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/__init__.py +23 -0
- apkbox/__main__.py +5 -0
- apkbox/cli.py +209 -0
- apkbox/core.py +774 -0
- apkbox/decompile.py +139 -0
- apkbox/errors.py +3 -0
- apkbox/fetch.py +315 -0
- apkbox/i18n.py +286 -0
- apkbox/locate.py +291 -0
- apkbox/util.py +42 -0
- apkbox/version.py +1 -0
- apkbox-2.1.0.dist-info/METADATA +174 -0
- apkbox-2.1.0.dist-info/RECORD +16 -0
- apkbox-2.1.0.dist-info/WHEEL +5 -0
- apkbox-2.1.0.dist-info/entry_points.txt +2 -0
- apkbox-2.1.0.dist-info/top_level.txt +1 -0
apkbox/__init__.py
ADDED
|
@@ -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__"]
|
apkbox/__main__.py
ADDED
apkbox/cli.py
ADDED
|
@@ -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())
|