6.9 KiB
本文是当时的分析 / 交付存档,路径按当时的工作目录写。 本项目里的对应关系: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 正是这么写分区的:
dd if=/ota/vela_app.bin of=/dev/app bs=32768 verify
所以本包对每条记录执行三步,全程只用官方存在的命令:
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 /data/s5icons/flash.sh
reboot
不需要表盘、不需要打包、不依赖表盘宿主权限。回滚用 rollback.sh。
路线 B:Lua 表盘(main.lua)
按你需要的形态提供。界面三个按钮 + 回滚:
- ① 只读检查(不写任何字节):
dd读出分区头,确认 magic 是-rom1fs-、 卷名是app;再逐条确认负载文件都在、长度正确。只有检查通过,② 才解锁。 - ② 写入并校验:逐条
dd写入 →dd读回 →cmp比对,带进度条。 - ③ 重启:
sync+reboot。 - 回滚:用
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 的渐变换了)。
⚠ 未验证项
我无法在真机上测试,以下三点请自行确认:
os.execute是否在表盘宿主里真的可用 ——docs-lua/intro/overview.md标为 ✅, 但那是模拟器环境的结论;docs-lua/intro/conventions.md也写明"接口通过逆向分析获得, 不同设备可用性不一致"。SCRIPT_PATH是否注入、值是什么 —— 这是 LoXe 文档的约定,不在本仓库文档里。main.lua里已有兜底常量,若加载失败请改成实际路径。- 写入
dd的权限 —— 表盘宿主进程是否有权写/dev/app。docs-shell只说明命令存在, 不代表当前进程有权限。
第一次请只按「① 只读检查」,看清输出再决定是否写入。或者直接走路线 A/C。
风险
- 往分区节点写数据是危险操作,写错位置可能导致无法开机。
- 系统 OTA 升级会整分区覆盖
vela_app.bin,图标会回到原厂(等于自动还原)。 - 修改系统资源分区可能影响保修;请自行确认设备所有权与当地法律。
- 动手前请确认你能刷回原厂
vela_app.bin(路线 C 那份镜像,或官方固件包里的同名文件)。