Commit Graph
473 Commits
Author SHA1 Message Date
AskaEth 7b32d775c0 fix(security): M64 P1 三条 root 命令注入 — 外部串改 execv 传 argv、电源键下线、press_ms clamp
P1-1 Wi-Fi SSID/PSK 注入(netmgrd + launcherd 回退两处同步):
- 不再拼 `set_network %d ssid '"%s"'` 交给 sh -c;新增 wpa_exec()/nm_direct_wpa_exec()
  以 fork+execv 传 argv(含 resolve_bin 解析绝对路径,替代 `PATH=...` 前缀)。
- 共用 nm_wifi_args.h:nm_wifi_ssid_ok/n_wifi_psk_ok/nm_wpa_quote/nm_wpa_set_argv,
  两份实现同一套规则(只修一处等于没修)。
- 约束层:SSID 1..32B、禁控制字符;PSK ≤63 或 64 hex、禁控制字符;`"`/`\` 按
  wpa_supplicant 语法转义。shell 元字符不再需要拒绝——execv 下没有 shell 解析。

P1-2 MeowPanel App 安装/导出/卸载注入:
- install/export/remove 改 run_argv()(fork+execv,PATH 只在子进程 setenv)。
- 包名统一 mp_pkg_ok()(仅 [a-z0-9._-],拒 `..`,与 nyai.c valid_pkg 同规则)→ 修 P2-7。
- `{path}` 追加 mp_path_unsafe()(与 mp_fonts/nyai 同一拒绝集)作第二道。

P1-3 /api/key/* 电源键:
- keycode_to_device 删除 KEY_POWER;116 进红线(403),前端遥控板电源/长按电源按钮移除。
- press_ms 全局 clamp 0..1000(单键与批量),`long` 缺省取上限。
- 按下→等待→抬起改独立线程(inject_key_async),不再阻塞单线程 8080 服务。

测试(宿主,反向控制):test_nm_wifi_args.c / test_mp_argsafe.c —— 同一套审计恶意串
(`a' ; id ; '`、`x';id;'`、`/tmp/a';id>/tmp/pwn;'.mpk`、`MeowAPPs/..`)在修复前实现下
注入成功(stdout 含 uid=)/放行,在修复后实现下不执行/拒绝。另含「两处实现同步」检查。

未刷机、未连设备、未发 /api/key 请求;只改源码,未提交二进制。
2026-11-12 08:50:10 +08:00
AskaEth 952a126477 Merge branch 'feat/m63-readme-doc-corrections-from-m61-audi' 2026-11-12 08:50:06 +08:00
AskaEth 4d39c037be docs(build): M63 修正 README 的 -o 归档语义/宿主路径/版本示例/入库清单 2026-11-12 08:50:04 +08:00
AskaEth f7921ba799 Merge branch 'feat/m56-post-merge-rebuild-batch-0-8-51-pack' 2026-11-12 08:49:57 +08:00
tower 28fbc6094a fix(build): M56 r1 评审三条 P2 + 一条观察(脚本权限/可复现口径注释/watchdog 参数去内联+入门禁/REPACK 显式失败)
- P2-1 repack_repro.sh 提交为 100755(scripts/build/ 下唯一非 755 的 .sh,而头注释就让人直接执行)
- P2-2 注释与事实对齐:写明 squashfs/分片/ota_update.in 可复现,但 .upt 仍有 93 字节
  Rock Ridge TF backup date 差异(genisoimage 无覆盖开关),验收口径仍是 体积+unpack 0 diff+进包 md5
- P2-3 watchdog 参数不再内联复制:build_watchdog.sh 认 ZIGCC(照 build_netmgrd.sh 口径),
  rebuild_binaries.sh 改成调它;meow_watchdog 加入 test_time32_abi.sh 的 BUILD_TARGETS(11 个)。
  改后重编仍逐字节 25f769509a43d0f66287efd456c64281 / 100116,与内联那版一致
- 观察1:REPACK 显式指定但不存在 -> exit 1(此前会静默回退到不可复现的旧 repack.sh)
2026-11-12 08:44:27 +08:00
tower d34406ac22 chore(fw): M56 重编 5 件受源码变动影响的产物 + 钉值回写 + 可复现打包器(0.8.51)
- 重编 netmgrd(M54 ACL)/audiod(M55 BT 增益 + M52 bind 失败即退)/launcherd(M52 multi_font 收口)/
  watchdog(M52 资源告警);meow_audio 源码未变,重编逐字节相同(md5 不变)
- fw_content.conf:5 条 md5 回写 + meow_power desc 去掉「policy 期3桩」(已实装)
- 新增 scripts/build/repack_repro.sh:mksquashfs -repro-time + genisoimage -creation-date
  + mtime 归一化 + 半成品防护(先写暂存再 mv、体积下限自检)
- build_fw_apps.sh:优先走可复现打包器、XIMAGE/TMPDIR 可在干净 checkout/worktree 覆盖
- rebuild_binaries.sh:新增 watchdog 目标(build_watchdog.sh 的 ROOT/.tools 在 worktree 不存在)
2026-11-12 08:25:04 +08:00
AskaEth dd8929e2df Merge branch 'feat/m55-bluetooth-output-volume-scaling' 2026-11-12 08:07:50 +08:00
AskaEth 2c0ec2ba15 test(audiod): r1 P2-1/P2-2 — 补 rapid-toggle 回归用例 + BT 音量语义文档(实现零改动)
评审 r1 的 `p2-2items`,两项都是「缺用例 / 缺一句文档」,不改任何已交付行为。

P2-1(连续拖滑块/ramp 中途反向缺回归):
- test_vol_gain 新增 G4b:块长 100(< 512 斜坡)× 60 次 100↔10 反向,实测 59 次落在
  斜坡中段;断言增益全程 ∈ [q15(10)=4, 32768] 且从不放大、块内逐样本只朝目标单调走、
  相邻样本跳变 ≤64 LSB(硬跳变 ~16380)、停手后 cur=4/left=0/bump_left=0。
- test_audiod_host 新增 T20(daemon 侧等价集成用例):经 IPC 连发 60 次 vol,
  每次只喂 100 帧(400B),断言 60 次全 ACK、underrun/dropped=0、
  落盘 PCM 无硬跳变(max_delta 1020 = 正弦自身斜率)、尾部 RMS 1.49 比头部
  6376.58 低 72dB(10% 真的生效)。

P2-2(文档):vol_gain.h 顶部新增「App/SDK 侧契约」段:BT 下 set_volume 同样生效
(走软件增益 + 持久化),AVRCP 钉 127 只为耳机功放满量程而非「音量交给耳机」,
pct 只在 [0,100] 有意义(<=0 真静音、>100 按满量程截断),16bit 写路径的
量化下限说明;vol_gain_dac_att_reg 注释补 pct>100 行为。build_audiod.sh
头部补 M55 说明(必须一起编 vol_gain.c、旧 SDK 说法已过期)。

验证:test_audiod_host 134/134 PASS、test_vol_gain 36/36 PASS、test_bt_dev PASS;
mipsel 交叉编译 0 warning,产物 md5 6cef4c54a1cb8e0c55f4ad5b6c4a323e(与 r1 完全一致,
证明二进制未变)。
2026-11-12 08:07:37 +08:00
AskaEth e37c622597 feat(audiod): BT 输出音量软件增益(曲线与 DAC 同源)+ vol_apply BT 分支真生效
真机现象:音量设 10 仍然满响度——vol_apply() 的 BT 分支 logmsg + return,
g_vol_pct 不更新、AVRCP 被 bt_vol_full() 钉在 127、写路径无任何软件缩放,
于是 BT 永远在耳机自己的满音量。

- 新增 vol_gain.{c,h}(纯整数,无 libm/软浮点):增益表由 dac_vol_set 的同一个
  衰减寄存器函数 vol_gain_dac_att_reg() 推出(0.5dB/步、平方锥度),
  Q31 迭代乘法开机算一次;vol=100 逐字节不变,vol=0 全 0(memset 快路)。
- dev_write(BT 路径)逐样本乘 + 32 位中间量 + 饱和;增益恒 <=1 故不可能削波。
- 改音量走 512 样本线性斜坡(Bresenham 余额分摊,末步不补差),防爆音。
- vol_apply() BT 分支改为更新 g_vol_pct + 持久化;DAC numid=1/2/11 仍不写
  (M50 T11 隔离断言不变)。
- dac_vol_set() 改用 vol_gain_dac_att_reg(),DAC/BT 两条路锥度不可能漂移。
- 测试:test_vol_gain(曲线/缩放/斜坡纯函数断言)+ T11 改为断言
  「BT 音量不写 DAC 寄存器」+ 新增 T18(wav 捕获音量阶梯 RMS/峰值)
  / T19(活改音量不爆音、不丢帧)。
2026-11-12 08:07:37 +08:00
AskaEth 4040b41649 Merge branch 'feat/m52-device-resource-guard-and-log-spam-e' 2026-11-12 08:03:09 +08:00
AskaEth 0d6c56100f fix(m52): r1 评审 P2-1 class 缺失 WARN 去刷屏 + 两个 nit(snprintf clamp / 恒真断言)
P2-1(必修):`multi_font: class %s font %s missing, fallback global` 与上一轮修掉的
enabled 行同在 meow_font_reload_multifont() 里、被 main_menu.c:1385 每 2s 调一次;
命中「某个 font.class_* 非空但文件缺失/不可读」时最多 6 行/2s(3 行/s),比 enabled
行凶 6 倍,会让「空转 60s 日志不增长」在 class 缺失形态下失效。
改法:循环内只记「本轮缺失类别位图」,循环外经新纯函数 mf_missing_warn_mask() 做跃迁
判定(缺失集合不变 → 静默;新类别缺失 → 1 条;恢复后再缺失 → 再 1 条)。回退行为不变
(缺失类别照旧回退全局),日志文本/参数不变。另加 _Static_assert 钉住类别数 ≤32(位图宽度)。

nit:wd_log() 的 snprintf 返回值显式 clamp(原「buf≤511+前缀≤23<544」是隐式不变量);
test_wd_fw 的 "scan finds self" 原断言带 `|| np > 0` 恒真、等于没测 → 改为真查表断言
getpid() 在 wd_rg_pid[] 中且 comm 非空、并与 /proc/self/stat 的 pid/ppid 交叉核对。

验证:test_wd_fw 128 pass/0 fail(原 124,+4 来自强化后的自扫断言);
test_mf_logsig 21 pass/0 fail(原 13,+8 为 P2-1 验收:单类缺失 60s@2s 只 1 条、
缺失集合变化再 1 条、恢复后再缺失再 1 条);launcher 宿主测试 ALL PASS;
三处交叉编译 0 warning(watchdog 100,116 B/25f76950、launcherd 902,044 B/75629078、
audiod 68,732 B/ebb9a7ac 未变=bind hunk 未动)。
2026-11-12 08:02:22 +08:00
AskaEth 166b32d0e5 feat(watchdog): M52 资源压力可观测 + multi_font 日志去刷屏 + audiod bind 失败即退
- watchdog(新 wd_resguard.h + meow_watchdog.c):每 10s 采 /proc/meminfo
  (MemFree/MemAvailable)、进程总数、单父进程子进程数前 5 名;过阈值时向
  MeowOS/logs 打 RESGUARD 告警,带等级/命中规则/进程名+PID 名单。零 fork、零堆分配、
  不发任何信号(只报警)。阈值依据 = 真机实测 56872kB / 空闲 100 进程 / maxch=11,
  推导写在 wd_resguard.h 文件头;日志只在等级跃迁或同态每 5 分钟复述。
- launcherd:multi_font 那行「每 2s 无条件打一条」改为只在配置签名跃迁时打
  (新 mf_logsig.h 纯函数 + test_mf_logsig 宿主单测,接进 build_launcher.sh test)。
  真机 6603 行日志里该行占 5297 行(80%)。
- audiod:io bind 失败不再只记一行就 return(旧行为让主线程 watchdog 空转 720s、
  客户端全 ECONNREFUSED),改为明确报错 + g_stop 干净退出;仅动 io_thread 一个 hunk。
2026-11-12 08:02:22 +08:00
AskaEth f3d7b386b2 Merge branch 'feat/m54-hcitool-acl-inbound-connection-parsi' 2026-11-11 21:50:57 +08:00
AskaEth 1e2604953d docs(bt): M54 r1 评审 P2 — 入站 hcitool con 样本内联 od -c 原始捕获(仅注释)
评审指出 S_CON_INBOUND 注释声称「od -c 逐字节校对」,但仓库与设备上都找不到
那份捕获件,traceability 不可审计。把 2026-11-11 20:53(0.8.50 / 10.58.215.50 /
耳机自主回连现场)`hcitool con 2>&1 | od -c` 的原样输出内联进注释块,并写清
逐字节读法、80 字节/末偏移 0000120 的自校验、以及复核命令(printf | od -c)。
同时如实注明该次采集未单独落盘(devdocs/evidence/ 只有 m50 那一份),出站样本
仍指 devdocs/evidence/m50_bt_result_20261111.log:7-8 的 cat -A 证据。

仅注释变化:meow_netmgrd.c 一字未动,测试断言未动(宿主单测 206 ok / 0 FAIL,
连跑 3 次 ALL PASS)。
2026-11-11 21:50:28 +08:00
AskaEth 6e2774ff5c fix(bt): M54 入站 ACL(> ACL)解析 — 耳机自主回连后 connected=0 而声音正常
bt_acl_parse 原先只认行内 "< ACL ",而 hcitool con 对**远端发起**的连接打的是
"> ACL "。耳机自动回连(最常见的连接方式)时于是:bt connected 空、bt status
connected=0、bt connect 报「未生效(hcitool con 无该 ACL)」,而 audiod 用
strstr("ACL") 认得出、声音正常从耳机出 —— 用户看到的就是「看不出到底连没连」。

改法:白名单取代裸子串匹配 —— 行首(可带空白)→ '<' 或 '>' → ' ' → "ACL" → ' '
→ 17 字符 BD_ADDR(且必须在此断词:再跟 hex/':' 不算)。因此 SCO 行、表头、
截断行都不收。MAC 统一大写(与 audiod bt_dev_acl_macs / bluetoothctl 一致)。
单一解析器:bt_acl_macs / bt_acl_has_tri(写后真态复查)/ bt_connected_text /
bt_status.connected_n 全部经 bt_acl_parse,无第二套。

单测用真机原始字节(od -c 校对):
  入站 0.8.50 设备 10.58.215.50 2026-11-11 20:53 "\t> ACL 33:DE:2E:21:AD:30
  handle 11 state 1 lm MASTER AUTH ENCRYPT \n"
  出站 devdocs/evidence/m50_bt_result_20261111.log:8 "\t< ACL ... handle 0
  state 5 lm MASTER \n"
覆盖 >/</>SCO/<SCO/表头/空/NULL/无方向符/截断(行中与行尾)/伪 MAC/大小写/CRLF/
max 裁剪,另加端到端:入站时 connected、status connected=1、connect 不报未生效、
SCO-only 不计连接、disconnect 假成功对入站 ACL 也报「仍有该 ACL」。
用旧解析器跑同一套测试 18 条 FAIL(证明用例不是自证)。

真机验证:同一时刻 0.8.50 旧二进制 bt status connected=0,而按新源码交叉编译
(mipsel 静态)的探针在设备上对**实时 > ACL** 链路报 connected=1 且
bt_connected_text 列出 "Device 33:DE:2E:21:AD:30 MOONDROP Pudding"。
2026-11-11 21:50:28 +08:00
AskaEth 26cab3a0ed Merge branch 'feat/m53-devkit-timeout-utility' 2026-11-11 21:41:20 +08:00
AskaEth c93814a78b fix(m53): r1 评审 P2 — 小数后缀时长、文档数字、信号失败告警
P2-1 时长解析:后缀乘数只作用在整数部分,nsec 没跟着乘。
  0.05m 被算成 50ms(应 3s)、1.5m 算成 60.5s(应 90s)、0.5h 算成 0.5s。
  修法:sec 与 nsec 都乘 mult(各自带溢出检查,mult==1 行为不变)。
  --help 与头注释的示例改为逐个标出等价值(2.5s=2.5s、1.5m=90s、0.5h=1800s),
  不再留文档写着、实际算错的例子。
  新增 T11:宿主上编译 parser 单测(27 例,含 0.01m/0.05m/1.5m/0.5h)真跑,
  并单独点名评审抓到的三条,防止回归。

P2-2 文档数字:§2 产物表 47 708 → 49 676,合计 2 189 472 → 2 191 440;
  全表按 MANIFEST 重核(其余七项未漂移),§11.1 补上新的体积/md5/sha256。

P2-3 信号失败不静默:新增 kill_child()(kill(-pgid) 失败时 ESRCH 视为组已消失,
  退化为 kill(pid),EPERM 等明确告警)、verify_process_group()(发信号前用
  getpgid 核对建组是否生效)、父进程 setpgid 的异常 errno 告警;连兜底 kill
  都失败时退出码 125 而不是谎报 124。新增 -DTIMEOUT_TEST_SETPGID_FAIL 故障注入
  与 T12(a-d)验证告警路径可达且兜底真的杀掉子进程。

真机复验(md5 aded39e0,49 676 B):124/137/0/孙进程清零/保证挂住的命令 3.01s
被 124 结束/0.01m=0.61s、0.05m=3.01s、2.5s=2.51s/无参 125;收工 bt=0 sleep=0,
Music 199164544。回归 44 通过 0 失败。
2026-11-11 21:23:52 +08:00
AskaEth 24f1d8a682 docs(m53): timeout 上卡后的 5 行复跑自检(已真机验证) 2026-11-11 21:23:52 +08:00
AskaEth 80c0bd4eb4 feat(m53): 自写 timeout(1) 并接入 devkit 本地源码构建路径
设备没有 timeout,busybox 1.31 也无该 applet;2026-11-11 18:44 一条挂在
硬件上的 bluetoothctl 累积 55 个残留进程耗尽进程表,dropbear 无法 fork,
整机在线却不可登录。本单给设备一个可用的 timeout。

- scripts/build/devkit-src/timeout.c(新,Apache-2.0,489 行)
  独立实现,未拷贝 busybox/GPL 代码;fork+setpgid(0,0) 后 kill(-pgid),
  孙进程一并终止;waitpid 回收直接子进程不留僵尸;-s/-k 两阶段终止;
  退出码 124/137/128+N/125/126/127 对齐 coreutils 语义。
  计时不用 timerfd(musl 的 timerfd_settime 在 4.4 上静默失效,实测
  timeout 2 sleep 30 等满 30s 才返回),改用 setitimer(ITIMER_REAL)+SIGALRM,
  与 SIGCHLD 一起进 signalfd,由一次 ppoll 等待,零轮询、不读时钟。
- build_devkit.sh:新增 build_local(B 路径,不碰上游 procps/lsof/ncurses
  流程);TOOLS/TOOL_NAMES/MANIFEST/licenses/stamp/--with-sources 接入;
  产物同时标注 Apache-2.0 并随附许可证原文。
- test_build_devkit.sh:T9(产物静态/架构/零 NEEDED/MANIFEST/license)、
  T10(源码纪律:独立实现、不调 clock_gettime、不用 timerfd、进程组模型)。
- DEVKIT.md/README.md:产物表、PATH 实测(ssh 非登录 shell 无 opt/bin,
  且设备无 bash → 用 sh -lc 或绝对路径)、禁裸跑纪律与 18:44 事故时间线、
  timerfd 静默失效的第二类 time64 坑、2026-11-11 真机验收原始输出。
2026-11-11 21:23:52 +08:00
AskaEth a41b61198e Merge branch 'feat/m51-final-binary-rebuild-batch-and-firmw' 2026-11-11 21:20:24 +08:00
AskaEth 0d1b2d4c19 fix(fw): M51 r2 — 重编脚本开箱可用、自证门禁不再假绿、归因与 desc 纠正
r2 评审 p2-6items 六条全部处理;**产物字节未变**(5 件 md5 与 31d4c22 完全一致)。

P2-1/P2-4 归因纠正(31d4c22 提交说明里那两行是错的,以本条为准):
  - bootmenu 的 +1728 **不是**工具链代差,是 M23 源码(m2.29→m2.30)。证据:旧件
    `readelf -p .comment` 已是 clang 22.1.8(与新件同代);旧件版本串 `0.1.0-m2.29-takeover`,
    而钉值 commit 884d4c4 的源码已是 m2.30;用 6364498(m2.29 树)+ 今天的工具链重编得
    157108/0507739d,与旧钉值件逐字节相同。
  - launcherd 的 −512 也不是编译器:新旧 .comment 集合一致(那道 21.1.8 来自 meow_ui.a 里的
    meow_font.o),真实原因是 SDK 静态库从 86800633 换成 cc0ed49b——用 58cdb73 源码 +
    86800633 的 .a 重编得 894940/cea0a735,与旧钉值件逐字节相同。
  - 因此**只有 meow_audiod 真有工具链代差**(旧 clang/LLD 21.1.8 → 新 22.1.8)。

P2-2 主 checkout 定位:`git rev-parse --git-common-dir` 给的是主仓库 .git 的**绝对**路径,
  父目录才是主 checkout;旧版把绝对路径当相对路径拼到 $ROOT 下,`cd` 失败被 `set -e`
  直接终止 → 干净 worktree 里静默 exit 1。现为 `cd "$_gcd/.."` + 全部 `|| true`,
  找不到 meow_ui.a 时**先**打印诊断(缺什么、试过哪些路径、怎么覆盖)再退出。

P2-3 .tools 兜底层级:旧版把 $COMPAT_HOME/Project/R3ProII 指向 ROOT,脚本里的
  `$HOME/Project/R3ProII/.tools` 就落到 `<checkout>/.tools`(干净 worktree 不存在 → 127)。
  现在造一个真目录,只放 `.tools -> ~/.tools` 与 `patch_v2 -> $ROOT/patch_v2`;
  零手工准备即可开箱重编。README 措辞同步改准。

P2-5 门禁假绿:test_time32_abi.sh 的退出码现计入 FAIL,脚本缺失也记 FAIL。
  失败桩自证:`PASS=33 FAIL=1`、exit 1(原为 PASS=33 FAIL=0 / exit 0)。

P2-6 desc:meow_audio 的 `volume` → 真实子命令 `status / tone / route / vol / output / monitor`
  (并避开 `|`,否则破坏清单 4 字段格式);bootmenu 的读取键改为 display.brightness
  (display.brightness_min 只是下限 clamp);netmgrd 去掉「以太网连接」的夸大说法。
2026-11-11 21:02:09 +08:00
AskaEth 31d4c22806 fix(fw): M51 刷机前批次 — 5 组二进制从最新 main 重编 + 钉值回写 + 语义指纹自证
本轮终结「二进制与源码两条轨道各走各的」:M23/M43/M47/M49/M50 只交源码,产物统一由本单
从 main 5a06a8c 一次重编。重编后 md5 全变(旧钉值来自另一代工具链/另一份源码),语义由
scripts/build/verify_fingerprints.sh 逐条自证。

- meow_bootmenu     157108 → 158836  8bce6558  (源码与 884d4c4 一致,变化全部来自工具链代差)
- meow_netmgrd      212740 → 222068  e62e7512  (M49 蓝牙控制面 + WiFi 连接修复,+9328 全为源码)
- meow_audiod       153484 →  68604  9f58b219  (musl 静态 → glibc-2.22 动态,链 libasound.so.2;M50)
- meow_audio         88888 →  88888  3909c8d2  (与钉值逐字节一致,重编即复现)
- meow_launcherd    894940 → 901020  e21450ff  (M47 蓝牙/WiFi 三态开关 +6592)

fw_content.conf:上述 6 行(netmgrd 双路径)md5 回写;desc 改为中性描述(文件是什么/什么形态),
不再写未进产物的功能版本号(M48 查出 bootmenu/netmgrd/audiod 三处 desc 撑谎)。
30/30 行 md5 与磁盘一致、全部 4 字段。

新增 scripts/build/rebuild_binaries.sh:干净 checkout / worktree 里的重编驱动(兼容 HOME +
.tools/meow_ui.a 兜底,不改各组件 build_*.sh 的本机硬编码路径)。
新增 scripts/build/verify_fingerprints.sh:字符串/动态段/4263 反汇编判据逐条断言。
2026-11-11 20:29:21 +08:00
AskaEth 5a06a8cc1a Merge branch 'feat/m50-audiod-bluetooth-output-end-to-end' 2026-11-11 20:14:50 +08:00
AskaEth d36a3a02a6 fix(audiod): build_audiod.sh 评审 P2 两条 — CC 单一命令词 + ZIG guard 下沉;顺手两处可见性
评审 r2(p2-2items/merge,reviewed 234b4d1)两条:

① P2-1 `CC="zig cc --target=mipsel-linux-musleabi"` 是死代码:多词整串存进变量,`"$CC"`
   按一个命令名展开 → `command not found`(评审实测 exit 127)。**M51 在 git worktree 里跑
   必踩**(`$ROOT/.tools/zig-mipsel-cc` 基本不存在 → 走 PATH 回退 → meow_audio 编不出来,
   而 meow_audio 正是 M43 带 time32 的那个 CLI)。
   - `CC` 只存**单个命令词**,多词 target flags 另放 `CC_EXTRA`(未加引号展开,故意分词);
   - PATH 回退改为 `CC="$(command -v zig)"` + `CC_EXTRA="cc --target=mipsel-linux-musleabi"`;
   - cli 分支加 `[ -n "$CC" ] || { 明确提示 CC=/ZIG= 或把 zig 放进 PATH; exit 1; }`——
     缺编译器时是**清晰错误**,不再是 command not found。

② P2-2 `[ -n "$ZIG" ] || exit 1` 放得太早:`$ZIG` 只有 audiod 目标用,却在 host/cli 分支之前,
   没有 zig 的机器上 `build_audiod.sh host`(只需宿主 cc)会被无谓打断。guard 下沉到 audiod
   段(MAIN 之后、LIBBT 之前),提示语也改成「audiod 目标需要它」。

顺带(评审附带观察):
③ host 目标不再用「先编一次 2>/dev/null、失败再编一次」兜底——那会把成功路径的 warning 吞掉。
   改成先跑一个 trivial 链接探针决定 `AS_LINK`(`-l:libasound.so.2` 或 `-lasound`),真正的编译
   全程保留 stderr。
④ `libasound.so.2.0.0 not found` 现在**列出试过的全部路径** + 覆盖语法(M51 在陌生环境里的
   唯一路标)。同时把 MAIN 的推导改成「git 不可用就留空」,避免产生 `/`、`$HOME` 这类伪候选。

自证(本 worktree 真跑):
- `ROOT=/nonexistent ./build_audiod.sh cli` → 正常出件 88888B/md5 3909c8d2…(评审的复现命令)
- PATH 里没有 zig → cli:`!! mipsel CC 未找到:设置 CC=…`(不再 command not found);
  audiod:`!! zig not found(audiod 目标需要它)…`
- PATH 里没有 zig → **host 目标照常 4 个产物 + test_bt_dev 71/71 + test_audiod_host 109/0**
- 正常环境两目标产物**一字未变**:meow_audiod 68604B/md5 9f58b219…、meow_audio 88888B/md5 3909c8d2…
- `test_time32_abi.sh --builds` ALL PASS,`build meow_audio src=yes macro=yes`
2026-11-11 20:05:49 +08:00
AskaEth 234b4d13b9 fix(audiod): build_audiod.sh 解 rebase 冲突后补主 checkout 回退(worktree 里也能找到固件树)
rebase 到 main(ca042aa,M43 已合入)时 build_audiod.sh 冲突,解决原则:M43 的 cli 目标
time32 接入与 M50 的路径环境变量化**两者都保留**,守护目标仍不链 time32:
  cli    : "$CC" $INC ... -static -DMEOW_TIME32_LIBC_OVERRIDES meow_audio.c ../sdk/meow_time32.c
  audiod : "$ZIG" cc --target=mipsel-linux-gnueabihf.2.22 ... meow_audiod.c bt_dev.c -lpthread -L"$LIBBT" -lasound
(M43 的守护例外注释按 main 的新措辞保留;LIBBT/ASOUND_SRC 环境变量覆盖与 host 链接兜底保留。)

解冲突时发现一个实际问题:`devdocs/fw-baseline/...` 与 `fw_apps/build/...` 这两条 ASOUND_SRC
候选**都没有入库**,在 git worktree 里运行时 REPO=worktree 根,两条都不存在 → 守护目标
直接 exit 1(本仓库正是按 worktree 并行开发的)。补一条回退:用
`git rev-parse --git-common-dir` 的父目录找到**主 checkout**(worktree 下即主仓根),
把主 checkout 里的 devdocs/ 与 fw_apps/ 也列为候选;普通 checkout 下 MAIN==REPO,行为不变。

自证(解完冲突、真实跑本脚本):meow_audiod 68604B/md5 9f58b219…(glibc-dynamic,
NEEDED=libasound.so.2/libc.so.6/libpthread.so.0,interpreter /lib/ld.so.1);
meow_audio 88888B/md5 3909c8d2…(static,含 S24/S32,4263=3/4403=0/16B-layout=OK);
test_time32_abi.sh --builds ALL PASS(build meow_audio src=yes macro=yes)。
2026-11-11 19:38:32 +08:00
AskaEth 6e67c74fe6 fix(audiod): M50 r3 评审 P2 三条 — fd 卫生 / 端点路径按 MAC 精确匹配 / 多 ACL 逐个试开
评审 r1(p2-3items/merge,reviewed 26c6077)三条:

① P2-1 dev_close() 在「BT 写路径判死 → 回落」这条路上会 close(0) 误关 stdin:
   播放线程处理 g_bt_fail_req 时**提前把 g_bt_dev 清 0**,下拍 dev_close() 就落到
   close(g_dev_fd) 分支,而 BT 打开成功时 dev_open() 返回 0(g_dev_fd==0)。
   - 去掉那行提前清零(让 dev_close 走 BT 分支自己清),并同时置 g_dev_fd = -1 做第二道保险;
   - 把关闭决策抽成纯函数 bt_dev_close_kind(bt_dev, fd)(0=NONE/1=BT/2=FD):**fd 0/1/2
     一律不当设备 fd 关**,宿主单测 D10 覆盖(含 bt_dev=0+fd=0 这条回归用例);
   - 补不变式「设备 fd 恒 >2」:dev_open 的 DAC/WAV open 过 fd_high()(0..2 时 F_DUPFD 升位,
     dup 失败则关掉并报打开失败——绝不留 stdio 区间里没人认领的设备 fd);
   - dev_close 注释改成诚实版:不再声称「已消除」,写明「BT 分支内自行清理;调用方不得
     提前清 g_bt_dev」。

② P2-2 g_bt_pcm 只写一次 → 换耳机 A→B 后 volume/info 打在 A 的端点上:
   - bt_refresh_endpoint() 每次先清空再探测,并用新 bt_dev_pick_endpoint() **按 g_bt_mac
     精确挑端点**(找不到就留空,宁可跳过 volume/info 也不串台);
   - bt_vol_full() 删除「拿 list-pcms 第一条端点凑数」的兜底(同样会串台);
   - 单测 D9:两条端点里按 MAC 分别取 A/B、未知 MAC 返回 -1。

③ P2-3 bt_dev_acl_mac() 只取第一条 ACL,多设备(手机/手柄并存,M49 正在测 BT 控制面)
   会挑错设备 → 耳机连着却回落 DAC:
   - 改名/重写为 bt_dev_acl_macs():收集**全部** ACL MAC(去重、上限由调用方给);
   - bt_find_mac() → bt_collect_candidates():①list-pcms 里已有端点的设备优先(真 A2DP)
     ②其后全部 ACL 设备;候选按可信度排序、去重;
   - bt_try_open() 改为对候选**轮流试开**,3 次尝试、300ms 间隔(1 候选=A,A,A 与 r2 语义
     相同;2 候选=A,B,A;3 候选=A,B,C),仍受退避门控;**删除裸 "bluealsa" 兜底**
     (那会落到「最近连接」的错设备,review §P2-3 明确指出);
   - 单测 D8c/D8d:两条 ACL 全取到且保序、三条含重复时去重并受上限裁剪。

顺带清死代码(评审「其余观察 2」):删除 bt_dev_count_src()、bt_dev_endpoint_path()
(生产路径已无调用者,前者早已只剩单测)。

宿主:test_bt_dev 71/71(新增 D8c/D8d/D9/D10 共 10 条),test_audiod_host 109/0,
交叉编译 0 warning(68604B,md5 9f58b2197ed91d24951918d41a78cf29,NEEDED 三条、
interpreter /lib/ld.so.1、GLIBC≤2.17),test_time32_abi.sh --builds 里
`build meow_audio src=yes macro=yes` 保持。真机项(耳机)未动:本轮不连蓝牙、不扫设备。
2026-11-11 19:37:03 +08:00
AskaEth 6e7c0d00c0 fix(audiod): M50 r2 蓝牙打开时序 — MAC 改从 hcitool con 取(list-pcms 易失)+ 短重试 + 行内匹配修正
真机(2026-11-11 18:2x/18:3x,MOONDROP Pudding 33:DE:2E:21:AD:30)暴露两个时序/解析问题:

① **list-pcms 易失,不能当 MAC 主来源**:bluealsa 的 a2dpsrc PCM 对象只在 A2DP profile
   配置好后的秒级窗口内存在(无人开 PCM 时 1~3s 就消失)。r1 用它取 MAC → 探针一错过窗口
   就 btdev=- / bt=fail 回落。改为 **主来源 `hcitool con` 的 ACL 行**(内核 ACL 是连接权威,
   开销一个 popen),拿到 MAC 直接 snd_pcm_open("bluealsa:DEV=<mac>,...") 让 bluealsa
   在 open 时自己创建 PCM;list-pcms 降级为兜底来源。
② **ACL 刚建立时 profile 可能还没配置好** → open 会瞬时 ENODEV。改 3 次 × 300ms 短重试
   (上限 600ms,不占播放线程太久);失败后才记为 fail#n 进退避(仍只在开流/重配边界)。
③ **端点路径要 D-Bus 全串**(`bluealsa-cli volume|info` 要它,不是 MAC):新增
   bt_dev_endpoint_path();PCM 撑住后再 list-pcms 取路径(此时稳定)→ bt_refresh_endpoint()。
④ **命令换行 bug**:旧 vol_full 的 token 扫描不认 '\n',真机把端点路径连换行一起塞进
   `bluealsa-cli volume <path>\n 127`,第二行成了独立命令 → 音量设置实际没下发。改为
   bt_dev_endpoint_path() 严格按空白/\r/\n 截断。
⑤ **行内匹配 bug**:is_src_line() 对整串 strstr 会借到下一行的 a2dpsrc —— hfphf/source
   行被误判成 A2DP 源端点(count_src 少算/endpoint_path 取错行)。改 line_is_src() 只在
   本行内判定。
⑥ STATE 增 `btacodec=`:读回 `bluealsa-cli info` 的 Selected codec。真机 MOONDROP Pudding
   只有 SBC/AAC,settings 请求 LDAC 时 bluealsa 接受参数但实际选 AAC —— 面板必须看到
   实际值(btcodec= 仍是请求值,btacodec= 是实际值)。
⑦ AVRCP 满量程 127(bluealsa-cli volume 取值范围 [0,127],旧值 100 只到 79%)。

宿主:test_bt_dev 66/66(新增 hcitool con 真机原文 / 端点路径 / info 原文解析用例),
test_audiod_host 109/0,交叉编译 0 warning(67944B,md5 0ec3b9a6…)。
真机:r1 二进制 18:26 那次已实测 BT 出声(bt=ok / played=55296 / bluealsa Running:true /
Delay 17.2ms / Volume 127),18:33 起耳机自动关机,r2 的 hcitool-con 主路径未能在真机复验
(耳机不可达),详见报告。
2026-11-11 19:37:03 +08:00
AskaEth 33bef36d16 fix(audiod): M50 蓝牙输出端到端 — 内联 DEV/CODEC 打开 + 失败必回落 DAC + MAC 解析修正
① ALSA 正确用法(核心):不再写 /usr/data/alsa.conf(HiBy 播放器数据文件、语法非法、
   libasound 从不加载,改它对解析零影响)。改为 snd_pcm_open("bluealsa:DEV=<mac>,
   PROFILE=a2dp,CODEC=<token>"),参数名严格取 /etc/alsa/conf.d/20-bluealsa.conf 的
   pcm.bluealsa @args[DEV PROFILE CODEC VOL SOFTVOL DELAY SRV](实测未知参数会被
   alsa-lib 拒绝);删掉 bt_apply_device_conf/bt_apply_codec_conf 两条改写分支。
② 硬 bug:bt_dev_detect_mac() 旧逻辑取端点 token「最后一个 '/' 之后」——真机
   bluealsa-cli list-pcms 输出是 D-Bus 路径 /org/bluealsa/hci0/dev_33_DE_2E_21_AD_30/
   a2dpsrc/source,旧代码会把 "source" 当 MAC(旧单测喂的是人造输入所以一直是绿的)。
   改按 dev_XX_XX_XX_XX_XX_XX 段解析,兼容老式 a2dpsrc/<MAC>。
③ codec:settings bt.codec 经 bt_dev_codec_token() 归一成 bluealsa canonical 名
   (v4.1.1 a2dp-codecs.c 别名表,strcasecmp);auto/未知 → unchanged(不干预协商)。
④ 失败必回落(硬验收):新增 BT_ST_IDLE/OK/FAIL 状态机 —— 无端点/未连接/set_params
   失败/写路径致命错(ENODEV 等)都回落 DAC 继续出声,绝不整机静音;回落前同步武装
   numid=9(hbc3000 未烧+enable 开 PCM 会楔死整机);STATE 增 bt=/btdev=/btcodec=
   三键供面板显示「输出=蓝牙(失败)」,volctl 回落时报 dac。
⑤ 防重试风暴:只在开流/重配边界重试,指数退避 2s→60s(bt_dev_should_try),写循环内
   绝不重试;用户显式切目标才清退避。
⑥ 音量:BT 真走 A2DP 时仍不下发 DAC 反转(回归确认),回落 DAC 时按 DAC 音量控;
   AVRCP 一键置最大从 100 改 127(bluealsa-cli volume 满量程 [0,127],旧值只到 79%)。
⑦ build_audiod.sh:ROOT/REPO 自动推导,ZIG/CC/LIBBT/ASOUND_SRC 可环境变量覆盖(旧
   hiby_os_crack 路径在多数 checkout 里不存在),宿主链接 -l:libasound.so.2 失败退
   -lasound;保留 M43 的 meow_audio time32 cli 目标。

宿主:test_bt_dev 51/51(真机 D-Bus 路径格式/别名表/退避),test_audiod_host 109/0,
0 warning。真机:新二进制 glibc-dynamic 上机正常,DAC 出声回归通过,无端点回落 DAC
出声(played/rms 实证),/usr/data/alsa.conf 全流程零写入;耳机出声一项待耳机上电。

不提交二进制、不改 fw_content.conf 钉值(按塔交付形态政策,由 M51 统一重编)。
2026-11-11 19:37:03 +08:00
AskaEth ca042aa79c Merge branch 'feat/m49-netmgrd-bt-control-plane-rebuild-and' 2026-11-11 19:34:41 +08:00
worker-m49 239cebae01 fix(bt): M49 r1 评审 — 写操作预算压回 1500ms 窗口内 + A2DP 总预算/TTL 缓存 + 测试可重复 + rfkill 顺序;rebase 到 main 保住 M43 time32
rebase 到 main(00e7271,M43 time32 已合入) 时解开 build_netmgrd.sh 冲突:**两者都保留**——
M43 的 `../sdk/meow_time32.c` + `-DMEOW_TIME32_LIBC_OVERRIDES`,以及我的 ROOT/ZIGCC/OUT
环境变量化。编译改在 mktemp 目录里做(产物名仍是字面量 `-o meow_netmgrd`,供
sdk/test_time32_abi.sh 的按名定位),编完再落到 OUT —— 设了 OUT 也不会覆盖/删除仓库里
那份被跟踪的二进制(r1 期间 mv 曾把它删掉过)。门禁自证:`--builds` → build meow_netmgrd
src=yes macro=yes;`--no-builds` → 4263=3 4403=0 16B-layout=OK ALL PASS。

P1-1(写操作越过客户端 1500ms 窗口):预算模型改成**每一层都受限**——
`BT_ACT_TMO_MS 800 / BT_ACT_SHORT_MS 600 / BT_VERIFY_BUDGET_MS 220 / gap 90`,
`bt_poll_probe` 把**剩余预算**传给探测函数(`bt_acl_has_tri`/`bt_info_flag` 新增 tmo 形参),
探测自身那次 run_cmd 也受同一个 deadline 约束;`bt_onoff` 同理(up 失败只在剩余预算内重试
一次);成功文案不再补设备名(省掉 1-2 次 fork,UI 本来就按 MAC join)。
探测改为三态:命令失败/超时 = -1(**未知**),不再被当成「没有 ACL」——否则 disconnect 会在
查不到的情况下报「已断开」,又是一次假成功。
实测(宿主桩驱动 bt_ctl_execute,命令卡死 5s 的形态):connect 1042-1047ms、pair 1042-1045ms、
bt on 831ms,全部 < 1450 且有 400ms+ 余量(r1 实测 1528/2424ms)。设备侧实测 fork 成本
≈25-30ms/次(bt a2dp 50ms、bt status 100ms),余量足够。

P2-1(A2DP 1+N 子进程塞进 status JSON):`bt_a2dp_query(buf,cap,budget)` 加**总预算**,
list-pcms 单命令 ≤400ms、每个 info ≤300ms、剩下端点超预算就不再查;`bt a2dp` 命令用 900ms。
status JSON 不在无连接时查(0 次 fork),有连接时走 10s TTL 缓存 + 500ms 预算;
`a2dp_codec`/`a2dp_rate` 字段保留(MeowPanel btCard 依赖,见 docs/plan/FUTURE_MEOWPANEL.md:48)。
实测 4 端点全卡死:911ms(r1 6053ms);status JSON 首次 350-384ms、缓存命中 31-49ms。

P2-2(test_bt_ctl 不可重复):开跑先 `rm -rf` 状态目录、退出再清;连跑 3 次 ALL PASS(贴了输出)。

P2-3(bt_onoff 顺序 + 多节点):开=先 rfkill unblock(**全部**同类型节点)再 hciconfig up,
up 未生效时剩余预算内重试一次;关=先 down 再 block。新增 `rfkill_set_block_all()`(旧
`rfkill_set_block` 仍只写首个节点,WiFi 路径行为不变)。桩改成「blocked 时 up 必败」,并加了
反向用例证明桩真的会失败(否则断言空转)。

P3:①info 的 RUNCMD_EMPTY(exit 0 无输出)不再当失败跳过;②`bt_pcm_paths_parse` 的
`/a2dpsrc` 搜索限制在行内 [p,e)(原来会跨 \n 命中下一行);③`bt_devices_text` 注释写明
「本轮扫描见到过的设备」而非「此刻在场」([DEL] 不删除、devices 块含 BlueZ 历史缓存)。

附带(塔 11:06 建议,BT 作用域内先落地):连续 3 次 RUNCMD_TIMEOUT 后熔断 30s,冷却期内
直接回「熔断中」不再 fork(真机 55 个挂死 bluetoothctl 吃光进程槽的事故);任何非超时返回清零。
单测覆盖:第 4 次调用 0ms 返回。范围只限 BT 写操作,WiFi/DNS 调用点未动。

真机(本轮只读,设备已恢复在线):新件 md5 e62e7512 上卡只读验收 status/paired/connected/
devices/a2dp 全通;`ps w | grep -c bluetoothctl` 前后均为 2(≤3);Music du 199164544;
临时目录/独立实例清理干净,/usr/bin 未动。
2026-11-11 19:15:00 +08:00
worker-m49 370c290160 fix(bt): M49 蓝牙控制面重建 — paired-devices/a2dp list-pcms+info/run_cmd 四态+写后真态复查+扫描 ANSI 解析
真机 0.8.49 现场 /usr/bin/meow_netmgrd 是 P1(eb07415) 之前的旧件:`bt status` 无
connected= 字段、`bt connected|devices|a2dp` 连子命令都不存在(被当成写级拒绝)、
`bt paired` 落到 `bluetoothctl paired` → "Invalid command in menu main"。上游只有
NETMGR_UNREACH 才回退直连,INVAL/PERM 直接返回 → 三个列表恒空。

本单修掉「部署新件后依然存在」的三个真 bug + 一个解析缺陷:

1. `bluetoothctl paired` → `paired-devices`(bt_status 计数、bt paired 子命令两处);
   `devices` 只用于补名,不再当配对权威。
2. A2DP:v4.1.1 没有 `a2dp list`。改为 `list-pcms` 取端点 → 每端点 `info <path>`;
   MAC 只从 D-Bus 路径的 `dev_XX_..` 段解(真机路径带 `/a2dpsrc/source` 段,
   按「最后一段取 token」会拿到 "source")。info 查不到就跳过,不编造。
3. `run_cmd` 四态(成功有输出/成功无输出/非 0 退出/超时/起不来)+ stderr 有界抓取
   (≤256B):旧实现只回字节数、失败也返回 0 → on/off/connect/disconnect/pair 恒报成功。
   写操作改为下发后复查真态(hcitool con / bluetoothctl info Paired: / hciconfig
   power),超时或未收敛如实报 MEOW_ERR_NODEV。兼容外壳 run_cmd() 契约不变。
4. 扫描缓存:实时 `[NEW] Device` 行以 ANSI ESC 开头(真机 od -c 证据),旧解析器只认
   行首 → 实时行全丢。新增 bt_clean_ansi()/bt_scan_line_parse():剥 ESC/CSI/控制符、
   去 `[bluetooth]# ` 提示符、只认 [NEW] 与裸 Device、排除 [CHG]/[DEL];bt_scan_on 改用
   g_bluetoothctl(原来硬编码,桩对 scan 无效)。

解析器全部抽成纯函数并用真机原始字节做单测(test_bt_ctl.c 扩充:四态、ANSI 样本、
截断/空输出、list-pcms/info、hcitool con)。test_netmgrd.sh 桩改 list-pcms/info +
真机 ANSI 缓存样本;顺带修掉两处宿主可移植性问题(/tmp 不可写→$TMPDIR、桩脚本在
PATH=/bin:/usr/bin 下用了 cat/rm→改 shell 内建,test_wifi_connect 的 g_wpa_cli 悬垂
栈指针)。

按塔要求:本 commit 不含二进制、不改 fw_content.conf(netmgrd 产物由 M51 统一重编)。
build_netmgrd.sh 只做 ROOT/ZIGCC/OUT 环境变量化(旧硬编码 $HOME/Project/R3ProII),
M51 复用;time32 接入仍归 M43。
2026-11-11 18:56:06 +08:00
AskaEth 00e7271395 Merge branch 'feat/m43-time32-rollout-to-system-components' 2026-11-11 18:51:24 +08:00
AskaEth 1ad2346d34 fix(m43): r2 评审 P2 — 构建级主判据 + 数据依赖语义 + bootmenu 注释
- test_time32_abi.sh 增加构建级主判据:解析各组件的 build_*.sh,按 -o <产物>
  定位实际编译行(自动拼接续行),断言同时出现 meow_time32.c 与
  -DMEOW_TIME32_LIBC_OVERRIDES。缺任一即 FAIL(100% 抓「漏加」)。
- 二进制 16B 断言从「空间窗口」改为「数据依赖」:0/4/8 写回必须来自
  被调函数填的 ts32 缓冲(lw off() → sw 0x0,lw off+4() → sw 0x8,
  含 sra 符号扩展),修掉无关结构体写回造成的 false positive。
  已知健康件 launcherd(非内联转换)仍 PASS;复现的评审反例 bad6b 现 FAIL。
- bootmenu 注释把 initramfs 误述改为 squashfs /usr/bin(M23 未碰该文件)。
2026-11-11 18:51:16 +08:00
AskaEth 7a32b0690e fix(m43): r1 评审 P2 — 语义断言入仓 + audiod 表述对齐产物
- test_time32_abi.sh 增加 16 字节 time64 布局断言:4263 后必须写 tv_sec 0x0/0x4
  (含 sra 符号扩展)+ tv_nsec 0x8;支持「内联」与「调用后转换」两种编译形态。
  自证:amsd 出货件 FAIL、修复后 amsd PASS、不加修复重编 sysui FAIL。
- build_audiod.sh / fw_content.conf:41 的 audiod 例外说明改为:「出货件是旧工具链
  静态 musl,4403→ENOSYS→4263+宽度转换本身健康;glibc-2.22 目标同样无 ABI 错位」,
  不再单称 glibc-dynamic。
2026-11-11 18:51:16 +08:00
AskaEth 884d4c4891 fix(fw): M43 系统组件接入 o32 time32(9 组件 + meow_audio CLI)
- 构建脚本链 ../sdk/meow_time32.c + -DMEOW_TIME32_LIBC_OVERRIDES:
  sysui/svcd/bootmenu/amsd/netmgrd/crash/svc_perf/powermgr/meowpanel,
  以及 musl-static 的 meow_audio CLI(monitor 取时/轮询)
- meow_audiod 守护例外:glibc-2.22 time_t 32 位,无 musl time64 ABI 错位,不链
- 重编 10 个产物:反汇编 4263=3 / 4403=0,__clock_gettime 按 16 字节布局写回
- test_time32_abi.sh 收紧为 addiu 系统调用号匹配,避免 .text+0x1133x 偏移误报
- fw_content.conf 同步全部新 md5(清单逐行校验通过)
2026-11-11 18:51:16 +08:00
AskaEth 96c15a06ec Merge branch 'feat/m24-bootmenu-hibyos-brightness-floor' 2026-11-11 18:42:54 +08:00
AskaEth a9281c2d64 fix(bootmenu): M23 评审 r2 — 电源键锁存取代「3s 抑制」,缺键视同不可信
P2-2(r2):删除「电源键按下后 3s 内不补」的定时抑制 —— 它到期就会把用户刚熄的屏点亮
(窗内可重复),且该行为被单测固定。改为**锁存**:扫描窗内一看到电源键按下边沿就置
scan_pwr_seen=1,本次窗口内此后一律不补(why="power-key-latched"),窗口结束才清零。
理由:Bug 4 保护的是「无人值守时媒体扫描导致黑屏几分钟」;用户已按键介入,需要亮屏时
自己再按一次即可。锁存同时覆盖「播放器只写 brightness=0 不改 bl_power」与「HibyOS 自动
超时熄屏」两种残余。

bl_power 门保留但降级为纯保险,注释改成诚实表述:据 patch_v2/meow_launcherd/backlight.c:90-95
实测,本机播放器熄屏(brightness 0 + FBIOBLANK 4)后 bl_power **仍为 0**,该门在播放器熄屏
场景不生效;真正的门是电源键锁存。(上一轮注释里的「播放器灭屏会置 bl_power>0」是反向推断,
已删。)

顺手关掉评审指出的竞态:亮度保底判定块从主循环前半(keypoll 之前)移到电源键边沿更新之后,
电源按下与播放器写 0 落在同一 poll 间隙时不会再出现一轮「没看到电源键就补亮」。

非阻塞 1(采纳):settings 文件存在但缺 display.brightness 键 → 返回 -1(视同不可信),
不再走 launcher 设置表的 101→100;否则扫描窗待机补 100 比老 bug 的 60 更亮。

单测(52 → 56 条):
- 删「power key expired -> floors」(把 bug 固定成期望的那条),换「latch 在窗口内永不过期」
  「下一轮窗口(锁存清零)恢复正常补」「latched 时面板暗也不写、why=power-key-latched」;
- 「missing key -> 100」改「missing key -> -1 (untrusted)」,并补 e2e「缺键 → saved=10 不是 100」;
- 两条名字去 degraded:`floor: saved 80 (from settings) floors at 80`、
  `hotkey: blank -> saved 60 (settings value)`(降级路径已封顶 10,80/60 只可能来自 settings)。

只交源码:不含二进制、不改 fw_content.conf、不动 build_bootmenu.sh。
2026-11-11 18:42:41 +08:00
AskaEth 8cd6e21cad fix(bootmenu): M23 评审 r1 — 修 P2-1 降级路径 + P2-2 Bug4 防护退化
P2-1(降级路径把老 bug 请回来):settings 不可读时不再原样采信面板瞬时值。
  bm_bri_pick_saved 的 panel 分支改为 min(面板, BM_BRI_SAFE_LOW),src 改名 panel-capped;
  BM_BRI_SAFE_LOW 20 → 10,与 meow_watchdog 的 WD_BRI_SAFE_LOW 对齐(同一系统同一个安全下限)。
  这样 settings 不可读 + 面板 60(播放器启动画面值)时 saved 是 10,扫描窗/确认框不会再写 60。
  注释改掉「与 wd_bri_decide 同构」的断言:方向相同,但 bootmenu 没有运行期亮度快照,
  降级走安全下限而不是 M20 的「快照 → cur>0 不写」链。
  单测:把原先钉死 bug 的 "fallback keeps panel 60" 改成 "caps panel 60 -> SAFE_LOW 10",
  并补「比 SAFE_LOW 更暗的真实值保留」「SAFE_LOW 与 M20 对齐」两个断言。

P2-2(busy 门可能让 Bug 4 防护变成空操作):删除 /proc D 态门,触发条件回到 pre-M23 的
  「窗口内 + 面板暗(bri<=0)+ 面板未下电(bl_power<=0)」。
  避免「熄屏瞬间爆亮」改由两条可观测信号承担:bl_power>0(播放器 FBIOBLANK)与
  「电源键按下后 3s 内不补」(supervisor 已在 10ms 轮询 evdev,记录按下边沿)。
  单测补 Bug4 防退化回归用例(窗口剩余 89999ms、面板暗、未下电 → 必须补)。

评审非阻塞三条:
- 日志节流:同一原因 ≥2s 一条、原因跃迁也 ≥1s(防 visible↔scan-floor 每 400ms 交替刷屏,
  90s 窗口最坏 ~90 行);日志字段 busy= 换成 pwr_ago=。
- 单测复跑配方补 TMPDIR;tmp_root() 增加自愈(TMPDIR → 可写 /tmp → $HOME/tmp/m23test)。
- 验收配方(3 次亮/灭屏 + 进一次开机菜单 + 期望日志行)写进报文。

只交源码:不含二进制、不改 fw_content.conf、不动 build_bootmenu.sh。
2026-11-11 18:42:41 +08:00
AskaEth bc7bafa5dc fix(bootmenu): M23 亮度单一权威源(只交源码,二进制由 M43 统一重编)
真机 0.8.46 两个症状:
1) 开机菜单亮度 = 第一屏的 60(用户设置 1)——根因是 M6.25 的选单亮度写回只改了源码、
   二进制没重编(strings 无 display.brightness_min),本次只交源码,重编交给 M43 批次。
2) HibyOS 亮/灭屏瞬间爆亮——supervisor 把启动瞬间面板读数(播放器写的 60)当保底,
   且 >60 一律钉 60;扫描窗保底只要读到亮度<=0 就在 400ms 内写回该值,用户主动熄屏
   也会被强行点亮。

改动(全部失败也沿用现状行为):
- saved_brightness 权威源改为 settings.conf 的 display.brightness(与 watchdog/launcher
  同源同 clamp);面板读数只在 settings 不可读时兜底;两者都不可知才写 BM_BRI_SAFE_LOW。
- 旧的 `>60 → 60` 硬钉改为 clamp 进 [display.brightness_min, max_brightness](bm_bri_clamp),
  不再用常量顶替用户值;为什么不会重新引入 M2.9 开机闪屏写在 bm_bri_pick_saved 注释里。
- 扫描窗保底加「播放器忙(proc 态 D)+ 面板未下电(bl_power<=0)」两门,窗口结束彻底放手。
- 热键熄屏恢复:面板真实正值优先(HibyOS 内改过亮度),否则用 settings 值,夹进 [min,max]。
- 决策抽纯函数 bm_bri_pick_saved / bm_bri_clamp / bm_bri_floor_decide / bm_bri_hotkey_saved。
- 亮度判定/写回补 bm_log 落卡(stderr 原行全部保留),只在状态跃迁时打,不在高频路径刷日志。
- test_bm_fw.c:宿主单测(settings=1/saved=60/now=0/blank 矩阵 + clamp 矩阵 + 真文件 e2e)。
- -Wall -Wextra 清理:3 处 unsigned/int 比较、未用参数、未用变量、sys/poll.h→poll.h。

不含二进制、不改 fw_content.conf 钉值(保持 4441822d)。
2026-11-11 18:42:41 +08:00
AskaEth 63644985a6 Merge branch 'feat/m47-settings-bt-and-wifi-true-toggle-sta' 2026-11-11 18:20:27 +08:00
AskaEth f49585dd62 fix(launcher): M47 r1 评审 P2 四项 — 未知态不写盘/不顶替 + deadline 从点击起算 + 开关弹窗后置清理
评审 r1 status=p2-4items / merge=fix-then-merge(reviewed e4eda3a)。逐条:

P2-1 未知态被写成 settings=0,把失真推到别的入口
- bt_page.c / wifi_page.c:`meow_settings_set_bool(..., disp == 1)` 加 `disp >= 0` 门。
  disp == -1(daemon 与直连都读不到)时**保留用户原意图不动**。该键被 notif_center /
  settings_page 当显示态读、被 app_launcher 用来决定自动拉起 WiFi,写 0 会把「不知道」
  污染成「确定已关」。
- test_bt_page.c 5d 期望同步改:未知 → settings 保持原意图 1(不再期望「回滚为 0」)。

P2-2 收尾后「探测未知」仍显示成确定态,第三态丢失
- mtog_calc:显示态**只认 probe**,probe < 0 直接返回 -1;last_real 不再顶替确定态
  (`(void)last_real`,仅由调用方用于弹窗文案)。超时/下发失败且探测未知 → 顶栏保留
  「关+?」、行内保留「状态未知」,与文件头契约一致。
- 弹窗文案按结论区分:「无法确认当前状态(上次已知:开启/关闭/未知)」vs
  「切换未生效:当前仍为开启/关闭」——不再出现「当前仍为开启」自相矛盾(该文案只在
  probe 已知且 != 意图时出现)。日志加 elapsed。

P2-3 deadline 起点错,注释上限是假的
- bt_page/wifi_page:`deadline` 从**点击时刻**起算(含下发耗时)。
- 注释改成如实上限:快路径 1~2s / ≈16 次探测;慢路径(on-off 读超时 1.5s + hciconfig
  0.3~2s,之后一次探测 status 1.5s + 直连 1.8s ≈3.3s)总阻塞最坏 ≈7s、预算内只够 1 次
  探测,此时只能确认「探测超时」而非「设备无响应」——文案已按此区分。异步化留给后续单。

P2-4 开关路径弹窗后缺 after_modal / 按键基线复位
- subpage.c:顶开关命中分支补上与行命中分支相同的后置清理(排空触摸 + 40ms 二次排空、
  触摸基线/cooldown、key_router_rebase、kd_*/kp_poll 基线、m_prev_*、after_modal=1)。
  s_dd_modal 是全屏压暗层,否则关闭后状态栏(0..27)残留压暗到下一次时钟重绘,电源/播放键
  也可能带进弹窗里的陈旧边沿。

顺带(评审观察,未改全局超时策略)
- netmgr_ipc.c:拆出 `nm_cmd_t(..., int *read_timeout)`;`nm_bt_power_state()` 走它,
  **读超时 = 状态未知且不把可达性缓存打成 down**(netmgrd 单线程忙时 `bt status` 自己
  跑 hciconfig 可能超 1.5s;旧行为会标 down 1s,令紧随的 bt_rows 闪出「服务未运行(直连
  模式)」并把后续探测切到 1.8s 直连)。WiFi/DNS 等其它调用保持旧语义(传 NULL)。

验证
- `bash patch_v2/meow_launcherd/build_launcher.sh test` → exit 0,**65 PASS / 0 FAIL / 0 warning**
  (矩阵 13 组,新增「下发失败·探测未知 → 未知不顶替」)。
- 交叉编译 0 warning;产物(临时副本,不入库)901020 B / md5 e21450ff6621a67fd854165413c58be1。
- `test_time32_abi.sh` → 4263=3 / 4403=0 ALL PASS;`pkg_install` 与 7 条切换中/未知态文案
  均在 strings 中(旧的两条「无法确认蓝牙/WiFi 状态」已合并为「无法确认当前状态(上次已知:%s)」)。
- 真机 --sysdump(只读,临时文件已删):exit 0,bt_on=1/wifi_on=1 与设备现状一致;
  Music du 前后 199164544;未替换 /usr/bin/meow_launcherd、未重启 launcherd。
- 交付形态不变:本 commit 只动 5 个源码文件;二进制与 scripts/build/fw_content.conf 对 main 零 diff。
2026-11-11 17:59:13 +08:00
AskaEth e4eda3a06f Merge branch 'main' into feat/m47-settings-bt-and-wifi-true-toggle-sta 2026-11-11 17:23:55 +08:00
AskaEth 514cc0e5d0 chore(m47): 只交源码 — 撤回本单的 launcherd 二进制与 fw 钉值
M42(pkg_install.c)已并入 main,本分支 base 的编译列表不含它,本单产出的
二进制/钉值是旧 base 的(1c88661a…)。为避免「每个单各改一次二进制和钉值」继续
制造 M48 审计查出的分叉,tower 立了 M51 统一重编批次:所有产物的最终重编、钉值
回写、语义指纹自证与 .upt 打包都在那一个受控批次里做一次。

本 commit 把两个文件恢复到 base 原值(不做任何手写钉值):
- patch_v2/meow_launcherd/meow_launcherd → 605e13a9…(base 版)
- scripts/build/fw_content.conf 的 launcherd 行 → base 原值

源码改动(bt_page/wifi_page/netmgr_ipc/net/subpage/两个测试/build_launcher.sh 的
ZIGCC/MIPS_SYSROOT 环境变量化)全部保留。合并 main 后编译列表需同时含 pkg_install.c。
2026-11-11 17:23:42 +08:00
AskaEth b20e3c43e9 fix(launcher): M47 设置页蓝牙/WiFi 开关只认真实态 — 三态查询 + 待确认收敛 + 未知态呈现
用户原话:「开关都存在显示问题 我都不能判断它是不是真的开启还是关闭(包括 WiFi 页也是)」。
根因不是「没读到硬件态」,而是「读到/没读到都被拿去当用户意图的装饰」:
- bt_page.c:103 开关初值来自 settings 布尔;:112-114 下发后立刻 g_sys.bt_on = on(显示意图);
  :129 c->bt_on = nm_bt_powered() 赋值后全文无人引用(M45 也独立发现)。
- wifi_page.c:40 读不到 settings 时默认 1(显示已开)。
- netmgrd 的 bt/wifi 查询本身把「问不到」塌缩成 0(nm_bt_powered/nm_wifi_connected)。

本单只解「状态可信」,不碰扫描/配对/音频(另有 M45/M46 单)。

1) 真实态三态(-1 未知 / 0 关 / 1 开)
- net.c 新增 net_bt_power_probe()(hciconfig hci0 UP/DOWN;无输出=未知)与
  net_wifi_power_probe()(rfkill wlan soft/hard 汇总,回退 wlan0 IFF_UP;读不到=未知)。
- netmgr_ipc.c 新增 nm_bt_power_state()(daemon bt status 的 available/powered 权威,不可达
  回退直连探测)与 nm_wifi_power_state()(netmgrd 的 wifi status 只有 wpa_state、不报射频态,
  故取本地 rfkill——daemon 的 wifi on/off 改的就是同一份内核状态)。
- 两个子页的 top_sw/rows 只读这两个函数;settings 布尔降级为「意图持久化」,不再决定外观。
- 「读不到默认 1」删除:未知就是未知。

2) 点击 → 待确认 → 真实态定稿
- 纯函数 meow_toggle_{display,pending,rollback,error}(bt_page.c,无 I/O/UI):
  只有 probe 确认才算收敛;probe 未知继续等,绝不因 last_real 恰好等于意图就宣布成功。
- 页面:落盘意图 → 下发 → 250ms 轮询(BT 上限 4s、WiFi 上限 5s;hciconfig 0.3~2s、
  wifi_up.sh 起 supplicant/DHCP 更慢,故不用 20ms 级忙轮询)→ 真实态定稿;失败/超时把
  settings 回滚到已确认真实态 + s_dd_modal 可见报错,不静默、不乐观。
- subpage.c:切换前先画「正在开启/关闭…(等待设备确认)」条(待确认态与稳定态可区分);
  切换后回读 top_sw 定稿(取消原「点了就翻面」),live tick 也回读,外部改动驻留期间可见。

3) 第三态呈现(未知 ≠ 已关)
- SDK meow_switch 只有两态:未知时开关停「关」并在左侧画灰色「?」;行内另有
  「蓝牙状态未知 / WiFi 开关状态未知」文字说明。选「?」是因为它不占用开关控件本身、
  且「关+?」不会被读成「已关」。

4) 顺带修 net.c 三条开关失真(塔追加的 M45 盘点,同一病根)
- s_net_toggle_bt() 补 rfkill soft(与 netmgrd 的 bt on/off 同一语义;开:先 unblock 再 up;
  关:先 down 再 block)。此前只发 hciconfig → 「BT 页关过再从头开」会被 rfkill 拦下。
- s_net_sync_from_sys() WiFi 判据从 wlan_connected()(已关联)改为射频态;BT 用三态,
  读不到不写(旧写法把「网卡 up 未关联」写成 0,把未知写成已关)。
- rfkill 同类多节点(本机 2 wlan + 2 bluetooth)按「任一 block 即 block」读、全部写入。

测试(`bash patch_v2/meow_launcherd/build_launcher.sh test`,新增 test 入口)
- test_bt_page.c:P4 三态与未知占位、P5 收敛四例(达成/下发失败/超时/未知)、P6 收敛矩阵
  12 组(真实开/关 × 下发成功/失败/超时 × probe 未知),全 PASS(64 条断言)。
- test_netmgr_ipc.c:新增 nm_bt_power_state 解析(powered=1/0、available=0、缺字段→-1)、
  nm_wifi_power_state 透传、不可达回退=未知、wifi_rows 未知态占位;并修掉两处既有测试缺陷
  (行内 UI 桩缺失导致本文件此前根本链不过;直连连接桩统一返回 "0\n" 导致等满 10s 必失败)。
- 交叉编译 0 warning(-Wall -Wextra,逐文件单独编 + 整链);launcherd 899388 B
  md5 1c88661a9410ac1a4139304362281cdd,同 commit 同步 scripts/build/fw_content.conf 钉值。

真机(10.58.215.50,仅只读自检,未替换 /usr/bin/meow_launcherd、未刷机)
- 二进制放 /usr/data/mnt/sd_0/MeowOS/tmp/ 跑 --sysdump:exit 0,输出 wifi_on=1 / bt_seen=1
  bt_on=0,与设备现状一致(rfkill 全 0 + wlan0 up + hci0 DOWN + netmgrd powered=0)。
- 卡上 du -s /usr/data/mnt/sd_0/Music 前后恒 199164544;临时文件已删;settings 未改。
- launcherd 在 squashfs 里 → 本改动需刷机生效。

已知不在本单:输出目标只写 settings(audiod 仅启动读一次)→ M45 finding / M46;
netmgrd 侧四处失真(未知当关、up 与 unblock 顺序、rfkill 只写首节点、wifi status 无射频字段)
已报 finding;设置主页/通知中心的开关显示仍是 settings(另立单)。
2026-11-11 17:16:28 +08:00
AskaEth 3f22158fc2 Merge branch 'feat/m42-launcherd-pkg-install-to-nyai' 2026-11-11 17:08:54 +08:00
AskaEth 58cdb73bf8 fix(launcherd): M42 包安装入口改委托 nyai — 删内联解包/验签/部署,复用 M35 安全链
- app_launcher.c 删掉 meow_pkg_install 旧内联实现(文件名推包名、硬要 .mpk 后缀、
  rm -rf 目标目录直解、失败不回滚 .bak_、不拒 ./..、系统包大小写敏感比较);
  导出保留签名逻辑不动(验签仍只 shell-out meow_verify)。
- 新增 pkg_install.c:fork+exec /usr/bin/nyai install <路径>(argv 直传不经 shell;
  MEOW_NYAI_BIN 覆写,缺省不走 PATH)。包身份/暂存/清理/防降级/大小写全由 nyai 闭环。
- stdout/stderr 分两条管道收:nyai 进度是缓冲 stdout,错误是即时 stderr,合流会把
  真错误顶掉(真机首测即踩到);错误文案只取 stderr 尾行。
- start/poll 两段式(+busy 查询)供 UI 主循环按帧驱动不假死;同步封装 20ms 切片轮询,
  180s 超时 SIGTERM→SIGKILL,不用时钟 ABI(M40 time32 教训)。
- main_menu --pkg-install 失败打 stderr 可读文案、退出码统一 0/1。
- test_pkg_install.c:44 断言(路径预检含任意文件名可装/目录/不可读/控制字符/超长、
  错误文案矩阵、argv 直传、start 立即返回、并发拒装、超时、nyai 缺失)。
- 重链 meow_launcherd 894940B/cea0a735(链 meow_ui.a 86800633,0 warning);
  fw_content.conf 同步钉值。
2026-11-11 16:37:00 +08:00
AskaEth ebd64d07e1 Merge branch 'feat/m41-reader-scroll-broken-in-folder-mode' 2026-11-11 13:23:24 +08:00
AskaEth bd6fc8da3c fix(reader): M41 r1 评审 P2 — 拖动重绘节流 + 终帧补齐 + 三条边界测试
P2-1(用户可感知):shelf_on_touch_move 每收到一条 TOUCH_MOVE 就同步全量重绘。
输入线程「坐标一变就一条 MOVE」且不合并(队列 128 条),folder ≤200 行走圆角卡
≈40ms/帧 → 主循环追不上手指、抬手后还会继续滑、极端情况丢 release。修法:
- 滚动位置仍每条 move 立即生效(纯算术,便宜);上屏节流:首帧立即画,之后每
  max(16ms, 上一帧实测耗时) 一次(自适应窗口,圆角卡 40ms 帧自动降到 ~40ms 周期,
  不再把队列拖垮)。
- 手势收尾(shelf_on_tap / shelf_on_swipe)无条件补终帧再清手势态;下一次按下也会
  先把上一次遗留的未上屏帧补上(HOME/长按等没有 TAP/SWIPE 收尾的手势)。
- 位置没变(已到顶/底)连帧都不画。

P2-2(测试盲区):
- 新增三条边界:顶栏起手不拖、副题区起手不拖、横向手势判向取消后不再滚动。
- 吞 TAP 边界带:20/40/60px(>8px 且 <65px,SDK 报 TAP)抬在目录行上不得进目录;
  6px 抖动(≤8px)仍算点击能进目录。
- 断言从「像素变了」升级为「像素**恰好**位移 N px」(内容区快照逐行比对,避开视口
  边缘裁剪带):swipe 120 → 恰好 120;拖动 60 → 恰好 60;并新增 6 步×10px 多步拖动
  断言终帧恰好 60(专测节流不丢最后一段)。
- 修掉把 20px 写成「< SHELF_DRAG_TAP_PX」的错误注释。

顺手:
- shelf_draw_notice 用 th->bg 而不是只在 shelf_redraw 赋值的 g_bg。
- build_reader.sh 用 trap 保证测试失败时也清 $TMPDIR/m41_scroll_sd。

验证:shelf_scroll_smoke 38/38、shelf_group_smoke 99/99、shelf_index_smoke 170/170。
红绿对照:dist=dy(旧 swipe 语义)→ 9 条 FAIL;拿掉「抬手补终帧」→ 恰好 1 条 FAIL
(6 步拖动终帧那条),证明节流与终帧都有测试盯着。
reader 1719220B md5 890175ec…;mpk 818162B md5 ffbb7097…(0.1.8/code9,未签名)。
2026-11-11 13:15:54 +08:00
AskaEth 2de47154bf fix(reader): M41 书架补滚动输入通路 — 列表/宫格、format/folder 全都能滑
书架(shelf.c)从 M6.40 首版起就**没有**滚动输入通路,不是 M32/M33 的回归:
  1) app_on_touch_down/move 只路由给阅读页 → 书架没有跟手拖动;
  2) shelf_on_swipe 用 dy 去 scroll_by,而 SDK 固定按 on_swipe(dir, dist, 0)
     分发(总位移在第 2 参 dx,dy 恒 0)→ 恒 scroll_by(0),死代码。
两条都在 shelf.c 自己身上,list/grid × format/folder 共用同一段,所以四种组合
全都「滑不动」(files/ncm 能滑是因为它们各自实现了 touch_down/move → scroll_by)。

修复:
- app.c:touch down/move 也路由给书架(阅读页优先级不变)。
- shelf.c:
  * 新增 shelf_on_touch_down/move 跟手拖动:按下点在内容区才起手;纵向主导(≥4px)
    才锁滚动,横向主导直接取消(不抢左缘返回);逐帧 scroll_by(-dy) 只重画内容区。
  * shelf_on_swipe 改按 dist(=dx,兼容 dy 分支)+ 方向定符号;本次手势已跟手滚过
    就不再叠加(防一划滚两倍)。
  * 拖动后的抬手吞掉 TAP(<65px 的拖动 SDK 报 TAP)→ 滑动时不会误开书/误进目录。
  * 控件重建(进/退目录、布局/分组切换、刷新)清滚动手势态,避免旧标记吃掉下一次滑动。
  * 提示条抽成 shelf_draw_notice(),滚动局部重画时照常重贴。
- 新增宿主回归 test/shelf_scroll_smoke.c(真实 shelf.c + 真实 SDK 绘制 + back_buf
  像素断言,27 条):format/folder × list/grid 都能滑;3 本书的目录滑不动;
  拖动后不误进目录、干净点击仍能进目录;folder 进出目录仍正常。把 dist=dx 改回
  dist=dy 后同一测试 6 条 FAIL(红绿对照证明它抓的就是这个 bug)。
- build_reader.sh 接入该测试;READER_SD_ROOT 加 #ifndef 以便宿主把扫描根指到临时目录。
- app.json 版本 0.1.7/code8 → 0.1.8/code9(同一版本号不发两个构建)。

宿主验证:shelf_scroll_smoke 27/27、shelf_group_smoke 99/99、shelf_index_smoke
170/170、其余 core/seg/chapter/loaders 全绿;reader 1718468B md5 259cb6ed5a60…
(meow_ui.a 用本仓 SDK 源码重编:79 成员全 MIPS32r2 o32)。真机验证待刷机
0.8.49 完成后部署(设备验证需用户手滑,面板无任意滑动注入端点)。
2026-11-11 13:01:12 +08:00
AskaEth a364932145 Merge branch 'feat/m40-power-key-time64-and-lock-screen-fix' 2026-11-11 12:35:35 +08:00