Initial Commit
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 491 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 206 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 104 KiB |
@@ -0,0 +1,272 @@
|
||||
> 本文是当时的分析 / 交付存档,路径按当时的工作目录写。
|
||||
> 本项目里的对应关系:tools 对应 _tools,inputs/target/vela_app.bin 对应 _x/vela_app.bin,
|
||||
> inputs/source 对应 _s5,.work 对应 _out(产物),dist 对应 _out/faces。
|
||||
|
||||
# Xiaomi Watch S4 eSIM(o62lte)系统应用图标替换 —— 固件包分析报告
|
||||
|
||||
分析对象:`c53475a6c7367c3583c6882665fd85ae_upd_mijia.watch.o62lte.bin`
|
||||
固件标识:`ro.build.version=3.202.79, build.time=2026-07-03 19:58:07, model=o62lte`
|
||||
机型字符串:`Xiaomi Watch S4 eSIM`
|
||||
包大小:199,385,001 字节
|
||||
|
||||
---
|
||||
|
||||
## 1. 固件包是什么
|
||||
|
||||
外层就是一个 **ZIP(Android SignApk 签名包)**,开头即 `PK\x03\x04`,第一个条目是 `ota.sh`。
|
||||
|
||||
```
|
||||
ota.sh 4524
|
||||
vela_app.bin 265684992 <- 应用资源分区(图标在这里)
|
||||
vela_watchface.bin 92967936
|
||||
vela_system.bin 72366080
|
||||
vela_font.bin 36146176
|
||||
vela_misc.bin 35799040
|
||||
vela_vendor.bin 17909760
|
||||
vela_ap.bin 11141120
|
||||
vela_quickapp.bin 5840896
|
||||
vela_i18n.bin 4399104
|
||||
vela_cp.bin 3174400
|
||||
vela_audio.bin 2730024
|
||||
vela_ota.bin 1441792
|
||||
vela_tee.bin 1048576
|
||||
META-INF/CERT.RSA / CERT.SF / MANIFEST.MF
|
||||
```
|
||||
|
||||
`ota.sh` 的全部动作就是逐分区裸写:
|
||||
|
||||
```sh
|
||||
dd if=/ota/vela_app.bin of=/dev/app bs=32768 verify
|
||||
dd if=/ota/vela_vendor.bin of=/dev/vendor bs=32768 verify
|
||||
dd if=/ota/vela_system.bin of=/dev/system bs=32768 verify
|
||||
... (vendor/i18n/app/quickapp/cp/misc/audio/tee/system/font/ap/watchface)
|
||||
```
|
||||
|
||||
签名信息:`CERT.SF` 里 `Created-By: 1.0 (Android SignApk)`、`X-Android-APK-Signed: 2, 3`,`MANIFEST.MF` 对每个分区镜像记录 SHA1。
|
||||
|
||||
> **结论:这份 OTA 包无法自行重新打包。** 改一个字节就过不了签名校验。
|
||||
|
||||
`vela_*.bin` 是 **Linux `romfs` 镜像**(magic `-rom1fs-`):
|
||||
|
||||
| 镜像 | 卷名 | 条目数 | romfs 大小 |
|
||||
|---|---|---|---|
|
||||
| vela_app.bin | app | 7834 | 265,684,928 |
|
||||
| vela_watchface.bin | watchface | 312 | 92,967,424 |
|
||||
| vela_system.bin | system | 1525 | 72,365,728 |
|
||||
| vela_font.bin | font | 15 | 36,145,776 |
|
||||
| vela_misc.bin | mcmisc | 1341 | 35,798,176 |
|
||||
| vela_vendor.bin | vendor | 58 | 17,909,328 |
|
||||
| vela_quickapp.bin | quickapp | 46 | 5,840,544 |
|
||||
| vela_i18n.bin | i18n | 277 | 4,399,072 |
|
||||
|
||||
在盘上它们挂到 `/resource/<卷名>`,所以 AP 二进制里出现的是 `/resource/app/...` 这样的绝对路径。
|
||||
|
||||
### ROMFS 结构(关键)
|
||||
|
||||
```
|
||||
super : "-rom1fs-" | u32 size | u32 checksum | name(NUL, 补齐16)
|
||||
inode : u32 next | u32 spec | u32 size | u32 checksum | name(NUL, 补齐16) | 文件数据紧跟其后
|
||||
next 低 4 位 = 类型:0=硬链接(spec=目标inode偏移) 1=目录(spec=首个子项偏移) 2=普通文件 8=可执行
|
||||
```
|
||||
|
||||
**目录项与文件数据是严格顺序紧密排布的**,每一项的偏移完全由它前面所有内容的长度决定。
|
||||
|
||||
---
|
||||
|
||||
## 2. 系统自带应用的图标在哪里(已定位)
|
||||
|
||||
AP 二进制里写死了一批路径,直接给出答案:
|
||||
|
||||
```
|
||||
/resource/app/alarm/launcher.bin
|
||||
/resource/app/activities/launcher.bin
|
||||
/resource/app/calendar/launcher.bin
|
||||
/resource/app/heartrate/launcher.bin
|
||||
... 共 39 个
|
||||
```
|
||||
|
||||
精确统计:**38 个 `<app>/launcher.bin`** 加 **1 个 `phone/contacts_launcher.bin`**(联系人图标),共 39 个。
|
||||
|
||||
即 **每个系统应用目录下都有一个 `launcher.bin`,它就是"应用列表/启动器"里显示的那个图标**。全部位于 `vela_app.bin`(=`/dev/app` 分区,265 MB)。
|
||||
|
||||
39 个图标全部是 **LVGL v9 二进制图片**:
|
||||
|
||||
```
|
||||
u8 magic=0x19 | u8 color_format | u16 flags | u16 w | u16 h | u16 stride | u16 reserved
|
||||
cf=0x10 ARGB8888 : 12 + 144*144*4 = 82956 字节
|
||||
cf=0x0a I8 : 12 + 256*4 + 144*144 = 21772 字节
|
||||
```
|
||||
|
||||
已全部解码验证(38/39,`voice/alexa/launcher.bin` 是 6005 字节的特例,`flags=0x0008`,格式待定)。
|
||||
|
||||
### 图标清单(偏移为 `vela_app.bin` 内绝对偏移,固件 3.202.79 专用)
|
||||
|
||||
| 应用 | 数据偏移 | 长度 | 尺寸 | 格式 |
|
||||
|---|---|---|---|---|
|
||||
| wxpay | 0x0000bc00 | 21772 | 144x144 | I8 |
|
||||
| womenhealth | 0x00047a00 | 82956 | 144x144 | ARGB8888 |
|
||||
| weather | 0x017de200 | 82956 | 144x144 | ARGB8888 |
|
||||
| walkie_talkie | 0x02d45600 | 82956 | 144x144 | ARGB8888 |
|
||||
| voice/alexa | 0x032fa000 | 6005 | 144x144 | I8(特例) |
|
||||
| voice/aivs | 0x0341c400 | 82956 | 144x144 | ARGB8888 |
|
||||
| todolist | 0x03aa9000 | 82956 | 144x144 | ARGB8888 |
|
||||
| timer | 0x03ad3c00 | 82956 | 144x144 | ARGB8888 |
|
||||
| sports/training | 0x0466b000 | 82956 | 144x144 | ARGB8888 |
|
||||
| sports/record | 0x04a63000 | 82956 | 144x144 | ARGB8888 |
|
||||
| sports | 0x04baee00 | 82956 | 144x144 | ARGB8888 |
|
||||
| sports/course | 0x05132400 | 82956 | 144x144 | ARGB8888 |
|
||||
| sleep | 0x05156400 | 82956 | 144x144 | ARGB8888 |
|
||||
| settings | 0x052e8000 | 82956 | 144x144 | ARGB8888 |
|
||||
| remote_camera | 0x06593a00 | 21772 | 144x144 | I8 |
|
||||
| recorder | 0x067a4600 | 82956 | 144x144 | ARGB8888 |
|
||||
| pressure | 0x07908400 | 82956 | 144x144 | ARGB8888 |
|
||||
| phone | 0x07a4d000 | 82956 | 144x144 | ARGB8888 |
|
||||
| phone/contacts_launcher | 0x07a82200 | 82956 | 144x144 | ARGB8888 |
|
||||
| perpetual_calendar | 0x07b3be00 | 21772 | 144x144 | I8 |
|
||||
| oxygen | 0x08f08c00 | 82956 | 144x144 | ARGB8888 |
|
||||
| nfccard | 0x093f2c00 | 82956 | 144x144 | ARGB8888 |
|
||||
| mijia | 0x09599200 | 82956 | 144x144 | ARGB8888 |
|
||||
| media | 0x0968e200 | 82956 | 144x144 | ARGB8888 |
|
||||
| lua | 0x096b5200 | 21772 | 144x144 | I8 |
|
||||
| interconnect | 0x096d8000 | 82956 | 144x144 | ARGB8888 |
|
||||
| innovation_research | 0x096efa00 | 82956 | 144x144 | ARGB8888 |
|
||||
| heartrate | 0x09fea800 | 82956 | 144x144 | ARGB8888 |
|
||||
| health_today | 0x0a223a00 | 82956 | 144x144 | ARGB8888 |
|
||||
| flashlight | 0x0b6dba00 | 82956 | 144x144 | ARGB8888 |
|
||||
| find_phone | 0x0b6f0000 | 82956 | 144x144 | ARGB8888 |
|
||||
| esimsms | 0x0b762200 | 82956 | 144x144 | ARGB8888 |
|
||||
| debug | 0x0b81a200 | 21772 | 144x144 | I8 |
|
||||
| compass | 0x0b8a1400 | 21772 | 144x144 | I8 |
|
||||
| chronograph | 0x0ba47e00 | 82956 | 144x144 | ARGB8888 |
|
||||
| calendar | 0x0bc4b200 | 82956 | 144x144 | ARGB8888 |
|
||||
| breath | 0x0bd54c00 | 82956 | 144x144 | ARGB8888 |
|
||||
| barometer | 0x0e825c00 | 82956 | 144x144 | ARGB8888 |
|
||||
| alarm | 0x0f3a7e00 | 82956 | 144x144 | ARGB8888 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 疑问一:为什么"一个 Lua 表盘点一下就替换成功了"
|
||||
|
||||
从这份固件包里能**确证**的事实:
|
||||
|
||||
1. 系统应用图标 = `vela_app.bin` 里的 `launcher.bin`,而 `vela_app.bin` 是**整个 265 MB 分区镜像**;
|
||||
2. 官方通道 `ota.sh` 的写入方式是 `dd ... of=/dev/app`,**裸写分区**;
|
||||
3. 官方 OTA 包是 **SignApk v2/v3 签名的**,因此**不可能**通过"重新打包 OTA"来替换图标;
|
||||
4. 分区设备节点 `/dev/app`、`/dev/system` 等对用户态是**可打开的字符/块设备**(LoXe 文档也专门警告"不要直接试写未知设备,对分区节点 `dd`/重定向可能立即破坏系统")。
|
||||
|
||||
把这四点连起来,社区图标包只能走一条路:**绕过签名校验,直接把图标字节写进分区**。
|
||||
|
||||
所以那个"Lua 表盘"的角色是**运载 + 执行载体**,而不是"表盘 API 能设置图标":
|
||||
|
||||
```
|
||||
图标包(做成表盘包的样子)
|
||||
├── asset/*.bin 实际是一个"补丁表":[(绝对偏移, 该长度字节), ...]
|
||||
└── main.lua ui.init / 按钮回调里做写入
|
||||
│ 点击"立即替换"
|
||||
▼
|
||||
按记录 seek+write 到 /dev/app(或整镜像 dd)
|
||||
▼
|
||||
ROMFS 是只读挂载 + 有页缓存 → 必须重启才能重新读取
|
||||
▼
|
||||
重启后应用列表图标变了
|
||||
```
|
||||
|
||||
**"稍等片刻 + 重启"正是这条链路的指纹**:如果只是普通文件级改动,不需要重启;裸写分区必须重启,因为已经挂载的只读视图和缓存都是旧的。
|
||||
|
||||
> 这一节里,(1)(2)(3)(4) 是本次从固件包**实证**的;"表盘脚本直接写 `/dev/app`" 是**最符合全部观察的推断**,需要用那个图标包本身(看 `main.lua` 是否出现 `lvgl.fs.open_file`+`/dev/app`+`write`+`seek`)或实机日志来最终确认。
|
||||
> 另外一种可能是:表盘只是商店分类/容器格式,真正推送由工具箱的安装通道完成。区分方法同上——看包里有没有 Lua 写盘代码,以及 payload 是"几十 KB 的补丁表"还是"75 MB 的整分区镜像"。
|
||||
|
||||
---
|
||||
|
||||
## 4. 疑问二:为什么"只适用于特定固件版本"(例如 3.100.032)
|
||||
|
||||
先纠正一点:**锁版本的不是工具,是包里的那份 payload。** AstroBox 这类工具箱本身跨版本通用;写着"仅适用于 3.100.032"的是那份资源包。
|
||||
|
||||
原因有四层,前两层是技术必然,后两层是工程现实:
|
||||
|
||||
**① 绝对寻址 + 顺序排布(决定性原因)**
|
||||
ROMFS 把所有 inode、文件名和文件数据**首尾相接顺序排布**。补丁表里存的是**绝对偏移**(例如 `0x0f3a7e00`)。只要前面对任何一个文件的长度变了 —— 新增一个图标、换一个字体、改一句文案、某个 `.bin` 从 I8 变 ARGB8888(长度差 61184 字节)—— **后面所有偏移全部平移**。此时旧补丁会把图标字节写进别的文件里,轻则图标错乱,重则损坏资源分区。
|
||||
|
||||
这跟"为什么这个 APK 版本的汉化补丁不能用到下一个版本"是同一类问题:**不是逻辑变了,是逐字节布局变了。**
|
||||
|
||||
**② 一致性**
|
||||
资源分区与 AP/system 分区是配套发布的(资源清单、图标索引、字体/i18n 版本互相对应)。混搭会出现显示错乱,严重时起不来。所以包作者干脆要求"先降级到配套版本"。
|
||||
|
||||
**③ 官方 OTA 会整分区覆盖**
|
||||
任何系统升级都执行 `dd ... of=/dev/app`,**你的图标会被整体重写回去**。所以"锁版本"同时意味着"OTA 之后失效";如果升级后还拿旧偏移去写,就是往错误位置写数据。
|
||||
|
||||
**④ 作者的取材边界**
|
||||
那个包和它同一作者发布的[小米手环11固件](https://www.bandbbs.cn/resources/7955/)、[红米手表6固件](https://www.bandbbs.cn/resources/7243/)放在一起 —— 他是做固件 dump 的人,只为"手上有 dump 的那个版本"做偏移表。另外那份 S4 41mm 的包标的是 `3.100.032`,而你这台 eSIM 是 `3.202.79`,本来就不是同一条构建线。
|
||||
|
||||
**所以"难道每个版本都有不可替代的变化吗"——不是。** 是"按绝对偏移硬写"这个做法天生不能跨版本。**换一种做法就可以跨版本**(见下一节)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 更好的做法:不重建 ROMFS,按名字原地替换(已实现并验证)
|
||||
|
||||
因为 144×144 的图标换成另一个 144×144、同色彩格式的图标时,**字节长度完全相同**,所以可以:
|
||||
|
||||
> **在设备上解析 ROMFS → 按路径找到 `<app>/launcher.bin` → 用等长的新图标覆盖它的数据 → inode 表、super block、后续所有偏移全部不动。**
|
||||
|
||||
这样:
|
||||
- 不需要重建 ROMFS,不需要改 `size`/`checksum`/`next`;
|
||||
- 不需要"每版本一份包"——偏移是**运行时从当前固件里算出来的**,所以**天然跨固件版本**;
|
||||
- 传输量从"265 MB(或压缩 75 MB)整分区"降到"每个图标 83 KB / 22 KB"。
|
||||
|
||||
### 已验证的实现
|
||||
|
||||
工具:`tools/romfs.py`(ROMFS 解析/遍历)、`tools/iconpack.py`(make / apply / list)、`tools/render_icons.py`(解码为 PNG)。
|
||||
|
||||
验证结果:
|
||||
|
||||
| 验证项 | 结果 |
|
||||
|---|---|
|
||||
| ARGB8888 编码器与原固件字节级一致(alarm 图标往返) | ✅ 完全一致(82956 字节) |
|
||||
| I8 编码器长度一致 + 视觉误差 | ✅ 长度一致,平均绝对误差 0.22/255 |
|
||||
| 原地替换后条目数 / 路径 / inode 表 / super block / 文件总长 | ✅ 全部**逐位不变**(7834 条,`inode table identical: True`) |
|
||||
| 内容变化的文件 | ✅ 恰好只有被替换的那几个 `launcher.bin` |
|
||||
|
||||
用 4 个自制图标(alarm / timer / wxpay / settings)跑通了
|
||||
`make → apply → 校验 → 回读渲染`全流程,见 `_out/patched_custom.png`。
|
||||
|
||||
---
|
||||
|
||||
## 6. 还缺的那一环:写入通道
|
||||
|
||||
到这里,"**改什么、改成什么、怎么做到版本无关**"都已经解决。**唯一未解决的是"怎么把这几万字节送进 `/dev/app`"**:
|
||||
|
||||
| 方式 | 说明 | 代价 |
|
||||
|---|---|---|
|
||||
| 实机 shell `dd` | 最直接,需要能拿到 shell(或 Recovery) | 需要相应工具/权限 |
|
||||
| 表盘包 + 脚本写分区节点 | 社区现有做法,整链路已由本次分析解释清楚 | 属于滥用未公开行为,OTA 后失效;写错偏移会损坏分区 |
|
||||
| 重打包 OTA zip | ❌ 签名校验过不去 | 不可行 |
|
||||
|
||||
要在你的机器上落地,下一步需要的是:**在真机上把 `/dev/app`(或至少 ROMFS 头与目标 inode)读出来**,用 `iconpack.py list` 得到你这一版固件的真实偏移,再生成补丁。这样就不再受"包只适用于某某版本"的限制。
|
||||
|
||||
---
|
||||
|
||||
## 7. 风险提示
|
||||
|
||||
- 对**分区节点**写数据是文档明确警告的危险操作;写错偏移会破坏资源分区,可能导致无法开机。
|
||||
- 写之前**务必备份**原始分区(至少备份 ROMFS 头 + inode 表 + 你要改的 `launcher.bin` 原字节,能回滚)。
|
||||
- 系统 OTA 会覆盖你的修改;跨版本套用旧补丁 = 往错误位置写数据。
|
||||
- 修改系统资源分区可能影响保修;请自行确认设备所有权与当地法律。
|
||||
- `voice/alexa/launcher.bin`(6005 字节,`flags=0x0008`)格式尚未确定,不要动它。
|
||||
|
||||
---
|
||||
|
||||
## 附:工作目录
|
||||
|
||||
```
|
||||
_x/ 解包后的固件(含 13 个分区镜像)
|
||||
tools/romfs.py ROMFS 解析与遍历
|
||||
tools/iconpack.py list / make / apply 图标补丁
|
||||
tools/render_icons.py 把 launcher.bin 解码成 PNG
|
||||
tools/patch_demo.py 原地等长替换的概念验证
|
||||
tools/demo_icons.py 编码器自检 + 生成示例图标
|
||||
tools/verify_patch.py 补丁后元数据一致性校验
|
||||
_out/icons/ 从原厂固件导出的 38 个应用图标 PNG
|
||||
_out/launcher_icons_sheet.png 全部图标总览
|
||||
_out/before_after.png 原地替换前后对比
|
||||
_out/patched_custom.png 自制图标写入后的回读结果
|
||||
```
|
||||
@@ -0,0 +1,268 @@
|
||||
# Xiaomi Watch S4 eSIM(o62lte)系统应用图标替换 —— 固件包分析报告
|
||||
|
||||
分析对象:`c53475a6c7367c3583c6882665fd85ae_upd_mijia.watch.o62lte.bin`
|
||||
固件标识:`ro.build.version=3.202.79, build.time=2026-07-03 19:58:07, model=o62lte`
|
||||
机型字符串:`Xiaomi Watch S4 eSIM`
|
||||
包大小:199,385,001 字节
|
||||
|
||||
---
|
||||
|
||||
## 1. 固件包是什么
|
||||
|
||||
外层就是一个 **ZIP(Android SignApk 签名包)**,开头即 `PK\x03\x04`,第一个条目是 `ota.sh`。
|
||||
|
||||
```
|
||||
ota.sh 4524
|
||||
vela_app.bin 265684992 <- 应用资源分区(图标在这里)
|
||||
vela_watchface.bin 92967936
|
||||
vela_system.bin 72366080
|
||||
vela_font.bin 36146176
|
||||
vela_misc.bin 35799040
|
||||
vela_vendor.bin 17909760
|
||||
vela_ap.bin 11141120
|
||||
vela_quickapp.bin 5840896
|
||||
vela_i18n.bin 4399104
|
||||
vela_cp.bin 3174400
|
||||
vela_audio.bin 2730024
|
||||
vela_ota.bin 1441792
|
||||
vela_tee.bin 1048576
|
||||
META-INF/CERT.RSA / CERT.SF / MANIFEST.MF
|
||||
```
|
||||
|
||||
`ota.sh` 的全部动作就是逐分区裸写:
|
||||
|
||||
```sh
|
||||
dd if=/ota/vela_app.bin of=/dev/app bs=32768 verify
|
||||
dd if=/ota/vela_vendor.bin of=/dev/vendor bs=32768 verify
|
||||
dd if=/ota/vela_system.bin of=/dev/system bs=32768 verify
|
||||
... (vendor/i18n/app/quickapp/cp/misc/audio/tee/system/font/ap/watchface)
|
||||
```
|
||||
|
||||
签名信息:`CERT.SF` 里 `Created-By: 1.0 (Android SignApk)`、`X-Android-APK-Signed: 2, 3`,`MANIFEST.MF` 对每个分区镜像记录 SHA1。
|
||||
|
||||
> **结论:这份 OTA 包无法自行重新打包。** 改一个字节就过不了签名校验。
|
||||
|
||||
`vela_*.bin` 是 **Linux `romfs` 镜像**(magic `-rom1fs-`):
|
||||
|
||||
| 镜像 | 卷名 | 条目数 | romfs 大小 |
|
||||
|---|---|---|---|
|
||||
| vela_app.bin | app | 7834 | 265,684,928 |
|
||||
| vela_watchface.bin | watchface | 312 | 92,967,424 |
|
||||
| vela_system.bin | system | 1525 | 72,365,728 |
|
||||
| vela_font.bin | font | 15 | 36,145,776 |
|
||||
| vela_misc.bin | mcmisc | 1341 | 35,798,176 |
|
||||
| vela_vendor.bin | vendor | 58 | 17,909,328 |
|
||||
| vela_quickapp.bin | quickapp | 46 | 5,840,544 |
|
||||
| vela_i18n.bin | i18n | 277 | 4,399,072 |
|
||||
|
||||
在盘上它们挂到 `/resource/<卷名>`,所以 AP 二进制里出现的是 `/resource/app/...` 这样的绝对路径。
|
||||
|
||||
### ROMFS 结构(关键)
|
||||
|
||||
```
|
||||
super : "-rom1fs-" | u32 size | u32 checksum | name(NUL, 补齐16)
|
||||
inode : u32 next | u32 spec | u32 size | u32 checksum | name(NUL, 补齐16) | 文件数据紧跟其后
|
||||
next 低 4 位 = 类型:0=硬链接(spec=目标inode偏移) 1=目录(spec=首个子项偏移) 2=普通文件 8=可执行
|
||||
```
|
||||
|
||||
**目录项与文件数据是严格顺序紧密排布的**,每一项的偏移完全由它前面所有内容的长度决定。
|
||||
|
||||
---
|
||||
|
||||
## 2. 系统自带应用的图标在哪里(已定位)
|
||||
|
||||
AP 二进制里写死了一批路径,直接给出答案:
|
||||
|
||||
```
|
||||
/resource/app/alarm/launcher.bin
|
||||
/resource/app/activities/launcher.bin
|
||||
/resource/app/calendar/launcher.bin
|
||||
/resource/app/heartrate/launcher.bin
|
||||
... 共 39 个
|
||||
```
|
||||
|
||||
精确统计:**38 个 `<app>/launcher.bin`** 加 **1 个 `phone/contacts_launcher.bin`**(联系人图标),共 39 个。
|
||||
|
||||
即 **每个系统应用目录下都有一个 `launcher.bin`,它就是"应用列表/启动器"里显示的那个图标**。全部位于 `vela_app.bin`(=`/dev/app` 分区,265 MB)。
|
||||
|
||||
39 个图标全部是 **LVGL v9 二进制图片**:
|
||||
|
||||
```
|
||||
u8 magic=0x19 | u8 color_format | u16 flags | u16 w | u16 h | u16 stride | u16 reserved
|
||||
cf=0x10 ARGB8888 : 12 + 144*144*4 = 82956 字节
|
||||
cf=0x0a I8 : 12 + 256*4 + 144*144 = 21772 字节
|
||||
```
|
||||
|
||||
已全部解码验证(38/39,`voice/alexa/launcher.bin` 是 6005 字节的特例,`flags=0x0008`,格式待定)。
|
||||
|
||||
### 图标清单(偏移为 `vela_app.bin` 内绝对偏移,固件 3.202.79 专用)
|
||||
|
||||
| 应用 | 数据偏移 | 长度 | 尺寸 | 格式 |
|
||||
|---|---|---|---|---|
|
||||
| wxpay | 0x0000bc00 | 21772 | 144x144 | I8 |
|
||||
| womenhealth | 0x00047a00 | 82956 | 144x144 | ARGB8888 |
|
||||
| weather | 0x017de200 | 82956 | 144x144 | ARGB8888 |
|
||||
| walkie_talkie | 0x02d45600 | 82956 | 144x144 | ARGB8888 |
|
||||
| voice/alexa | 0x032fa000 | 6005 | 144x144 | I8(特例) |
|
||||
| voice/aivs | 0x0341c400 | 82956 | 144x144 | ARGB8888 |
|
||||
| todolist | 0x03aa9000 | 82956 | 144x144 | ARGB8888 |
|
||||
| timer | 0x03ad3c00 | 82956 | 144x144 | ARGB8888 |
|
||||
| sports/training | 0x0466b000 | 82956 | 144x144 | ARGB8888 |
|
||||
| sports/record | 0x04a63000 | 82956 | 144x144 | ARGB8888 |
|
||||
| sports | 0x04baee00 | 82956 | 144x144 | ARGB8888 |
|
||||
| sports/course | 0x05132400 | 82956 | 144x144 | ARGB8888 |
|
||||
| sleep | 0x05156400 | 82956 | 144x144 | ARGB8888 |
|
||||
| settings | 0x052e8000 | 82956 | 144x144 | ARGB8888 |
|
||||
| remote_camera | 0x06593a00 | 21772 | 144x144 | I8 |
|
||||
| recorder | 0x067a4600 | 82956 | 144x144 | ARGB8888 |
|
||||
| pressure | 0x07908400 | 82956 | 144x144 | ARGB8888 |
|
||||
| phone | 0x07a4d000 | 82956 | 144x144 | ARGB8888 |
|
||||
| phone/contacts_launcher | 0x07a82200 | 82956 | 144x144 | ARGB8888 |
|
||||
| perpetual_calendar | 0x07b3be00 | 21772 | 144x144 | I8 |
|
||||
| oxygen | 0x08f08c00 | 82956 | 144x144 | ARGB8888 |
|
||||
| nfccard | 0x093f2c00 | 82956 | 144x144 | ARGB8888 |
|
||||
| mijia | 0x09599200 | 82956 | 144x144 | ARGB8888 |
|
||||
| media | 0x0968e200 | 82956 | 144x144 | ARGB8888 |
|
||||
| lua | 0x096b5200 | 21772 | 144x144 | I8 |
|
||||
| interconnect | 0x096d8000 | 82956 | 144x144 | ARGB8888 |
|
||||
| innovation_research | 0x096efa00 | 82956 | 144x144 | ARGB8888 |
|
||||
| heartrate | 0x09fea800 | 82956 | 144x144 | ARGB8888 |
|
||||
| health_today | 0x0a223a00 | 82956 | 144x144 | ARGB8888 |
|
||||
| flashlight | 0x0b6dba00 | 82956 | 144x144 | ARGB8888 |
|
||||
| find_phone | 0x0b6f0000 | 82956 | 144x144 | ARGB8888 |
|
||||
| esimsms | 0x0b762200 | 82956 | 144x144 | ARGB8888 |
|
||||
| debug | 0x0b81a200 | 21772 | 144x144 | I8 |
|
||||
| compass | 0x0b8a1400 | 21772 | 144x144 | I8 |
|
||||
| chronograph | 0x0ba47e00 | 82956 | 144x144 | ARGB8888 |
|
||||
| calendar | 0x0bc4b200 | 82956 | 144x144 | ARGB8888 |
|
||||
| breath | 0x0bd54c00 | 82956 | 144x144 | ARGB8888 |
|
||||
| barometer | 0x0e825c00 | 82956 | 144x144 | ARGB8888 |
|
||||
| alarm | 0x0f3a7e00 | 82956 | 144x144 | ARGB8888 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 疑问一:为什么"一个 Lua 表盘点一下就替换成功了"
|
||||
|
||||
从这份固件包里能**确证**的事实:
|
||||
|
||||
1. 系统应用图标 = `vela_app.bin` 里的 `launcher.bin`,而 `vela_app.bin` 是**整个 265 MB 分区镜像**;
|
||||
2. 官方通道 `ota.sh` 的写入方式是 `dd ... of=/dev/app`,**裸写分区**;
|
||||
3. 官方 OTA 包是 **SignApk v2/v3 签名的**,因此**不可能**通过"重新打包 OTA"来替换图标;
|
||||
4. 分区设备节点 `/dev/app`、`/dev/system` 等对用户态是**可打开的字符/块设备**(LoXe 文档也专门警告"不要直接试写未知设备,对分区节点 `dd`/重定向可能立即破坏系统")。
|
||||
|
||||
把这四点连起来,社区图标包只能走一条路:**绕过签名校验,直接把图标字节写进分区**。
|
||||
|
||||
所以那个"Lua 表盘"的角色是**运载 + 执行载体**,而不是"表盘 API 能设置图标":
|
||||
|
||||
```
|
||||
图标包(做成表盘包的样子)
|
||||
├── asset/*.bin 实际是一个"补丁表":[(绝对偏移, 该长度字节), ...]
|
||||
└── main.lua ui.init / 按钮回调里做写入
|
||||
│ 点击"立即替换"
|
||||
▼
|
||||
按记录 seek+write 到 /dev/app(或整镜像 dd)
|
||||
▼
|
||||
ROMFS 是只读挂载 + 有页缓存 → 必须重启才能重新读取
|
||||
▼
|
||||
重启后应用列表图标变了
|
||||
```
|
||||
|
||||
**"稍等片刻 + 重启"正是这条链路的指纹**:如果只是普通文件级改动,不需要重启;裸写分区必须重启,因为已经挂载的只读视图和缓存都是旧的。
|
||||
|
||||
> 这一节里,(1)(2)(3)(4) 是本次从固件包**实证**的;"表盘脚本直接写 `/dev/app`" 是**最符合全部观察的推断**,需要用那个图标包本身(看 `main.lua` 是否出现 `lvgl.fs.open_file`+`/dev/app`+`write`+`seek`)或实机日志来最终确认。
|
||||
> 另外一种可能是:表盘只是商店分类/容器格式,真正推送由工具箱的安装通道完成。区分方法同上——看包里有没有 Lua 写盘代码,以及 payload 是"几十 KB 的补丁表"还是"75 MB 的整分区镜像"。
|
||||
|
||||
---
|
||||
|
||||
## 4. 疑问二:为什么"只适用于特定固件版本"(例如 3.100.032)
|
||||
|
||||
先纠正一点:**锁版本的不是工具,是包里的那份 payload。** AstroBox 这类工具箱本身跨版本通用;写着"仅适用于 3.100.032"的是那份资源包。
|
||||
|
||||
原因有四层,前两层是技术必然,后两层是工程现实:
|
||||
|
||||
**① 绝对寻址 + 顺序排布(决定性原因)**
|
||||
ROMFS 把所有 inode、文件名和文件数据**首尾相接顺序排布**。补丁表里存的是**绝对偏移**(例如 `0x0f3a7e00`)。只要前面对任何一个文件的长度变了 —— 新增一个图标、换一个字体、改一句文案、某个 `.bin` 从 I8 变 ARGB8888(长度差 61184 字节)—— **后面所有偏移全部平移**。此时旧补丁会把图标字节写进别的文件里,轻则图标错乱,重则损坏资源分区。
|
||||
|
||||
这跟"为什么这个 APK 版本的汉化补丁不能用到下一个版本"是同一类问题:**不是逻辑变了,是逐字节布局变了。**
|
||||
|
||||
**② 一致性**
|
||||
资源分区与 AP/system 分区是配套发布的(资源清单、图标索引、字体/i18n 版本互相对应)。混搭会出现显示错乱,严重时起不来。所以包作者干脆要求"先降级到配套版本"。
|
||||
|
||||
**③ 官方 OTA 会整分区覆盖**
|
||||
任何系统升级都执行 `dd ... of=/dev/app`,**你的图标会被整体重写回去**。所以"锁版本"同时意味着"OTA 之后失效";如果升级后还拿旧偏移去写,就是往错误位置写数据。
|
||||
|
||||
**④ 作者的取材边界**
|
||||
那个包和它同一作者发布的[小米手环11固件](https://www.bandbbs.cn/resources/7955/)、[红米手表6固件](https://www.bandbbs.cn/resources/7243/)放在一起 —— 他是做固件 dump 的人,只为"手上有 dump 的那个版本"做偏移表。另外那份 S4 41mm 的包标的是 `3.100.032`,而你这台 eSIM 是 `3.202.79`,本来就不是同一条构建线。
|
||||
|
||||
**所以"难道每个版本都有不可替代的变化吗"——不是。** 是"按绝对偏移硬写"这个做法天生不能跨版本。**换一种做法就可以跨版本**(见下一节)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 更好的做法:不重建 ROMFS,按名字原地替换(已实现并验证)
|
||||
|
||||
因为 144×144 的图标换成另一个 144×144、同色彩格式的图标时,**字节长度完全相同**,所以可以:
|
||||
|
||||
> **在设备上解析 ROMFS → 按路径找到 `<app>/launcher.bin` → 用等长的新图标覆盖它的数据 → inode 表、super block、后续所有偏移全部不动。**
|
||||
|
||||
这样:
|
||||
- 不需要重建 ROMFS,不需要改 `size`/`checksum`/`next`;
|
||||
- 不需要"每版本一份包"——偏移是**运行时从当前固件里算出来的**,所以**天然跨固件版本**;
|
||||
- 传输量从"265 MB(或压缩 75 MB)整分区"降到"每个图标 83 KB / 22 KB"。
|
||||
|
||||
### 已验证的实现
|
||||
|
||||
工具:`_tools/romfs.py`(ROMFS 解析/遍历)、`_tools/iconpack.py`(make / apply / list)、`_tools/render_icons.py`(解码为 PNG)。
|
||||
|
||||
验证结果:
|
||||
|
||||
| 验证项 | 结果 |
|
||||
|---|---|
|
||||
| ARGB8888 编码器与原固件字节级一致(alarm 图标往返) | ✅ 完全一致(82956 字节) |
|
||||
| I8 编码器长度一致 + 视觉误差 | ✅ 长度一致,平均绝对误差 0.22/255 |
|
||||
| 原地替换后条目数 / 路径 / inode 表 / super block / 文件总长 | ✅ 全部**逐位不变**(7834 条,`inode table identical: True`) |
|
||||
| 内容变化的文件 | ✅ 恰好只有被替换的那几个 `launcher.bin` |
|
||||
|
||||
用 4 个自制图标(alarm / timer / wxpay / settings)跑通了
|
||||
`make → apply → 校验 → 回读渲染`全流程,见 `_out/patched_custom.png`。
|
||||
|
||||
---
|
||||
|
||||
## 6. 还缺的那一环:写入通道
|
||||
|
||||
到这里,"**改什么、改成什么、怎么做到版本无关**"都已经解决。**唯一未解决的是"怎么把这几万字节送进 `/dev/app`"**:
|
||||
|
||||
| 方式 | 说明 | 代价 |
|
||||
|---|---|---|
|
||||
| 实机 shell `dd` | 最直接,需要能拿到 shell(或 Recovery) | 需要相应工具/权限 |
|
||||
| 表盘包 + 脚本写分区节点 | 社区现有做法,整链路已由本次分析解释清楚 | 属于滥用未公开行为,OTA 后失效;写错偏移会损坏分区 |
|
||||
| 重打包 OTA zip | ❌ 签名校验过不去 | 不可行 |
|
||||
|
||||
要在你的机器上落地,下一步需要的是:**在真机上把 `/dev/app`(或至少 ROMFS 头与目标 inode)读出来**,用 `iconpack.py list` 得到你这一版固件的真实偏移,再生成补丁。这样就不再受"包只适用于某某版本"的限制。
|
||||
|
||||
---
|
||||
|
||||
## 7. 风险提示
|
||||
|
||||
- 对**分区节点**写数据是文档明确警告的危险操作;写错偏移会破坏资源分区,可能导致无法开机。
|
||||
- 写之前**务必备份**原始分区(至少备份 ROMFS 头 + inode 表 + 你要改的 `launcher.bin` 原字节,能回滚)。
|
||||
- 系统 OTA 会覆盖你的修改;跨版本套用旧补丁 = 往错误位置写数据。
|
||||
- 修改系统资源分区可能影响保修;请自行确认设备所有权与当地法律。
|
||||
- `voice/alexa/launcher.bin`(6005 字节,`flags=0x0008`)格式尚未确定,不要动它。
|
||||
|
||||
---
|
||||
|
||||
## 附:工作目录
|
||||
|
||||
```
|
||||
_x/ 解包后的固件(含 13 个分区镜像)
|
||||
_tools/romfs.py ROMFS 解析与遍历
|
||||
_tools/iconpack.py list / make / apply 图标补丁
|
||||
_tools/render_icons.py 把 launcher.bin 解码成 PNG
|
||||
_tools/patch_demo.py 原地等长替换的概念验证
|
||||
_tools/demo_icons.py 编码器自检 + 生成示例图标
|
||||
_tools/verify_patch.py 补丁后元数据一致性校验
|
||||
_out/icons/ 从原厂固件导出的 38 个应用图标 PNG
|
||||
_out/launcher_icons_sheet.png 全部图标总览
|
||||
_out/before_after.png 原地替换前后对比
|
||||
_out/patched_custom.png 自制图标写入后的回读结果
|
||||
```
|
||||
@@ -0,0 +1,139 @@
|
||||
> 本文是当时的分析 / 交付存档,路径按当时的工作目录写。
|
||||
> 本项目里的对应关系:tools 对应 _tools,inputs/target/vela_app.bin 对应 _x/vela_app.bin,
|
||||
> inputs/source 对应 _s5,.work 对应 _out(产物),dist 对应 _out/faces。
|
||||
|
||||
# S5 图标包 → S4 eSIM 替换包
|
||||
|
||||
把 **Xiaomi Watch S5(q63, v4.101.020)** 的系统应用图标,写进 **Xiaomi Watch S4 eSIM(o62lte)**
|
||||
的 `/dev/app` 分区。
|
||||
|
||||
## 文件
|
||||
|
||||
| 文件 | 说明 |
|
||||
|---|---|
|
||||
| `main.lua` | Lua 表盘入口(LVGL 界面 + dd 写入/校验状态机) |
|
||||
| `payload.lua` | 负载清单:38 条记录(1 条超级块 canary + 37 个图标) |
|
||||
| `payload/` | 逐条的图标字节,文件名与 `payload.lua` 对应 |
|
||||
| `payload_rollback.lua` + `payload_rollback/` | 同样 38 条,内容为 S4 原厂字节(回滚用) |
|
||||
| `flash.sh` / `rollback.sh` | **不需要表盘**:有 nsh shell 时直接跑这两条脚本 |
|
||||
| `payload.json` / `manifest.json` | 机器可读的明细(偏移、长度、格式、来源) |
|
||||
| `README.md` | 本文件 |
|
||||
|
||||
## 机制:走官方 OTA 同一条路
|
||||
|
||||
Lua 表盘无法"设置图标"。系统图标住在 `/dev/app` 这个 265 MB 的 ROMFS 分区镜像里
|
||||
(`/resource/app/<应用>/launcher.bin`),必须**写入分区 + 重启**才会生效。
|
||||
|
||||
关键发现来自 [`docs-shell`](../../):设备侧有 `dd` / `cmp` / `md5` / `reboot`,
|
||||
而 Lua 里 **`os.execute` 可用**(`io.popen` 不可用)。固件自带的 `ota.sh` 正是这么写分区的:
|
||||
|
||||
```sh
|
||||
dd if=/ota/vela_app.bin of=/dev/app bs=32768 verify
|
||||
```
|
||||
|
||||
所以本包对每条记录执行三步,全程只用官方存在的命令:
|
||||
|
||||
```sh
|
||||
dd if=payload/NNN.bin of=/dev/app bs=4 seek=<offset/4> count=<length/4>
|
||||
dd if=/dev/app of=/tmp/chk.bin bs=4 skip=<offset/4> count=<length/4>
|
||||
cmp /tmp/chk.bin payload/NNN.bin # 退出码 0 = 一致
|
||||
```
|
||||
|
||||
**为什么 `bs=4`**:所有目标偏移都是 512 对齐,且每条的长度都是 4 的倍数
|
||||
(82956 / 21772 / 32 都能被 4 整除),所以 `bs=4` 正好写满目标区间、
|
||||
**不会多写一个字节到相邻文件里**,也不会产生不完整的尾块。
|
||||
|
||||
最后 `reboot` 让只读挂载重新读取分区 —— 这就是"点一下、稍等片刻、手表重启、替换成功"
|
||||
那条链路。
|
||||
|
||||
## 关于"打包方法"
|
||||
|
||||
我把 `docs-lua` 全部 68 个文件读完了:**里面没有表盘打包/安装的说明。** 它给出的是**运行时契约**:
|
||||
|
||||
| 事实 | 出处 |
|
||||
|---|---|
|
||||
| 内建表盘源码就在 `/watchface/lua/<名字>/`(circle, clearheart, complexzone, corona, daynight) | `lvgl/modules/fs.md` |
|
||||
| 表盘资源 `/watchface/`(`resource.bin`、`aod`) | 同上 |
|
||||
| **已安装表盘在 `/data/app/watchface/`(builtin / market)** | 同上 |
|
||||
| 资源用绝对路径引用,如 `/watchface/lua/corona/image/sec.bin` | `lvgl/widgets/analogtime.md` |
|
||||
| 入口:`init(style)` 返回根对象;全局 `pageOnPause` / `pageOnResume` | `activity/overview.md` |
|
||||
| `/data` fatfs 可读写;`/tmp` tmpfs 可读写(重启清空);其余系统目录 romfs **只读** | `docs-shell/syntax-and-environment.md` |
|
||||
| `pm install <包名\|rpk>` / `am start <包名>` 是**快应用**的包管理器 | `docs-shell/runtimes-and-media.md` |
|
||||
|
||||
也就是说:**Lua 表盘本身就是一个目录**(`.lua` + 资源),没有专用打包格式;
|
||||
真正没被文档覆盖的是"怎么把这个目录推上手表、怎么在 `watchface.db` 里注册"。
|
||||
这一步取决于你所用的工具(AstroBox / 表盘自定义工具等)。
|
||||
|
||||
**好消息是这一步对本项目不是必需的** —— 见下面两条替代路线。
|
||||
|
||||
## 三条路线(按推荐顺序)
|
||||
|
||||
### 路线 A:`flash.sh`(最简单,只要能拿到一次 shell)
|
||||
|
||||
把 `flash.sh`、`payload/` 复制到手表 `/data/s5icons/`,然后:
|
||||
|
||||
```sh
|
||||
sh /data/s5icons/flash.sh
|
||||
reboot
|
||||
```
|
||||
|
||||
不需要表盘、不需要打包、不依赖表盘宿主权限。回滚用 `rollback.sh`。
|
||||
|
||||
### 路线 B:Lua 表盘(`main.lua`)
|
||||
|
||||
按你需要的形态提供。界面三个按钮 + 回滚:
|
||||
|
||||
1. **① 只读检查**(不写任何字节):`dd` 读出分区头,确认 magic 是 `-rom1fs-`、
|
||||
卷名是 `app`;再逐条确认负载文件都在、长度正确。**只有检查通过,② 才解锁。**
|
||||
2. **② 写入并校验**:逐条 `dd` 写入 → `dd` 读回 → `cmp` 比对,带进度条。
|
||||
3. **③ 重启**:`sync` + `reboot`。
|
||||
4. **回滚**:用 `payload_rollback` 走同一套流程。
|
||||
|
||||
分片执行(定时器每 40 ms 一条),界面不会卡死;每条失败都会计数并在结束时报告。
|
||||
|
||||
### 路线 C:直接刷整分区(最稳)
|
||||
|
||||
```
|
||||
_out/s4_from_s5/vela_app_s5icons.bin 已改好图标的 vela_app.bin(265 MB)
|
||||
```
|
||||
|
||||
用你已有的刷机通道写进 `/dev/app`。完全绕开设备侧脚本。
|
||||
|
||||
## 替换内容
|
||||
|
||||
S4 有 40 个 launcher 图标槽位:
|
||||
|
||||
- **37 个真正被改写**(字节有差异)
|
||||
- **2 个字节完全相同,已从负载中剔除**:`voice_alexa`、`wxpay`
|
||||
- **1 个 S4 独有、S5 没有**:`walkie_talkie`,保持原厂
|
||||
- S5 独有的 12 个(`launcher_*` 第三方图标、`temperature`、`calorie_management` 等)按约定跳过
|
||||
|
||||
编码策略:S5 文件长度 == S4 槽位长度 → 原样拷贝;否则解码 S5 图标后按 **S4 槽位的色彩格式**
|
||||
重新编码,确保长度逐字节相等。结果:**8090 条目录项、inode 表、superblock 逐位不变**。
|
||||
|
||||
验证过的等价性:把这些负载按偏移重放进原厂镜像,得到的文件与我逐字节验证过的
|
||||
`vela_app_s5icons.bin` **完全相同(sha256 一致)**。
|
||||
|
||||
观感差异:10 个视觉上无变化,29 个有可见差异,多数是重新配色/描边
|
||||
(例如 `barometer` 的底盘由蓝变白、`activities` 的渐变换了)。
|
||||
|
||||
## ⚠ 未验证项
|
||||
|
||||
我**无法在真机上测试**,以下三点请自行确认:
|
||||
|
||||
1. **`os.execute` 是否在表盘宿主里真的可用** —— `docs-lua/intro/overview.md` 标为 ✅,
|
||||
但那是模拟器环境的结论;`docs-lua/intro/conventions.md` 也写明"接口通过逆向分析获得,
|
||||
不同设备可用性不一致"。
|
||||
2. **`SCRIPT_PATH` 是否注入、值是什么** —— 这是 LoXe 文档的约定,不在本仓库文档里。
|
||||
`main.lua` 里已有兜底常量,若加载失败请改成实际路径。
|
||||
3. **写入 `dd` 的权限** —— 表盘宿主进程是否有权写 `/dev/app`。`docs-shell` 只说明命令存在,
|
||||
不代表当前进程有权限。
|
||||
|
||||
**第一次请只按「① 只读检查」**,看清输出再决定是否写入。或者直接走路线 A/C。
|
||||
|
||||
## 风险
|
||||
|
||||
- 往分区节点写数据是危险操作,写错位置可能导致无法开机。
|
||||
- 系统 OTA 升级会整分区覆盖 `vela_app.bin`,图标会回到原厂(等于自动还原)。
|
||||
- 修改系统资源分区可能影响保修;请自行确认设备所有权与当地法律。
|
||||
- **动手前请确认你能刷回原厂 `vela_app.bin`**(路线 C 那份镜像,或官方固件包里的同名文件)。
|
||||
@@ -0,0 +1,66 @@
|
||||
# 图标命名对照表 —— Xiaomi Watch S4 eSIM (o62lte, 固件 3.202.79)
|
||||
|
||||
把你的 **144×144 PNG** 按下面的「PNG 文件名」命名,放进同一个目录,我就能一次性生成完整补丁包。
|
||||
|
||||
- 共 **39** 个可替换图标
|
||||
- 文件名规则:目录层级用 `_` 连接,去掉结尾的 `launcher`
|
||||
(`alarm/launcher.bin` → `alarm.png`,`sports/training/launcher.bin` → `sports_training.png`)
|
||||
- PNG 请带透明通道(RGBA);圆形外圈留透明即可,不要自己画背景
|
||||
|
||||
| PNG 文件名 | 原厂尺寸 | 槽位格式 | 槽位字节 | 数据偏移 | 注意 |
|
||||
|---|---|---|---|---|---|
|
||||
| `alarm.png` | 144x144 | ARGB8888 | 82956 | `0x0f3a7e00` | |
|
||||
| `barometer.png` | 144x144 | ARGB8888 | 82956 | `0x0e825c00` | |
|
||||
| `breath.png` | 144x144 | ARGB8888 | 82956 | `0x0bd54c00` | |
|
||||
| `calendar.png` | 144x144 | ARGB8888 | 82956 | `0x0bc4b200` | |
|
||||
| `chronograph.png` | 144x144 | ARGB8888 | 82956 | `0x0ba47e00` | |
|
||||
| `compass.png` | 144x144 | I8 | 21772 | `0x0b8a1400` | ⚠ 需 I8 编码(256 色) |
|
||||
| `debug.png` | 144x144 | I8 | 21772 | `0x0b81a200` | ⚠ 需 I8 编码(256 色) |
|
||||
| `esimsms.png` | 144x144 | ARGB8888 | 82956 | `0x0b762200` | |
|
||||
| `find_phone.png` | 144x144 | ARGB8888 | 82956 | `0x0b6f0000` | |
|
||||
| `flashlight.png` | 144x144 | ARGB8888 | 82956 | `0x0b6dba00` | |
|
||||
| `health_today.png` | 144x144 | ARGB8888 | 82956 | `0x0a223a00` | |
|
||||
| `heartrate.png` | 144x144 | ARGB8888 | 82956 | `0x09fea800` | |
|
||||
| `innovation_research.png` | 144x144 | ARGB8888 | 82956 | `0x096efa00` | |
|
||||
| `interconnect.png` | 144x144 | ARGB8888 | 82956 | `0x096d8000` | |
|
||||
| `lua.png` | 144x144 | I8 | 21772 | `0x096b5200` | ⚠ 需 I8 编码(256 色) |
|
||||
| `media.png` | 144x144 | ARGB8888 | 82956 | `0x0968e200` | |
|
||||
| `mijia.png` | 144x144 | ARGB8888 | 82956 | `0x09599200` | |
|
||||
| `nfccard.png` | 144x144 | ARGB8888 | 82956 | `0x093f2c00` | |
|
||||
| `oxygen.png` | 144x144 | ARGB8888 | 82956 | `0x08f08c00` | |
|
||||
| `perpetual_calendar.png` | 144x144 | I8 | 21772 | `0x07b3be00` | ⚠ 需 I8 编码(256 色) |
|
||||
| `phone.png` | 144x144 | ARGB8888 | 82956 | `0x07a4d000` | |
|
||||
| `phone_contacts.png` | 144x144 | ARGB8888 | 82956 | `0x07a82200` | |
|
||||
| `pressure.png` | 144x144 | ARGB8888 | 82956 | `0x07908400` | |
|
||||
| `recorder.png` | 144x144 | ARGB8888 | 82956 | `0x067a4600` | |
|
||||
| `remote_camera.png` | 144x144 | I8 | 21772 | `0x06593a00` | ⚠ 需 I8 编码(256 色) |
|
||||
| `settings.png` | 144x144 | ARGB8888 | 82956 | `0x052e8000` | |
|
||||
| `sleep.png` | 144x144 | ARGB8888 | 82956 | `0x05156400` | |
|
||||
| `sports.png` | 144x144 | ARGB8888 | 82956 | `0x04baee00` | |
|
||||
| `sports_course.png` | 144x144 | ARGB8888 | 82956 | `0x05132400` | |
|
||||
| `sports_record.png` | 144x144 | ARGB8888 | 82956 | `0x04a63000` | |
|
||||
| `sports_training.png` | 144x144 | ARGB8888 | 82956 | `0x0466b000` | |
|
||||
| `timer.png` | 144x144 | ARGB8888 | 82956 | `0x03ad3c00` | |
|
||||
| `todolist.png` | 144x144 | ARGB8888 | 82956 | `0x03aa9000` | |
|
||||
| `voice_aivs.png` | 144x144 | ARGB8888 | 82956 | `0x0341c400` | |
|
||||
| `voice_alexa.png` | 144x144 | I8 | 6005 | `0x032fa000` | ⛔ 格式特殊,暂不支持 |
|
||||
| `walkie_talkie.png` | 144x144 | ARGB8888 | 82956 | `0x02d45600` | |
|
||||
| `weather.png` | 144x144 | ARGB8888 | 82956 | `0x017de200` | |
|
||||
| `womenhealth.png` | 144x144 | ARGB8888 | 82956 | `0x00047a00` | |
|
||||
| `wxpay.png` | 144x144 | I8 | 21772 | `0x0000bc00` | ⚠ 需 I8 编码(256 色) |
|
||||
|
||||
## 两点必须注意
|
||||
|
||||
**1. 槽位字节必须完全相等。** ROMFS 是绝对偏移顺序排布,替换文件的字节长度必须和原文件一模一样,否则后面所有文件的偏移整体平移、镜像损坏。正因为你的 PNG 是 144×144、与原厂一致,编码后长度天然相等,可以直接原地覆盖。
|
||||
|
||||
**2. 标注「需 I8 编码」的那几个,会被自动量化成 256 色。** 它们的槽位只有 21772 字节,装不下 ARGB8888(82956 字节),所以只能用索引色。实测对扁平图标平均误差 0.22/255,肉眼无差别;但如果你的图标有渐变/照片级内容,这几个会有可见色带。
|
||||
|
||||
`voice_alexa` 是 6005 字节的特例(`flags=0x0008`),格式未确定,建议先不动。
|
||||
|
||||
## 生成命令
|
||||
|
||||
```
|
||||
python _tools/iconpack.py make _x/vela_app.bin <你的PNG目录> _out/mypack --patched
|
||||
```
|
||||
|
||||
产出:`icons.patch`(补丁表)、`icons.rollback`(回滚包)、`payload/`(每个图标的 .bin)、`manifest.json`(含偏移与 SHA256)、`vela_app_patched.bin`(可直接写入的整分区镜像)。
|
||||
@@ -0,0 +1,70 @@
|
||||
> 本文是当时的分析 / 交付存档,路径按当时的工作目录写。
|
||||
> 本项目里的对应关系:tools 对应 _tools,inputs/target/vela_app.bin 对应 _x/vela_app.bin,
|
||||
> inputs/source 对应 _s5,.work 对应 _out(产物),dist 对应 _out/faces。
|
||||
|
||||
# 图标命名对照表 —— Xiaomi Watch S4 eSIM (o62lte, 固件 3.202.79)
|
||||
|
||||
把你的 **144×144 PNG** 按下面的「PNG 文件名」命名,放进同一个目录,我就能一次性生成完整补丁包。
|
||||
|
||||
- 共 **39** 个可替换图标
|
||||
- 文件名规则:目录层级用 `_` 连接,去掉结尾的 `launcher`
|
||||
(`alarm/launcher.bin` → `alarm.png`,`sports/training/launcher.bin` → `sports_training.png`)
|
||||
- PNG 请带透明通道(RGBA);圆形外圈留透明即可,不要自己画背景
|
||||
|
||||
| PNG 文件名 | 原厂尺寸 | 槽位格式 | 槽位字节 | 数据偏移 | 注意 |
|
||||
|---|---|---|---|---|---|
|
||||
| `alarm.png` | 144x144 | ARGB8888 | 82956 | `0x0f3a7e00` | |
|
||||
| `barometer.png` | 144x144 | ARGB8888 | 82956 | `0x0e825c00` | |
|
||||
| `breath.png` | 144x144 | ARGB8888 | 82956 | `0x0bd54c00` | |
|
||||
| `calendar.png` | 144x144 | ARGB8888 | 82956 | `0x0bc4b200` | |
|
||||
| `chronograph.png` | 144x144 | ARGB8888 | 82956 | `0x0ba47e00` | |
|
||||
| `compass.png` | 144x144 | I8 | 21772 | `0x0b8a1400` | ⚠ 需 I8 编码(256 色) |
|
||||
| `debug.png` | 144x144 | I8 | 21772 | `0x0b81a200` | ⚠ 需 I8 编码(256 色) |
|
||||
| `esimsms.png` | 144x144 | ARGB8888 | 82956 | `0x0b762200` | |
|
||||
| `find_phone.png` | 144x144 | ARGB8888 | 82956 | `0x0b6f0000` | |
|
||||
| `flashlight.png` | 144x144 | ARGB8888 | 82956 | `0x0b6dba00` | |
|
||||
| `health_today.png` | 144x144 | ARGB8888 | 82956 | `0x0a223a00` | |
|
||||
| `heartrate.png` | 144x144 | ARGB8888 | 82956 | `0x09fea800` | |
|
||||
| `innovation_research.png` | 144x144 | ARGB8888 | 82956 | `0x096efa00` | |
|
||||
| `interconnect.png` | 144x144 | ARGB8888 | 82956 | `0x096d8000` | |
|
||||
| `lua.png` | 144x144 | I8 | 21772 | `0x096b5200` | ⚠ 需 I8 编码(256 色) |
|
||||
| `media.png` | 144x144 | ARGB8888 | 82956 | `0x0968e200` | |
|
||||
| `mijia.png` | 144x144 | ARGB8888 | 82956 | `0x09599200` | |
|
||||
| `nfccard.png` | 144x144 | ARGB8888 | 82956 | `0x093f2c00` | |
|
||||
| `oxygen.png` | 144x144 | ARGB8888 | 82956 | `0x08f08c00` | |
|
||||
| `perpetual_calendar.png` | 144x144 | I8 | 21772 | `0x07b3be00` | ⚠ 需 I8 编码(256 色) |
|
||||
| `phone.png` | 144x144 | ARGB8888 | 82956 | `0x07a4d000` | |
|
||||
| `phone_contacts.png` | 144x144 | ARGB8888 | 82956 | `0x07a82200` | |
|
||||
| `pressure.png` | 144x144 | ARGB8888 | 82956 | `0x07908400` | |
|
||||
| `recorder.png` | 144x144 | ARGB8888 | 82956 | `0x067a4600` | |
|
||||
| `remote_camera.png` | 144x144 | I8 | 21772 | `0x06593a00` | ⚠ 需 I8 编码(256 色) |
|
||||
| `settings.png` | 144x144 | ARGB8888 | 82956 | `0x052e8000` | |
|
||||
| `sleep.png` | 144x144 | ARGB8888 | 82956 | `0x05156400` | |
|
||||
| `sports.png` | 144x144 | ARGB8888 | 82956 | `0x04baee00` | |
|
||||
| `sports_course.png` | 144x144 | ARGB8888 | 82956 | `0x05132400` | |
|
||||
| `sports_record.png` | 144x144 | ARGB8888 | 82956 | `0x04a63000` | |
|
||||
| `sports_training.png` | 144x144 | ARGB8888 | 82956 | `0x0466b000` | |
|
||||
| `timer.png` | 144x144 | ARGB8888 | 82956 | `0x03ad3c00` | |
|
||||
| `todolist.png` | 144x144 | ARGB8888 | 82956 | `0x03aa9000` | |
|
||||
| `voice_aivs.png` | 144x144 | ARGB8888 | 82956 | `0x0341c400` | |
|
||||
| `voice_alexa.png` | 144x144 | I8 | 6005 | `0x032fa000` | ⛔ 格式特殊,暂不支持 |
|
||||
| `walkie_talkie.png` | 144x144 | ARGB8888 | 82956 | `0x02d45600` | |
|
||||
| `weather.png` | 144x144 | ARGB8888 | 82956 | `0x017de200` | |
|
||||
| `womenhealth.png` | 144x144 | ARGB8888 | 82956 | `0x00047a00` | |
|
||||
| `wxpay.png` | 144x144 | I8 | 21772 | `0x0000bc00` | ⚠ 需 I8 编码(256 色) |
|
||||
|
||||
## 两点必须注意
|
||||
|
||||
**1. 槽位字节必须完全相等。** ROMFS 是绝对偏移顺序排布,替换文件的字节长度必须和原文件一模一样,否则后面所有文件的偏移整体平移、镜像损坏。正因为你的 PNG 是 144×144、与原厂一致,编码后长度天然相等,可以直接原地覆盖。
|
||||
|
||||
**2. 标注「需 I8 编码」的那几个,会被自动量化成 256 色。** 它们的槽位只有 21772 字节,装不下 ARGB8888(82956 字节),所以只能用索引色。实测对扁平图标平均误差 0.22/255,肉眼无差别;但如果你的图标有渐变/照片级内容,这几个会有可见色带。
|
||||
|
||||
`voice_alexa` 是 6005 字节的特例(`flags=0x0008`),格式未确定,建议先不动。
|
||||
|
||||
## 生成命令
|
||||
|
||||
```
|
||||
python tools/iconpack.py make inputs/target/vela_app.bin <你的PNG目录> .work/mypack --patched
|
||||
```
|
||||
|
||||
产出:`icons.patch`(补丁表)、`icons.rollback`(回滚包)、`payload/`(每个图标的 .bin)、`manifest.json`(含偏移与 SHA256)、`vela_app_patched.bin`(可直接写入的整分区镜像)。
|
||||
Reference in New Issue
Block a user