S5 图标包 → Xiaomi Watch S4 eSIM(图标修改器 · Lua 表盘)
把 Xiaomi Watch S5 41mm(q63, v4.101.020)的 OS4 图标包写进 Xiaomi Watch S4 eSIM(o62lte, 3.202.79) 的 /dev/app 分区。
- 149 条负载 = 37 个 launcher 图标 + 11 个控制中心顶栏状态 + 10 张合成图 + 8 个系统 UI 素材(锁屏锁 / 状态栏系统锁 / 免打扰小图标 / 通知默认图 / 通知空状态 / 设置-通知 / 米家免打扰×2)+ 12 张压力 App 小表情 + 160 张对讲机素材(
walkie_talkie/**),全部等长替换(144×144 图标 / 128×128 磁贴 / 32~176 小图;图标是 I8 索引色需复用原调色板) - 长宽比不一致的素材一律不替换(保持原厂):源图与槽位长宽比相差 >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 - 界面 466×466(设计稿在
tools/pages.py,本机真 MiSans 渲染 + 真实像素判圆屏切边) - 只读预检查 + 写入 + 一键回滚;另外还有一个不依赖表盘的
flash.sh/rollback.sh(有 shell 时直接跑)
1. 交付物与安装
| 文件 | 说明 |
|---|---|
dist/S5Icons.face |
主交付物 8,024,916 B(sha256 b371a40e8eed53a0…),表盘 id 362150101,b5=0 |
dist/S5Icons_toolchain.face |
官方工具链原样产物(b5=10);上面那个装不上时换这个 |
.work/s4_from_s5/vela_app_s5icons.bin |
重建后得到的整分区镜像(265 MB,可直接刷机,绕开设备侧脚本) |
flash.sh / rollback.sh |
有 nsh shell 时不用表盘:把脚本和 payload/ 复制到 /data/s5icons/ 再 sh flash.sh |
docs/分析报告-S4eSIM.md |
固件结构 / 为什么必须等长 / 为什么锁版本 的完整分析 |
docs/图标命名对照表.md |
39 个槽位的名字、偏移、格式对照表 |
docs/ui_all_466.png |
466×466 七个页面预览 |
docs/s4e_sysicons.png |
42 张系统素材(22 控制中心 + 8 锁屏/通知/设置/米家 + 12 压力表情):S4 原厂 → 从 S5 41mm 搬来的 对照图 |
docs/s4e_pressure.png |
12 张压力 App 小表情:S4 原厂 → q63 对照图 |
装法与普通 Lua 表盘相同(resource.bin 推到 /data/app/watchface/market/<id>/)。装好后在表盘上操作:
首页 →「快速帮助」→「环境预检查」 只读,不写任何字节
首页 →「开始替换」→ 确认 → 自动写入 149 条 →「重启手表」
快速帮助 →「回滚修改」→ 确认 → 写回原厂图标
2. 目录
main.lua 表盘源码(含 __UIIMG__ / __PAY__ / __RB__ 占位符,构建时回填)
payload/ 80 个负载 .bin(NNN_名字.bin)+ payload.lua 清单
payload_rollback/ 同结构,内容是原厂字节(回滚用)
ui_images/ 界面素材(LVGL v9 .bin,8 张)
assets/ 设计素材(Mdi 图标、返回箭头、QQ、封面)
assets/override/ 3 张 112×112 自制 PNG(activities / sports_course / interconnect),优先级高于固件来源
tools/ 全部脚本(见下表)
dist/ 交付的 .face
docs/ 说明、分析报告、预览图
inputs/ 要你自己放固件分区镜像(见第 4 节,不进仓库)
vendor/ 放第三方 LuaDevTemplate-main(不进仓库)
.work/ 构建工作区(产物,可删)
| 脚本 | 作用 |
|---|---|
tools/paths.py |
所有路径的唯一入口(inputs / vendor / .work / dist,支持环境变量覆盖) |
tools/make_s5_to_s4_inset.py |
S5 图标 → S4 槽位(尺寸归一化 + I8 调色板复用 + 自制 PNG 覆盖) |
tools/make_dd_payload.py |
切分成逐条负载 + 回滚负载 + flash.sh / rollback.sh / payload.json |
tools/make_ui_images.py |
设计素材 PNG → LVGL .bin + 圆形 logo |
tools/pages.py |
466×466 页面规格(唯一的设计稿来源) |
tools/render_ui.py |
用真 MiSans 渲染 7 页预览 |
tools/check_clip.py |
用真实渲染像素判圆屏是否切边 |
tools/gen_ui_lua.py |
两遍构建 → dist/S5Icons.face(自动回填偏移 + 0 漂移校验) |
tools/romfs.py tools/lvgl_bin.py tools/iconpack.py tools/lua_balance.py |
共用底层(ROMFS 解析 / LVGL 图片编解码 / Lua 块平衡) |
3. 目标固件的事实
| 项 | 结论 |
|---|---|
| 机型 | Xiaomi Watch S4 eSIM(o62lte),屏幕 466×466 |
| Lua 宿主 | 有,Lua 5.4;7 个 C 模块 activity / dataman / miwear / navigator / screen / topic / vibrator + luavgl |
| 图标槽位 | 39 个 launcher.bin(38 个 <app>/launcher.bin + phone/contacts_launcher.bin),都在 /dev/app(265 MB ROMFS 分区) |
| 图标格式 | 33 个 ARGB8888(82,956 B)+ 6 个 I8(21,772 B);144×144 画布 |
| 原厂画法 | 内容 136×136 居中(4px 透明边);I8 槽位必须复用源图调色板,否则边缘锯齿 + 彩点 |
4. 替换内容
- 37 个槽位真正被改写;
voice_alexa、wxpay与 S5 源逐字节相同,已从负载中剔除 - 另外搬了 22 张控制中心磁贴(
control_center/icon/*,S4 128×128 ← q63 116×116,缩放 + 复用源图 I8 调色板):勿扰 / 静音 / 手电 / 设置 / 飞行 / 电池 / 移动网络 / 抬腕亮屏 / 排水 / 查找手机 / 蓝牙 / 耳机 / 取消 / 加号 / 网络开关。对照图见docs/s4e_sysicons.png- ⚠ 说明:S4 原厂是蓝色主题(选中=实心蓝盘),q63 是浅色主题(白盘 + 浅色字形)——q63 那边看着「淡」是它的正常样子(OS4 的浅色控制中心)。
- ✅ 已实测:效果可以(用户确认)。
- 另外 8 个系统 UI 素材也一起搬了(和 S5 eSIM 那份同一组):
system/icon/lock_icon.bin(锁屏锁)、common/icon/system_lock.bin(状态栏锁)、common/icon/quiet_mode.bin(免打扰小图标)、notifications/no_message.bin(通知空状态)、settings/icon/notify.bin(设置-通知,80→64)、mijia/enable|disable_donot_disturb.bin(米家免打扰,80→64)、esimsms/icon/esim_sms_reminder.bin(短信提醒)。对照图同docs/s4e_sysicons.png。 - 压力 App 小表情也搬了 12 张(S4 没有 health 分区,这些都在
/dev/app的pressure/icon/):4 档情绪 48px(relax/mild/mid/severe)+ 32px 三张 +press_grade1..4+presure_icon。没搬:96px(两边本来就一样)、pressure_eomji_bg/_widbg(S4 没有这两张)、整屏背景与measure/*动画帧、reminder.bin。对照图docs/s4e_pressure.png walkie_talkie是 S4 独有、S5 没有,保持原厂activities、sports_course、interconnect用assets/override/里的自制 112×112 PNG;voice_aivs(小爱同学)用assets/override/voice_aivs.png(用户给的 144×144 PNG)—— 都会裁到实心范围、缩放到该槽位自己的画法(136×136)再归一化- 编码策略:S5 文件长度 == S4 槽位长度 → 原样拷贝;否则解码后按 S4 槽位的色彩格式重新编码,保证长度逐字节相等
5. 重建
5.1 需要自己准备的输入(放进 inputs/,不进仓库)
| 路径 | 是什么 | 大小 / sha256 前 16 位 |
|---|---|---|
inputs/target/vela_app.bin |
目标固件(o62lte 3.202.79)app 分区 | 265,684,992 / caa66693f49477dd |
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 |
可选:渲染预览用;没有就退化成系统字体(只影响预览图) | — |
vendor/LuaDevTemplate-main/ |
官方模板(里面有 Compiler.exe) |
— |
固件分区镜像就是官方 OTA 包(ZIP)里的 vela_*.bin 条目,解压出来即可。
5.2 步骤
# 需要 Python 3 + Pillow
python tools/make_s5_to_s4_inset.py # S5 图标 -> S4 槽位(产出 .work/s4_from_s5/vela_app_s5icons.bin)
python tools/make_dd_payload.py # 切成逐条负载 + 回滚 + flash.sh/rollback.sh
python tools/make_ui_images.py # 界面素材
python tools/gen_ui_lua.py # 两遍构建 -> dist/S5Icons.face
python tools/render_ui.py # 466 预览(可选)
python tools/check_clip.py # 圆屏切边检查(可选)
5.3 只想换自定义图标?
把 144×144 PNG 按 docs/图标命名对照表.md 命名,然后:
python tools/iconpack.py make inputs/target/vela_app.bin <你的PNG目录> .work/mypack --patched
6. 已知边界
- 对讲机素材默认不打包(ICON_WALKIE=1 可打开);160 条对讲机 PNG 仍在 ssets/walkie_talkie/。
- 控制中心的米家/融合中心那组:默认不打包(用户 2026-10-02 决定不改;
ICON_MIJIA=1可打开)。已确认用户两台设备本来就装了同一个米家手表 App(com.xiaomi.smarthome.watch.*.rpk,129 张设备图标),128 张与 q63 逐字节相同,所以控制中心那套不需要移植;更多设备图标属于米家 App 内部素材,/dev/app里没有对应槽位。
- 偏移锁固件版本:ROMFS 是绝对偏移顺序排布,跨版本套用旧偏移 = 往错误位置写。换固件要重跑上面的流程。
- 等长是硬要求:替换文件长度与原槽位不相等就会让后面所有偏移平移、镜像损坏;脚本对每条都断言长度相等。
- I8 槽位(compass / debug / lua / perpetual_calendar / remote_camera / wxpay)必须复用源图调色板,否则边缘会出锯齿和彩点。
- 真机能力:
os.execute/dd可用;io.popen捕获不到输出;os.execute的返回值不可靠(成功也可能是 65280)→ 判据一律看内容。 - 首次请只点「环境预检查」,看清输出再替换。
7. 第三方与风险
- 本仓库不含固件、OTA 包、字体;
.face由第三方Compiler.exe编译(见 ../README.md) - 往分区节点写数据是危险操作;官方 OTA 升级会整分区覆盖,图标回到原厂