Files
o62lte-customising-plan/s5e-icons/docs/说明.md
T
2026-10-02 21:34:56 +08:00

253 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Xiaomi Watch S5 eSIM 图标替换 Lua 表盘 —— 交付说明
> 目标固件:`miwear.watch.p62lte_v3.112.035_full_adbe74f0 (1).bin`(**Xiaomi Watch S5 eSIM 46mm**,平台 p62lte,480×480)
> 图标来源:**Xiaomi Watch S5 41mm(q63, v4.101.020)的 OS4 图标包**(即上一个项目里那份 S5 图标)
> 全部结论都有本机脚本证据;**真机行为未验证**,第 8 节列出了要你实测的三点。
---
## 1. 交付物
| 文件 | 说明 |
|---|---|
| `dist/S5eIcons.face` | **主交付物** 11,466,316 B(sha256 6d9c04f7017a1101…),表盘 id `462150101`,b5=0(与固件自带表盘一致) |
| `dist/S5eIcons_toolchain.face` | 官方工具链原样产物(b5=10);上面那个装不上时换这个 |
| `.work/s5e_from_os4/vela_app_os4.bin` | 改好图标的整分区镜像(走刷机通道,绕开设备侧脚本) |
| `.work/s5e_from_os4/vela_health_os4.bin` | 同上(health 分区) |
| `docs/s5e_before_after.png` | 47 个 launcher 图标:原厂 → 替换后 对照图 |
| `docs/s5e_sysicons.png` | 44 个系统 UI 素材(22 控制中心 + 14 压力表情 + 8 其他):原厂 → 从 S5 41mm 搬过来的 对照图 |
| `docs/s5e_pressure.png` | 14 张压力 App 小表情:原厂 → q63 对照图 |
| `docs/s5e_ui_all.png` | 480×480 七个页面预览(真 MiSans 字体渲染) |
| `main.lua` | 表盘源码(带 __UIIMG__ / __PAY__ / __RB__ 占位符) |
| `payload/`、`payload_rollback/` | 负载文件 + 清单 |
| `tools/s5e_*.py` | 全部生成 / 构建 / 校验脚本 |
---
## 2. 目标固件的事实(从固件包里查出来的,不是猜的)
| 项 | 结论 | 证据 |
|---|---|---|
| 机型 | **Xiaomi Watch S5 eSIM 46mm** | `vela_ap.bin` / `vela_cp_image.bin` 里的机型字符串 |
| 屏幕 | **480×480** | 固件自带表盘素材 `lua/daynight/asset/theme5/bigsun.bin`、`lua/complexzone/asset/Theme1/citymage1.bin` 都是 I8 480×480(231,436 B);`complexzone.lua` 建 480×480 根对象 |
| Lua 宿主 | **有**(Lua 5.4) | `vela_ap.bin`:`Lua 5.4`、`Copyright (C) 1994-2020`、`cannot create state`、`SCRIPT_PATH`、`watchface.db`、`luavgl`,以及**同样的 7 个 C 模块** `luaopen_activity/dataman/miwear/navigator/screen/topic/vibrator` |
| 自带 Lua 表盘 | 7 个 | `vela_watchface.bin`:outdooradv / daynight / corona / complexzone / clearheart / circle / asymmetryzone |
| 图标槽位 | `/dev/app` **34** 个 + `/dev/health` **15** 个 | ROMFS `app` / `health` 卷里的 `*/launcher.bin` |
| 图标格式 | **全部 144×144 ARGB8888 = 82,956 B** | 逐个解码确认 —— **这台机器没有 I8 图标槽位**(S4 eSIM 有 6 个),所以不会有 256 色量化损失 |
| 原厂画法 | 内容 136×136 居中(4px 透明边);`launcher_*` 是 138@(3,3);`debug/lua/voice_alexa/sports_game` 满幅 144 | 逐像素量 alpha 边界 |
| 分区节点 | `/dev/app`、`/dev/health` | 官方 `ota.sh`:`dd if=/ota/vela_app.bin of=/dev/app`、`... of=/dev/health` |
---
## 3. 替换了什么
- 能对上 **47** 个槽位(app 33 + health 14);OS4 里没有对应的 2 个槽位**保持原厂**:`walkie_talkie`、`sports_game`
- 其中 **8 个槽位**的 OS4 图标与 S5 eSIM 原厂**逐字节相同**(debug / esimsms / interconnect / lua / sports / sports_course / sports_record / sports_training),照样写入,保证整套一致
- **另外搬了 8 个系统 UI 素材**(都在 `/dev/app`,从 q63 取同名文件):`system/icon/lock_icon.bin`(锁屏上的锁)、`common/icon/system_lock.bin`(状态栏系统锁)、`common/icon/quiet_mode.bin`(免打扰小图标)、`notifications/default.bin`(通知默认图)、`notifications/no_message.bin`(通知空状态)、`settings/icon/notify.bin`(设置-通知,80→64 缩放)、`mijia/enable|disable_donot_disturb.bin`(米家免打扰,80→64 缩放)。对照图见 `docs/s5e_sysicons.png`。
- **控制中心磁贴整套搬了 22 张**(`control_center/icon/*`,S5 eSIM 128×128 ← q63 116×116 缩放 + 复用源图 I8 调色板):勿扰 / 静音 / 手电 / 设置 / 飞行 / 电池 / 移动网络 / 抬腕亮屏 / 排水 / 查找手机 / 蓝牙 / 耳机 / 取消 / 加号 / 游戏模式 / 网络开关。q63 是**浅色主题**(白盘 + 浅色字形),S5 eSIM 原厂是蓝色主题;白盘放在深色控制中心上的实际观感已在 S4 eSIM 上验证过(`s4e-icons/docs/s4e_control_center.png`)。
- **`activities` 用自制图覆盖**(`assets/override/activities.png`,112×112 的跑道/同心环,与 S4 项目同一张):裁到实心 bbox(100×100) → 放大到目标槽位画法(136×136,LANCZOS+锐化) → 居中粘回 (4,4);实测成品外径与原厂图标完全对齐
- **尺寸归一化**:把 OS4 的满幅 144 图按目标槽位的画法缩放(多数 136、`launcher_*` 138)并居中到同一原点 —— 这样**每条负载长度与原槽位完全相等**,ROMFS 的 inode 表和后面所有文件的偏移一个字节都不动
- 负载 **4.41 MB**(替换 + 回滚各一份),.face 共 8.98 MB
---
## 4. 91 条偏移表(由 `.work/s5e_from_os4/manifest.json` 生成)
> **2026-10-02 追加**:本表之后又加了 **160 条对讲机素材**(`walkie_talkie/**`,来自用户给的整套 PNG,
> 已放进 `assets/walkie_talkie/`)→ 现在一共 **251 条**(app 223 / health 28)。
> 这些槽位是 **I8+RLE 压缩**、长度不是 4 的倍数,靠 `iconpack.encode_into_slot()`(量化 → RLE →
> `ctrl=0` 空指令补齐到原 `csize`)做到逐字节等长,设备端写入自动用 `dd bs=1`。
> 完整 253 条名单见 `main.lua` 回填后的 PAYTBL(或 `.work/s5e_from_os4/manifest.json`)。
| # | 分区 | 名称 | 分区内偏移 | 长度 | 备注 |
|---|---|---|---|---|---|
| 1 | app | alarm | 0x06226200 | 82956 | |
| 2 | app | calendar | 0x05C23600 | 82956 | |
| 3 | app | chronograph | 0x059D1A00 | 82956 | |
| 4 | app | compass | 0x05802000 | 82956 | |
| 5 | app | debug | 0x05546800 | 82956 | 与 OS4 一致 |
| 6 | app | esimsms | 0x053E6E00 | 82956 | 与 OS4 一致 |
| 7 | app | find_phone | 0x05374C00 | 82956 | |
| 8 | app | flashlight | 0x0535CE00 | 82956 | |
| 9 | app | interconnect | 0x05183C00 | 82956 | 与 OS4 一致 |
| 10 | app | launcher_baidumap | 0x0516F600 | 82956 | |
| 11 | app | launcher_bdmap | 0x0515B000 | 82956 | |
| 12 | app | launcher_calculator | 0x05146A00 | 82956 | |
| 13 | app | launcher_kugou | 0x04DB9400 | 82956 | |
| 14 | app | launcher_micar | 0x04D80A00 | 82956 | |
| 15 | app | launcher_netease | 0x04D6BA00 | 82956 | |
| 16 | app | launcher_wechat | 0x04D57400 | 82956 | |
| 17 | app | launcher_ximalaya | 0x04D42000 | 82956 | |
| 18 | app | lua | 0x04D2DA00 | 82956 | 与 OS4 一致 |
| 19 | app | media | 0x04CFEE00 | 82956 | |
| 20 | app | mijia | 0x04BC8600 | 82956 | |
| 21 | app | nfccard | 0x04900C00 | 82956 | |
| 22 | app | perpetual_calendar | 0x04469C00 | 82956 | |
| 23 | app | phone | 0x0433B200 | 82956 | |
| 24 | app | phone_contacts | 0x04391A00 | 82956 | |
| 25 | app | recorder | 0x042B9400 | 82956 | |
| 26 | app | remote_camera | 0x04088200 | 82956 | |
| 27 | app | settings | 0x02C41C00 | 82956 | |
| 28 | app | timer | 0x02949E00 | 82956 | |
| 29 | app | todolist | 0x0291F200 | 82956 | |
| 30 | app | voice_aivs | 0x022DF800 | 82956 | |
| 31 | app | voice_alexa | 0x021AA600 | 82956 | |
| 32 | app | weather | 0x01B09400 | 82956 | |
| 33 | app | wxpay | 0x0000BC00 | 82956 | |
| 34 | health | activities | 0x0C51B600 | 82956 | |
| 35 | health | barometer | 0x0B910400 | 82956 | |
| 36 | health | breath | 0x08A2BC00 | 82956 | |
| 37 | health | health_today | 0x07077E00 | 82956 | |
| 38 | health | heartrate | 0x06EBB200 | 82956 | |
| 39 | health | innovation_research | 0x06558600 | 82956 | |
| 40 | health | oxygen | 0x0634AA00 | 82956 | |
| 41 | health | pressure | 0x03B9D400 | 82956 | |
| 42 | health | sleep | 0x0272F000 | 82956 | |
| 43 | health | sports | 0x01E89200 | 82956 | 与 OS4 一致 |
| 44 | health | sports_course | 0x02692600 | 82956 | 与 OS4 一致 |
| 45 | health | sports_record | 0x01B5FA00 | 82956 | 与 OS4 一致 |
| 46 | health | sports_training | 0x015A6400 | 82956 | 与 OS4 一致 |
| 47 | health | womenhealth | 0x001E3A00 | 82956 | |
| 48 | app | system/icon/lock_icon.bin | 0x02AB2200 | 2060 | 锁屏上的锁 |
| 49 | app | common/icon/system_lock.bin | 0x058DFC00 | 3340 | 状态栏系统锁 |
| 50 | app | common/icon/quiet_mode.bin | 0x058FCC00 | 3340 | 免打扰/静音小图标 |
| 51 | app | notifications/default.bin | 0x044BE800 | 32012 | 通知默认大图标 |
| 52 | app | notifications/no_message.bin | 0x044B2C00 | 7436 | 通知中心空状态 |
| 53 | app | settings/icon/notify.bin | 0x02D97C00 | 5132 | 设置-通知 |
| 54 | app | mijia/enable_donot_disturb.bin | 0x04BE3A00 | 5132 | 米家 免打扰开 |
| 55 | app | mijia/disable_donot_disturb.bin | 0x04BE6E00 | 5132 | 米家 免打扰关 |
| 56 | health | pressure/icon/presure_relax.bin | 0x03C85400 | 3340 | 压力表情-放松 48 |
| 57 | health | pressure/icon/presure_mild.bin | 0x03C89600 | 3340 | 压力表情-轻度 48 |
| 58 | health | pressure/icon/presure_mid.bin | 0x03C8D800 | 3340 | 压力表情-中度 48 |
| 59 | health | pressure/icon/presure_severe.bin | 0x03C81200 | 3340 | 压力表情-重度 48 |
| 60 | health | pressure/icon/presure_mild32.bin | 0x03C88C00 | 2060 | 压力表情-轻度 32 |
| 61 | health | pressure/icon/presure_mid32.bin | 0x03C8CE00 | 2060 | 压力表情-中度 32 |
| 62 | health | pressure/icon/presure_severe32.bin | 0x03C80800 | 2060 | 压力表情-重度 32 |
| 63 | health | pressure/icon/presure_icon.bin | 0x03C8E600 | 3340 | 压力 App 小图标 |
| 64 | health | pressure/icon/press_grade1.bin | 0x03E62A00 | 2060 | 压力等级徽标 1 |
| 65 | health | pressure/icon/press_grade2.bin | 0x03E62000 | 2060 | 压力等级徽标 2 |
| 66 | health | pressure/icon/press_grade3.bin | 0x03E61600 | 2060 | 压力等级徽标 3 |
| 67 | health | pressure/icon/press_grade4.bin | 0x03E60C00 | 2060 | 压力等级徽标 4 |
| 68 | health | pressure/icon/pressure_eomji_bg.bin | 0x03DC1E00 | 54636 | 表情底版 |
| 69 | health | pressure/icon/pressure_eomji_widbg.bin | 0x03DB9200 | 35628 | 表情底版(小) |
| 70 | app | control_center/icon/settings.bin | 0x0557CC00 | 17420 | 控制中心磁贴 |
| 71 | app | control_center/icon/raise_bright_open.bin | 0x05581200 | 17420 | 控制中心磁贴 |
| 72 | app | control_center/icon/raise_bright_close.bin | 0x05589E00 | 17420 | 控制中心磁贴 |
| 73 | app | control_center/icon/not_disturb_open.bin | 0x05592A00 | 17420 | 控制中心磁贴 |
| 74 | app | control_center/icon/not_disturb_close.bin | 0x0559B600 | 17420 | 控制中心磁贴 |
| 75 | app | control_center/icon/mute_open.bin | 0x055A1800 | 17420 | 控制中心磁贴 |
| 76 | app | control_center/icon/mute_close.bin | 0x055AA400 | 17420 | 控制中心磁贴 |
| 77 | app | control_center/icon/mobile_network_open.bin | 0x055AEA00 | 17420 | 控制中心磁贴 |
| 78 | app | control_center/icon/mobile_network_close.bin | 0x055C0200 | 17420 | 控制中心磁贴 |
| 79 | app | control_center/icon/game_mode_on.bin | 0x055D2800 | 17420 | 控制中心磁贴 |
| 80 | app | control_center/icon/game_mode_off.bin | 0x055DB400 | 17420 | 控制中心磁贴 |
| 81 | app | control_center/icon/flashlight.bin | 0x055E4000 | 17420 | 控制中心磁贴 |
| 82 | app | control_center/icon/find_phone_close.bin | 0x055F1200 | 17420 | 控制中心磁贴 |
| 83 | app | control_center/icon/earphone.bin | 0x055F6600 | 3340 | 控制中心磁贴 |
| 84 | app | control_center/icon/drain_close.bin | 0x05604600 | 17420 | 控制中心磁贴 |
| 85 | app | control_center/icon/cancel.bin | 0x05609A00 | 3340 | 控制中心磁贴 |
| 86 | app | control_center/icon/bt_connect.bin | 0x0560C400 | 3340 | 控制中心磁贴 |
| 87 | app | control_center/icon/battery_open.bin | 0x0560D200 | 17420 | 控制中心磁贴 |
| 88 | app | control_center/icon/battery_close.bin | 0x05615E00 | 17420 | 控制中心磁贴 |
| 89 | app | control_center/icon/air_mode_open.bin | 0x0561A400 | 17420 | 控制中心磁贴 |
| 90 | app | control_center/icon/air_mode_close.bin | 0x0562CA00 | 17420 | 控制中心磁贴 |
| 91 | app | control_center/icon/add.bin | 0x05631000 | 3340 | 控制中心磁贴 |
> 偏移都 512 对齐、长度都是 4 的倍数 → 设备侧 `dd bs=4` 正好写满目标区间,不会多写一个字节到相邻文件里。
---
## 5. 表盘是怎么写的(机制)
Lua 表盘不能设置图标;系统图标住在 ROMFS 分区镜像里,必须**写分区 + 重启**才生效。表盘只做运载 + 执行:
```
每条负载三步(只用官方存在的 dd,判据全看内容、不看返回码):
1 版本校验:dd 读出设备上该偏移处 64 字节 -> 与「原厂字节」比对
不一致就判为「版本不符」,该分区剩余记录全部跳过(不写坏)
2 写入 :dd if=/tmp/s5e_wr.bin of=<分区> bs=4 seek=<偏移/4> count=<长度/4>
3 回读校验:dd 读回整段 82,956 字节 -> 与负载逐字节比对
回滚 = 同一套流程,只是写入换成原厂字节、版本校验改比对替换后的字节
最后 sync + reboot(ROMFS 是只读挂载 + 有页缓存,不重启看不到新图标)
```
**为什么能等长原地替换**:ROMFS 把 inode、名字、数据首尾相接顺序排布,绝对偏移全靠前面所有内容的长度决定。所有替换都是等长覆盖,所以 inode 表 / superblock / 目录项 / 后续文件偏移全部不变(本机逐位验证)。
但偏移**锁固件版本**,所以第 1 步的版本校验是必须的:别的版本会全部「版本不符」跳过,而不是把图标写进别人的文件里。
表盘里的偏移是**构建时两遍测量回填**的:先编一遍,在 .face 里搜负载魔数定位,量出真实偏移写回 Lua,再编第二遍并逐条校验 0 漂移。
---
## 6. 本机验证结果(全绿)
| 验证项 | 结果 |
|---|---|
| 负载回放 == 改后镜像(`vela_app_os4.bin` / `vela_health_os4.bin`) | 逐字节相同 |
| 回滚回放 == 原厂镜像 | 逐字节相同(sha256 一致) |
| inode 表 / superblock / 条目数 | 逐位不变(3432 / 5555 条) |
| 变化的文件 | 恰好只有被改的 `launcher.bin`(app 29、health 10 有实际差异) |
| UI 素材、负载、回滚在最终 .face 里的偏移 | 从 .face 里的 Lua 表读出来重新校验,逐条字节一致(`tools/s5e_verify.py`,独立于构建脚本) |
| 两遍构建偏移漂移 | 0 |
| 480 页面圆屏切边 | 真实渲染像素测量,7 个页面全部 0 越界 |
| Lua 语法块平衡 | balanced |
| 预览图尺寸 | 326×326(Compiler.exe 对 462 设备强制,实测) |
---
## 7. 「环境预检查」通过意味着什么(**重要**)
预检查是**只读**的,所以它是**必要但不充分**条件:
| 预检查能证明 | 预检查证明不了 |
|---|---|
| `/dev/app`、`/dev/health` 两个节点存在且**可读** | **可写** —— 只有真正写第一条时才知道(这一步一个字节都不写) |
| 分区头是 `-rom1fs-`、卷名是 `app` / `health`、报出分区大小 | 表盘宿主的字体/界面能力 |
| 每分区抽 1 条对版:显示 `app 原厂✓` 或 `app 已替换✓` | 其余 46 条的偏移(这些在写入时逐条对版) |
| 负载表全部 4 对齐、不越界 | |
**为什么证明不了「可写」也敢点**:写入路径自带三重保护 ——
1. 每条写之前先读设备上 64 字节**对版**:等于「原厂字节」或「替换后字节」任一才动手;两者都不是就判「版本不符」,**该分区剩余记录全部跳过**,绝不往别人的文件里写
2. 写完整段 82,956 字节**回读比对**,不一致就计失败
3. 只做**等长原地覆盖**,不碰 inode 表 / superblock / 目录项 —— 最坏结果是「图标没变」,不会把分区写坏
**判断成功的唯一标准**:跑完显示 `> 成功 91 / 失败 0(共 91)` 和 `> app 63/63 health 28/28`。**不是 0 就先别重启**,点「回滚修改」再排查。
> 因为对版接受两种状态,**重复点「开始替换」或断电后半途重来都是安全的**(已经写入新图标的那部分不会被判成「版本不符」)。
---
## 8. 还没验证的(真机只能你来测)
1. **表盘宿主进程有没有权限写 `/dev/app` 和 `/dev/health`**
S4 eSIM 上写 `/dev/app` 是实测通过的;这台同源平台但没测过。**请先点「环境预检查」**(只读,不写任何字节)。
2. **`lvgl.Font('MiSans-Medium' / 'MiSans-Semibold', size)` 在这台上的表现**
`vela_font.bin` 里确实有 `MiSans-Semibold.ttf` / `MiSans-Medium.ttf`,S4 上这个 API 可用。界面文字依赖它;若不可用,文字会退回主题默认字体(中文可能缺字)。
3. **`/dev/health` 的写权限**。如果它不可写,app 的 33 条仍会成功,日志里 health 会全失败。
4. **安装通道**:和 `S5Icons.face` 一样走你的表盘安装方式(Compiler.exe 内部用的是 push 到 `/data/app/watchface/market/<id>/resource.bin`)。
输出怎么读(「正在替换」页):
- 每 10 条一行 `> n/91 成 x 失 y`
- 结束:`> 成功 x / 失败 y(共 91)`,必要时 `> 版本不符跳过 n 条`,以及 `> app a/63 health b/28`
- **失败 0** 表示 91 条都写入并回读校验通过 → 点「重启手表」
---
## 9. 想改的话
```powershell
$py = "python" # 本机 Python 3 + Pillow
# 只改界面/坐标:改 main.lua 与 tools/s5e_pages.py,然后
& $py tools\\s5e_render_ui.py # 渲染 480 预览
& $py tools\\s5e_check_clip.py # 圆屏切边检查
& $py tools\\s5e_build.py # 两遍构建 -> dist\\S5eIcons.face
& $py tools\\s5e_verify.py # 独立校验
# 换图标来源 / 换固件版本
& $py tools\\s5e_payload.py # 重新生成负载 + 整分区镜像(自带证明)
& $py tools\\s5e_ui_images.py # 重新生成 UI 素材(改了素材时)
& $py tools\\s5e_build.py
```
> `.work/`、`.work/` 是产物,别手改;`main.lua` 才是源码。