S5 图标包 → Xiaomi Watch S5 eSIM 46mm(图标修改器 · Lua 表盘)
把 Xiaomi Watch S5 41mm(q63, v4.101.020)的 OS4 图标包写进 Xiaomi Watch S5 eSIM 46mm(p62lte, v3.112.035) 的 /dev/app 与 /dev/health 两个分区。
- 153 条负载:47 个 launcher 图标(app 33 + health 14)+ 44 个系统 UI 素材(8 个锁屏/通知/免打扰小图标 + 22 张控制中心磁贴 + 14 张压力 App 小表情)+ 160 张对讲机素材(
walkie_talkie/**)→ 全部等长覆盖,inode 表与后面所有偏移一个字节都不动 - 长宽比不一致的素材一律不替换(保持原厂):源图与槽位长宽比相差 >0.5% 就跳过 —— 拉伸会把方块变长方形、裁切会切掉按钮两端。所以
walkie_talkie/image/stop.bin(176×96 vs 144×96)与walkie_talkie/guidance/finish_guidance.bin(176×96 vs 160×96)保持原厂(本机逐字节确认) - 对讲机那一组是 I8+RLE 压缩槽位(固件里
flags=0x08,长度不是 4 的倍数)→iconpack.encode_into_slot():量化(颜色数从 256 逐级往下试)→ RLE → 用ctrl=0的 2 字节空指令补齐到原csize,做到逐字节等长;设备端写入这类记录自动退化成dd bs=1 - 界面按 480×480 重做(设计稿在
tools/s5e_pages.py,本机用真 MiSans 渲染 + 真实像素判圆屏切边,7 页 0 越界) - 压力 App 小表情也搬了 14 张(
pressure/icon/):4 档情绪 48px(relax/mild/mid/severe)+ 32px 三张 +press_grade1..4等级徽标 +presure_icon+ 两张表情底版(pressure_eomji_bg/_widbg);96px 那四张两边本来就一样(自动跳过)。没搬的:整屏背景(pressure_bg/success_bg/success_arc/press_widget_bg*/press_chart_bg)、reminder.bin、pressure/measure/*动画帧(体积大) - 系统 UI 素材整套一起搬:8 个锁屏/通知/免打扰小图标 + 22 张控制中心磁贴(q63 磁贴是 116×116 浅色主题,缩放到 S5 eSIM 的 128×128)。白盘放在深色控制中心上的观感已在 S4 eSIM 上实测,效果可以
- 只读预检查 + 每条写入前后对版/回读校验 + 一键回滚
1. 交付物与安装
| 文件 | 说明 |
|---|---|
dist/S5eIcons.face |
主交付物 10,852,268 B(sha256 50da912734a5db48…),表盘 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/说明.md |
完整说明:固件事实、47 条偏移表、验证结果、待实测项 |
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 七个页面预览 |
装法与普通 Lua 表盘相同(resource.bin 推到 /data/app/watchface/market/<id>/)。装好后在表盘上操作:
首页 →「快速帮助」→「环境预检查」 只读,一个字节都不写
首页 →「开始替换」→ 确认 → 自动写入 153 条 →「重启手表」
快速帮助 →「回滚修改」→ 确认 → 写回原厂图标
输出怎么读(「正在替换」页):每 10 条一行 > n/153 成 x 失 y;结束时 > 成功 x / 失败 y(共 153)、必要时 > 版本不符跳过 n 条,以及 > app/health 分区分条统计。
**判断成功的唯一标准是 失败 0。**不是 0 就先别重启,点「回滚修改」。
2. 目录
main.lua 表盘源码(含 __UIIMG__ / __PAY__ / __RB__ 占位符,构建时回填)
payload/ payload.bin = MAGIC + 91 条顺序拼接;payload.lua = 逐条偏移表
payload_rollback/ 原厂字节(回滚用)
ui_images/ 界面素材(LVGL v9 .bin,8 张)
assets/ 设计素材 PNG(Mdi 图标、返回箭头、QQ、封面)
assets/override/ 自制图覆盖(activities.png = 跑道图标,优先级高于固件源)
tools/ 全部脚本(见下表)
dist/ 交付的 .face
docs/ 说明与预览图
inputs/ 要你自己放固件分区镜像(见第 4 节,不进仓库)
vendor/ 放第三方 LuaDevTemplate-main(不进仓库)
.work/ 构建工作区(产物,可删)
| 脚本 | 作用 |
|---|---|
tools/paths.py |
所有路径的唯一入口(inputs / vendor / .work / dist,支持环境变量覆盖) |
tools/s5e_payload.py |
从两个分区镜像生成 91 条负载 + 回滚 + 整分区镜像(自带正反两个证明) |
tools/s5e_ui_images.py |
设计素材 PNG → LVGL .bin + 圆形 logo |
tools/s5e_pages.py |
480×480 页面规格(唯一的设计稿来源) |
tools/s5e_render_ui.py |
用真 MiSans 渲染 7 页预览 |
tools/s5e_check_clip.py |
用真实渲染像素判圆屏是否切边 |
tools/s5e_build.py |
两遍构建 → dist/S5eIcons.face(自动回填偏移 + 0 漂移校验) |
tools/s5e_verify.py |
独立校验:从 .face 里反解 Lua 表与偏移,再与固件镜像逐字节比对 |
tools/romfs.py tools/lvgl_bin.py tools/iconpack.py tools/render_ui.py tools/lua_balance.py |
共用底层(ROMFS 解析 / LVGL 图片编解码 / UI 渲染 / Lua 块平衡) |
3. 目标固件的事实(从固件包里查出来的)
| 项 | 结论 | 证据 |
|---|---|---|
| 机型 | Xiaomi Watch S5 eSIM 46mm | vela_ap.bin / vela_cp_image.bin 里的机型字符串 |
| 屏幕 | 480×480 | 自带表盘素材 bigsun.bin、citymage1.bin 都是 I8 480×480(231,436 B);complexzone.lua 建 480×480 根对象 |
| Lua 宿主 | 有,Lua 5.4 | vela_ap.bin 里 Lua 5.4、SCRIPT_PATH、watchface.db、luavgl,以及同样的 7 个 C 模块 luaopen_activity/dataman/miwear/navigator/screen/topic/vibrator |
| 自带 Lua 表盘 | 7 个 | 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 个),不存在量化损失 |
| 原厂画法 | 内容 136×136 居中(4px 透明边);launcher_* 138@(3,3);debug / lua / voice_alexa / sports_game 满幅 144 |
逐像素量 alpha 边界 |
4. 替换内容
- 能对上 47 个槽位(app 33 + health 14);OS4 里没有对应的 2 个槽位保持原厂:
walkie_talkie、sports_game - 其中 8 个槽位的 OS4 图标与原厂逐字节相同(debug / esimsms / interconnect / lua / sports / sports_course / sports_record / sports_training),照样写入,保证整套一致
activities用自制图覆盖:assets/override/activities.png(112×112 红/绿/青同心环,即 S4 项目里那张跑道图标)优先于 q63 固件源。裁剪 = 取实心 bbox(100×100) → 放大到目标槽位画法(136×136,LANCZOS + 锐化) → 居中粘回 (4,4);实测成品外径与原厂图标完全对齐(都是 (4,4)-(139,139))voice_aivs(小爱同学)同样用自制图覆盖:assets/override/voice_aivs.png(用户给的 144×144 PNG,圆形白盘直径 140)→ 裁到实心 → 缩放到槽位画法 136×136 → 居中;编码后 82,956 B 与原槽位等长- 尺寸归一化:把 OS4 的满幅 144 图按目标槽位的画法缩放(多数 136、
launcher_*138)并居中到同一原点 —— 这样每条负载长度与原槽位完全相等 - 负载 4.41 MB(替换 + 回滚各一份),.face 共 8.98 MB
5. 重建
5.1 需要自己准备的输入(放进 inputs/,不进仓库)
| 路径 | 是什么 | 大小 / sha256 前 16 位 |
|---|---|---|
inputs/target/vela_app.bin |
目标固件(p62lte)app 分区 | 103,043,072 / 9b41c5f5eb17c112 |
inputs/target/vela_health.bin |
目标固件 health 分区 | 206,924,800 / 489752c8a3fb24ff |
inputs/target/vela_font.bin |
目标固件 font 分区(可选:渲染预览时从里面取 MiSans) | 43,379,712 / 16cb803ac1bd74c5 |
inputs/source/vela_app.bin |
图标来源(S5 41mm q63)app 分区 | 206,000,128 / 161a8f7a5f0bd8af |
inputs/source/vela_health.bin |
图标来源 health 分区 | 233,939,968 / 12a3bd2f8b62e969 |
inputs/fonts/MiSans-Medium.ttf、MiSans-Semibold.ttf |
可选;不提供就自动从 inputs/target/vela_font.bin 里取 |
— |
vendor/LuaDevTemplate-main/ |
官方模板(里面有 Compiler.exe) |
— |
固件分区镜像就是官方 OTA 包(ZIP)里的 vela_*.bin 条目,解压出来即可。
5.2 步骤
# 需要 Python 3 + Pillow
python tools/s5e_payload.py # 负载 + 整分区镜像(自己证明 正放=改后镜像 / 反放=原厂镜像)
python tools/s5e_ui_images.py # 界面素材
python tools/s5e_render_ui.py # 480 预览(可选)
python tools/s5e_check_clip.py # 圆屏切边检查(可选)
python tools/s5e_build.py # 两遍构建 -> dist/S5eIcons.face
python tools/s5e_verify.py # 独立校验
只想改界面/坐标:改 main.lua 与 tools/s5e_pages.py,然后跑 s5e_render_ui.py + s5e_check_clip.py + s5e_build.py + s5e_verify.py。
6. 已知边界(踩过的坑)
-
对讲机素材默认不打包(ICON_WALKIE=1 可打开);160 张 PNG 仍在 ssets/walkie_talkie/。
-
偏移锁固件版本:换固件要重跑
s5e_payload.py+ 构建。用错版本时表盘会判「版本不符」整分区跳过,不会把字节写进别人的文件里。 -
Compiler.exe 的设备表里没有 480 档(枚举 DeviceType 1..1500 只有 17 个已知值,圆表且预览 326×326 的只有 362 / 462)→ 这里用 462 + 326×326 预览。预览只是表盘列表里的缩略图。
-
表盘宿主能不能写
/dev/app、/dev/health未在真机验证(S4 eSIM 上写/dev/app是实测通过的,同源平台)。所以界面里第一步是只读的「环境预检查」。 -
负载文件必须原样连续放在 .face 里,偏移才等于
文件基址 + 魔数长度 + 相对偏移;s5e_build.py会断言这一点。魔数长度不要手写常量(曾经把 15 写成 16,全部偏移差 1 字节)。 -
独立校验不是摆设:
tools/s5e_verify.py从 .face 里的 Lua 表反解偏移再与固件镜像比对,本项目开发时它抓出两个真 bug —— 魔数长度差 1;Lua 表里存的是/dev/app而不是运行时期望的app键(会让 dd 拼成if=nil)。 -
负载写入走
/tmp中转(resource.bin 内的偏移不保证 4 字节对齐,不能直接dd skip)。
7. 第三方与风险
- 本仓库不含固件、OTA 包、字体;
.face由第三方Compiler.exe编译(见 ../README.md) - 往分区节点写数据是危险操作:写错偏移可能损坏资源分区。表盘自带版本校验与回滚,但请自行确认设备所有权并承担后果
- 官方 OTA 升级会整分区覆盖,图标会回到原厂(等于自动还原)