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 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
@@ -0,0 +1,5 @@
1
+ import sys
2
+
3
+ from .cli import main
4
+
5
+ sys.exit(main())
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())