C.C.Nemo
C.C.Nemo
发布于 2026-11-01 / 7 阅读
0
0

官方 ChatGPT 装不进 Android 12:改 1 个字节,再造一个中文包

上一篇里,这台联想小新 Pad Plus(TB-J607F)的 ChatGPT 最后落成了桌面上的一个网页快捷方式。我当时写的是「折腾到最后落成桌面上的一个网页图标——也算给精简补了个反面」。

用了几天,意见来了:网页版得开浏览器、等加载,输入框还老被键盘顶跑。App 好用太多了。

行,上 App。结果这件事在这台机器上分成了两半:装不上,和装上了但一半是英文。两半都得自己动手。

ChatGPT 在 Android 12 上的两道封锁与两次自己动手

三道封锁都在系统侧,两次突破都在本地完成。全程没有碰 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,原签名就废了,得自己重签。

APK Signature Scheme v2 的内容摘要算法

网上讲 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(总块数) ‖ 所有块摘要顺序拼接 )

我在这栽了两次:

  1. 分块前缀是 0xa5,不是 0x5a。 照着几篇高赞博客写成 0x5a,稳稳地报 SHA-256 digest of contents did not verify。
  2. 三段的块要合并计数。 不是三段各算各的再拼,是先把三段的块编号连续排下去,再统一算总摘要。

这两条最后是对着 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 编码。

重建时的三条硬约束

  1. 全局字符串池的条数和顺序不能变。 资源值存的是「池内索引」,删一条、插一条,后面所有 string 资源集体错位。
  2. 只保留 string 类型。 语言分包不需要 drawable、layout、xml。
  3. 配置里的语言字节改成 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 覆盖

九、代价

  1. 不能通过 Play 更新。 签名和官方不同,商店识别不出来。要更新得重走一遍打包流程。
  2. 将来换官方版必须先卸载,不能覆盖安装。
  3. Play 的地区限制还在。 那是账号层面的,改包解决不了。
  4. 这是民间汉化。 译法按常用说法来,跟官方译法有出入。

十、几条能复用的经验

  1. 改 APK 一定要能自证。 判断依据是逐条目 md5 + 逐字节 diff,不是「我记得我没改别的」。
  2. 自己签自己验,毫无意义。 拿官方原包验一遍,才证明实现对了。
  3. --force-sdk 是假门。 只在单 APK 的第一道检查有效,install-create 里直接被忽略。
  4. Android 12 没有 install-multiple,只能用 install-create 三件套。
  5. 分包不能单独装,要 install-create -p 包名。
  6. resources.arsc 必须 STORED + 4 字节对齐。

最后

上一篇里我写「不是所有东西都该被清掉,清之前最好先弄清它到底是什么」。这篇算是它的续集,只不过这次不是清,是补。

这台机器缺的东西其实很少:一个 SDK 版本号,一包语言资源。第一样差 1,第二样差 998 KB。两样都不是「破解」,是把厂商没为这个系统版本准备的那两块,自己补上。

代价是它从此和官方更新脱钩。这个代价我认,因为替代方案是桌面上的一个网页快捷方式,而那个方案已经被用了一周并且被判了死刑。

至于那些还没翻的英文长句,就先留着。一台 2023 年的中端平板跑着 2026 年的客户端,界面里剩几个英文单词,真不算什么大事。


评论