上一篇里,这台联想小新 Pad Plus(TB-J607F)的 ChatGPT 最后落成了桌面上的一个网页快捷方式。我当时写的是「折腾到最后落成桌面上的一个网页图标——也算给精简补了个反面」。
用了几天,意见来了:网页版得开浏览器、等加载,输入框还老被键盘顶跑。App 好用太多了。
行,上 App。结果这件事在这台机器上分成了两半:装不上,和装上了但一半是英文。两半都得自己动手。

三道封锁都在系统侧,两次突破都在本地完成。全程没有碰 OpenAI 的服务器,也没有往包里塞任何第三方代码。
一、三堵墙,一堵比一堵硬
1. Play 商店:搜得到,点不进
Play 里其实能搜到 ChatGPT。但详情页顶上明写着:
此商品无法在您所在的国家/地区购买或下载
日志里对应的是:
Finsky: com.openai.chatgpt not available availableViaToc=true availabilityStatus=4
availabilityStatus=4 就是「该账号所在地区不发行」。这堵墙绑在账号上,改包解决不了,所以不必在这上面耗。
2. 官方 APK:差一个 SDK 号
从镜像站拿到官方 1.2026.300 的完整分包,aapt dump badging 一看:
sdkVersion:'32'
targetSdkVersion:'37'
而这台机器:
ro.build.version.sdk = 31
Android 12 是 31,Android 12L 才是 32。OpenAI 从某个版本起把最低要求提到了 12L。直接装就是 INSTALL_FAILED_OLDER_SDK。
3. --force-sdk:一扇假门
pm install 有个 --force-sdk 参数,看名字像是专为这种情况准备的。实测:
adb shell pm install --force-sdk -r /data/local/tmp/base.apk
# Failed to parse APK file: ... Requires newer sdk version #32 (current version is #31)
以为路径不对,换成分包安装:
adb shell pm install-create --force-sdk -r
完全不生效,参数被直接忽略。
翻 PackageManagerService 才明白:--force-sdk 只在单 APK 的 setParamsSize 阶段跳过一次 minSdk 检查,真正提交时还会走一次 INSTALL_FAILED_OLDER_SDK;而 install-create 那条路径压根不读这个标志。
顺带记一条:Android 12 没有 pm install-multiple,会报 Unknown command。分包只能 install-create / install-write / install-commit 三件套。
二、那就自己改那一个字节
minSdkVersion 编译后在二进制 AXML 里长这样:
<uses-sdk android:minSdkVersion="32" android:targetSdkVersion="37"/>
uses-sdk 元素的 minSdkVersion 属性值是一个 4 字节小端整数,属性名的资源 ID 是 0x0101020C。扫 manifest 找到这个 ID,定位到属性结构 +16 偏移处的值,改掉即可。
结果:
偏移 0xB93C: 0x20 → 0x1F
0x20 是 32,0x1F 是 31。整个 AndroidManifest.xml 共 114,696 字节,只差这 1 个。
改完拿四个包逐条目做 md5 比对:
| 分包 | 条目数 | 内容有差异的条目 |
|---|---|---|
com.openai.chatgpt.apk |
5,397 → 5,397 | 只有 AndroidManifest.xml |
config.arm64_v8a.apk |
14 → 14 | 只有 AndroidManifest.xml |
config.en.apk |
3 → 3 | 只有 AndroidManifest.xml |
config.mdpi.apk |
68 → 68 | 只有 AndroidManifest.xml |
这一步比改动本身更重要。「我保证没干别的」是空话,「5397 个条目逐条目 md5 一致」才是证据。
三、签名:v2 里那几个总是写错的数字
改完 manifest,原签名就废了,得自己重签。

网上讲 v2 的文章很多,但分块前缀和分块计数这两处,我看到的几乎全写错了。
Android 7 以后主流是 APK Signature Scheme v2。签名块塞在 ZIP 中央目录之前:
[u64 块长度]
[u64 pair 长度][u32 pair id] [value]
...
[u64 块长度]
[16 字节 "APK Sig Block 42"]
我先把块长度算错,写成 len(pair) + 8,设备报:
INSTALL_PARSE_FAILED_NO_CERTIFICATES: ... Attempt to get length of null array
正确的是 len(pair) + 24——尾部那个 8 字节长度字段和 16 字节 magic 也要算进 block_size。
内容摘要:两个数字
v2 不对整个文件做哈希,而是把「条目数据 / 中央目录 / EOCD」三段连续每 1 MB 切一块。
每块:
chunk_digest = H( 0xa5 ‖ u32(块长) ‖ 块内容 )
总体:
content_digest = H( 0x5a ‖ u32(总块数) ‖ 所有块摘要顺序拼接 )
我在这栽了两次:
- 分块前缀是
0xa5,不是0x5a。 照着几篇高赞博客写成0x5a,稳稳地报SHA-256 digest of contents did not verify。 - 三段的块要合并计数。 不是三段各算各的再拼,是先把三段的块编号连续排下去,再统一算总摘要。
这两条最后是对着 AOSP 的 ApkSigningBlockUtils.java 读出来的——网上那批文章基本都错在同一处。
怎么确认自己没写错
这点比算法本身重要。
自己签的包自己验通过,什么也说明不了:如果摘要算法本身错了,签名和验签用的是同一套错逻辑,照样"通过",但装进设备就报 digest 不匹配。
所以我做的第一件事,是拿 OpenAI 官方原包自带的签名去验:
verify_v2('gpt_xapk/com.openai.chatgpt.apk')
# OK (alg=0x0104, certs=1425, attrs=12)
用别人的证书、别人的签名验通了,才证明我的解析和摘要实现是对的。然后才敢去签自己的包。
四、装上
Android 12 装分包:
S=$(adb shell pm install-create -r -d | grep -oE '\[[0-9]+\]' | tr -d '[]')
adb shell pm install-write -S 95432101 $S split_base.apk /data/local/tmp/base.apk
adb shell pm install-write -S 22345678 $S split_arm64.apk /data/local/tmp/config.arm64_v8a.apk
adb shell pm install-write -S 10240 $S split_en.apk /data/local/tmp/config.en.apk
adb shell pm install-write -S 20480 $S split_mdpi.apk /data/local/tmp/config.mdpi.apk
adb shell pm install-commit $S
装完核对:
versionName = 1.2026.300
minSdk = 31
targetSdk = 37
splits = [base, config.arm64_v8a, config.en, config.mdpi]
com.openai.chatgpt/.MainActivity 入口正常,Welcome 页 → 主界面,能对话。
第一半结束。 这时候打开一看,问题是第二半。
五、界面中英混杂,而语言选项明明写着中文
侧栏是 Projects / Library / Apps / Remote,可主区域的中文却完全正常。进设置看 General → App language,显示的是 中文;点开下拉才发现实际选中的是 System default,而系统语言就是 zh-CN。

同一个开关,两种界面。区别只在于有没有 config.zh 这一包资源可读。
应用一直在请求中文,只是没有中文资源可读。
为什么 base.apk 里一个中文字都没有
ChatGPT 把界面文案按语言拆成了独立分包,而 APKPure 那个 XAPK 是按「通用设备」打的,只塞了英文。
把每个包的 resources.arsc 拆开,读 ResTable_type 里的 config(size(4) mcc(2) mnc(2) language(2) country(2)):
| 分包 | 语言配置 |
|---|---|
base.apk(91 MB) |
80 个 type 块,全部是「默认」 |
config.en.apk |
en en-AU en-CA en-GB en-IN en-SG 等 8 种 |
config.zh.apk |
不存在 |
再直接搜字节:base.apk 的 resources.arsc 里,UTF-8 中文(0xE4–0xE9 开头的三字节序列)出现 0 次。
而 manifest 引用的 res/xml/_generated_res_locale_config.xml 里,声明支持的语言明确包含 zh、zh-HK、zh-TW。
资源声明有,资源文件没有。 中英混排的全部原因就在于文案分了两条路:
- 服务端下发的(问候语、建议项、聊天标题)走账号语言 → 中文正常
- 客户端写死的(侧栏、设置项、按钮)走资源文件 → 只有英文
公开渠道确实不发语言分包
把能试的都试了:APKPure 只有 en;APKMirror 三个变体都是 90 MB 上下的单 APK,也是 en;APKFab 有验证码;APKCombo 域名已废弃。
下载站只保证「装上能跑」,不管界面语言。可靠来源只剩 Play,而 Play 卡在地区上。
六、造一个 config.zh
语言分包的本质很朴素:同一批资源 ID + 一个语言配置 + 一套译文。那就自己造。
资源表长什么样
resources.arsc 是嵌套 chunk:
RES_TABLE (0x0002)
└── RES_TABLE_PACKAGE (0x0200)
├── 类型字符串池 / 键名字符串池
├── RES_TABLE_TYPE_SPEC (0x0202)
└── RES_TABLE_TYPE (0x0201) ← 每种类型 × 每种配置一个
string 在类型池里的下标决定 type id,这个包是 0x13,8582 条;全局字符串池 10054 条,UTF-8 编码。
重建时的三条硬约束
- 全局字符串池的条数和顺序不能变。 资源值存的是「池内索引」,删一条、插一条,后面所有 string 资源集体错位。
- 只保留
string类型。 语言分包不需要 drawable、layout、xml。 - 配置里的语言字节改成
zh。 位置在ResTable_config的 language 字段(偏移 8,2 字节)。
照这三条重建,得到一个 998 KB 的 config.zh.apk。
两个安装坑
坑一:resources.arsc 必须 STORED + 4 字节对齐。
用 ZIP_DEFLATED 写进去,装上是这样:
INSTALL_FAILED_INVALID_APK: ... resources.arsc ... 4-byte boundary
必须 ZIP_STORED,并且保证数据起始偏移是 4 的倍数(用 extra field 填充)。
坑二:分包不能单独装。
adb shell pm install -r -d /data/local/tmp/config.zh.apk
# Full install must include a base package
得建一个「继承已有应用」的会话:
adb shell pm install-create -r -d -p com.openai.chatgpt
这里的 -p 不是「装到哪个包」,是告诉 PackageManager:这是一个已存在应用的增量分包。
七、结果
译文字典 986 条,命中 880 条。侧栏、设置页、主界面都跟着变了:
- 侧栏:图像 / 资料库 / 项目 / 远程 / 定时任务 / 探索 / 对话 / 查看全部…
- 设置:我的 ChatGPT、个性化、记忆、插件、账户、工作空间、升级套餐、用量与限额、外观、通用、通知、语音、安全与登录、存储、隐私中心、历史与数据、关于、退出登录
- 主界面:帮我做作业 / 给我讲讲某个话题 / 出一套练习题
- 输入框:回复 ChatGPT
发一条 hello,回了「你好呀!欢迎来到学习小课堂!🎨✨」——功能没受影响。
am force-stop 之后从桌面图标冷启动,也正常。
八、还有英文,我如实说在哪
没有全翻。 二级菜单、三级页面里的整段说明,还有不少是英文。
原因是这类字符串我没收进词表——它们多是长句("ChatGPT can use general knowledge to give fast, in-depth answers. These aren't personalized and don't use memory."),逐条机翻的质量风险高,我优先保证了能看见的菜单项和按钮。
没收录的字符串会自动回落英文,不会报错,所以表现就是「一层中文、往里走还夹着英文」。
想补随时能补,不用重装应用:
python build_zh_split.py zh_all.json config.zh.apk
# 再用同一条 install-create -p com.openai.chatgpt 覆盖
九、代价
- 不能通过 Play 更新。 签名和官方不同,商店识别不出来。要更新得重走一遍打包流程。
- 将来换官方版必须先卸载,不能覆盖安装。
- Play 的地区限制还在。 那是账号层面的,改包解决不了。
- 这是民间汉化。 译法按常用说法来,跟官方译法有出入。
十、几条能复用的经验
- 改 APK 一定要能自证。 判断依据是逐条目 md5 + 逐字节 diff,不是「我记得我没改别的」。
- 自己签自己验,毫无意义。 拿官方原包验一遍,才证明实现对了。
--force-sdk是假门。 只在单 APK 的第一道检查有效,install-create里直接被忽略。- Android 12 没有
install-multiple,只能用install-create三件套。 - 分包不能单独装,要
install-create -p 包名。 resources.arsc必须 STORED + 4 字节对齐。
最后
上一篇里我写「不是所有东西都该被清掉,清之前最好先弄清它到底是什么」。这篇算是它的续集,只不过这次不是清,是补。
这台机器缺的东西其实很少:一个 SDK 版本号,一包语言资源。第一样差 1,第二样差 998 KB。两样都不是「破解」,是把厂商没为这个系统版本准备的那两块,自己补上。
代价是它从此和官方更新脱钩。这个代价我认,因为替代方案是桌面上的一个网页快捷方式,而那个方案已经被用了一周并且被判了死刑。
至于那些还没翻的英文长句,就先留着。一台 2023 年的中端平板跑着 2026 年的客户端,界面里剩几个英文单词,真不算什么大事。