Files
2026-10-03 00:01:12 +08:00
..
2026-10-03 00:01:12 +08:00
2026-10-02 21:42:55 +08:00
2026-10-03 00:01:12 +08:00
2026-10-03 00:01:12 +08:00
2026-10-02 21:42:55 +08:00
2026-10-02 21:42:55 +08:00
2026-10-03 00:01:12 +08:00
2026-10-03 00:01:12 +08:00
2026-10-02 21:34:56 +08:00
2026-10-02 21:42:55 +08:00
2026-10-03 00:01:12 +08:00
2026-10-02 21:34:56 +08:00
2026-10-02 21:34:56 +08:00
2026-10-02 21:42:55 +08:00

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 里没有对应槽位。
  1. 偏移锁固件版本:ROMFS 是绝对偏移顺序排布,跨版本套用旧偏移 = 往错误位置写。换固件要重跑上面的流程。
  2. 等长是硬要求:替换文件长度与原槽位不相等就会让后面所有偏移平移、镜像损坏;脚本对每条都断言长度相等。
  3. I8 槽位(compass / debug / lua / perpetual_calendar / remote_camera / wxpay)必须复用源图调色板,否则边缘会出锯齿和彩点。
  4. 真机能力:os.execute / dd 可用;io.popen 捕获不到输出;os.execute 的返回值不可靠(成功也可能是 65280)→ 判据一律看内容。
  5. 首次请只点「环境预检查」,看清输出再替换。

7. 第三方与风险

  • 本仓库不含固件、OTA 包、字体;.face 由第三方 Compiler.exe 编译(见 ../README.md)
  • 往分区节点写数据是危险操作;官方 OTA 升级会整分区覆盖,图标回到原厂