Skip to content

Releases: xf8410/hlpatch

v3.28.2-recovery.3 崩溃日志落盘修复 + 隔离采集 Hook

Choose a tag to compare

@xf8410 xf8410 released this 04 Oct 08:32

v3.28.2-recovery.3

崩溃日志落盘修复 + 隔离采集 Hook + 大响应处理 + 续养快照发布。

这次修了什么

1. 崩溃日志落盘路径修正(v3.28.2 原始 fix)

  • 之前:崩了写 /data/data/jp.pokemon.pokeuma/files/uma_predict.log
  • 现在:写 Android/media/<包名>/hachimi/
  • 旧的路径在 Android 11+ 沙箱规则下拿不到,写不进去,崩了就丢了——现在能落盘
  • 配套加 crash log 全套函数:hl_crash_log_dir() / hl_crash_log_file() / hl_crash_log_init() / hl_crash_log_file_cstr()

2. 隔离采集 Hook(这次 Vinzelles 帮修的核心)

  • 之前 hook 注册混在一起,崩了不知道是谁的锅
  • 现在每个 hook 走 hook_registry 注册 + hook_abi 准入检查(拒绝未知 ABI)
  • 唯一采样 worker + Publisher 持有锁 + 锁外析构——避免多 worker 数据竞争

3. 大响应输出风险(bounded_pages + observation_queue)

  • 之前 dump 大 JSON 容易撑爆内存 / 卡 IO
  • 现在:
    • bounded_pages:分页导出 + range 限制
    • observation_queue:有界队列 + 长度上限
    • observer_limits:单 hook 采样上限

4. 续养快照发布(recovery.3 真正解决的痛点)

  • 之前:截图续养一次 publish 大响应,App 端经常 timeout
  • 现在:derived_export + ramen_bridge 改用 V2 实例身份 + 去重 + ACK
  • recovery-candidate.yml CI 每次编译 baseline(v3.28.2 旧版)和 candidate(修复版)对比差异

文件清单

文件 大小 sha256 前缀 说明
libhachimi_ura.so 5.0 MB 41306ec2 主 SO(编译后产物)
SHA256SUMS - - sha256 校验文件
BUILD-MANIFEST.json - - 构建 manifest(编译器版本/源码 commit/工具链锁)
build.log - - cargo 编译日志(107 个 warning 都是无害的 Rust 2024 static-mut 提示)

完整 sha256:

41306ec21236b6fd7f5f16d441a8e983fdbd34ac41cf37dee2b25f8102c2ec19  libhachimi_ura.so

安装方式

把这个 .so 替换到你游戏仓位置,重新打开应用就行。

# 校验
sha256sum libhachimi_ura.so
# 对照 SHA256SUMS 里的 41306ec2... 一致就对了

这次没改的东西

  • v3.28.0 baseline commit(721c086)
  • v3.28.1 training_anim_skip(这次基线不带)
  • v3.28.2 旧版 Sigsegv guard / screen mirror A-stage(不包含,单纯 v3.28.0 + recovery.3 patch)

验证状态

  • ✅ candidate CI(nightly, candidate-only mode): 8/8 step 通过
  • ✅ recovery tests(17 个 tests/recovery/*.py + 1 个 scripts/recovery/tests/test_provenance.py): 全 pass
  • ⏳ 真机验证:未跑(需要装到手机上 verify)

来源

  • workbench 分支:workbench/so-recovery-3-20261004
  • 源码 commit:cb4ec0df80d6702a262730b2257eedc3ea4c61b5
  • v3.28.2 tag 等同:a86707ec(v3.28.1/v3.28.2 共 tag)

v3.28.8

Choose a tag to compare

@github-actions github-actions released this 02 Oct 12:42

Immutable tagged URA plugin release.

Source commit: 25c92e6
Cargo version: 3.28.8
Binary size: 4594816 bytes
SHA-256: adc57dc4fadce748116ab1601e8b5b9c5fa1eca8e9efcacbd8980be0582349ed
Workflow run: 37008144389

v3.28.7 取证校准版(严格模块标签 + 线程实名 + 跨线程保护)

Choose a tag to compare

@github-actions github-actions released this 21 Sep 01:15

v3.28.7 — 取证校准版(严格模块标签 + 线程实名 + 跨线程保护)

基线沿革:v3.28.6 同款链(v3.28.0 + cumulative 3.27.23 + SIGSEGV 防护 + 崩溃日志修复 + 摘 AI + 动态版本 + meta 直查 + crash-truth + pc/lr 取证)。
本版只校准取证仪器 + 修一个跨线程 longjmp 隐患,不改任何数据行为。

v3.28.6 现场暴露的三个仪器缺陷

v3.28.6 装机后拿到新样本:

[117] S:exit
[118] S:enter
CRASH at step 118 sig=11 addr=0x1f022058000109 tid=486774850816 pc=native+1511833d0 lr=native+1511834b0 thr=init FATAL
  1. "native" 标签是合并出来的假货:libnative.so(游戏库,base 0x7xx…)与 libnativewindow.so(系统图形库,base 0x1xxx…)被前缀规则并成一条,+0x1511833d0(≈5.6GB)这种偏移无法换算到函数——标签必须各归各行;
  2. thr=init 不可全信:线程表无"最新注册优先"判定,pthread_t 回收后老标签会被新线程顶着出来——需带写入序号 + tid→标签的离线映射(写进日志);
  3. 跨线程 longjmp 隐患(提前拆弹):旧逻辑只要全局恢复标志为真就 siglongjmp,哪怕崩溃发生在别的线程上——会跳到别人的栈上执行,属严重 UB。必须"武装线程 == 崩溃线程"才允许回收。

本版修正(全部为取证/安全,零数据路径变更)

  1. 模块表严格化:native(libnative.so)/ natwin(libnativewindow.so)/ nathelp(libnativehelper.so)各自独立成行;同名段只有相距 ≤64MB 才合并;查表改为"最小包含区间优先"(不再被大跨度条目遮蔽)。
  2. 线程实名制:每槽带写入序号,同 tid 取最新注册标签;注册时把 T:init=<tid>、T:push=<tid>、T:http=<tid> 直接写进 predict 日志——崩溃行的 tid= 可离线对上真身。
  3. armed-tid 双重门:hl_arm_recovery() 记录武装线程;handler 的 longjmp 与 RECOVERED 判词都要求"武装线程 == 崩溃线程"(两处门,grep 断言恰好 2)。
  4. 新字段:fa=0x…(内核记录的故障地址,与 addr 互相校验)、c=<n>(si_code:1=野地址 MAPERR,2=权限/执行错误 ACCERR)。
  5. 标记带线程号:S:enter tid=… / S:exit tid=…——/summary 读写线程身份直接印在轨迹里。
  6. 模块表容量 128→256 行。

下一次崩溃怎么读(v3.28.7 版)

CRASH at step N sig=11 addr=0x… tid=… pc=<mod>+0x… lr=<mod>+0x… thr=<init|push|http|?>
      fa=0x… c=<1|2> RECOVERED|FATAL
  • c=1(MAPERR):野地址访问——坏指针/内存写坏;
  • c=2(ACCERR):权限或取指错误——踩 W^X 页/调用空函数指针;
  • pc=native+… 偏移现在可换算(对应 libnative.so 内代码位置);
  • thr=init 现在可信(用紧邻的 T:init=<tid> 行核对);
  • RECOVERED 现在只会出现在"武装线程自己崩且被兜住"的情况,其余一律 FATAL(如实记录)。

保留项与排除项(与前版一致)

保留:全部数据端点、嗅探/发收包落盘、meta 直查控制台、纯数据管道(摘 AI)、动态版本、SIGSEGV 防护、崩溃日志落盘、4MiB 深栈。
不含:画面映射(截屏)、training_anim_skip、签名明文观测。

SHA256 见 SHA256SUMS / BUILD-MANIFEST.txt。

v3.28.6 崩溃归因版(pc/lr + 线程标签)

Choose a tag to compare

@github-actions github-actions released this 21 Sep 00:45

v3.28.6 — 崩溃归因版(pc/lr + 线程标签 + 细窗口标记)

基线沿革:v3.28.5 同款链(v3.28.0 + cumulative 3.27.23 + SIGSEGV 防护 + 崩溃日志修复 + 摘 AI + 动态版本 + meta 直查 + 崩溃取证/恢复窗口修复)。
本版只增强崩溃归因,不改任何数据行为(数据端点、嗅探、发收包落盘原样保留)。

v3.28.5 的战果(首次拿到真判词)

v3.28.5 装机后的新日志尾巴:

[92] S:json
[93] S:json_built
[94] S:obs_done
[95] S:cache_done
CRASH at step 95 sig=11 addr=0x72bf999f80 tid=486810748160 FATAL

三条结论:

  1. JSON 组装段无罪——S:json_built 已打出,100KB 的 format! 完整跑完;
  2. FATAL = 崩溃发生在"武装窗口"之外(S:cache_done 之后,或发生在未武装的线程上)——所以 SIGSEGV 防护没有生效不是防护失灵,是崩点不在它的射程内;
  3. addr=0x72bf999f80 位于游戏堆段(0x72…)——这是"对象生死/线程时序"层面的问题(指针拿到时对象还活着,读取时已被释放/复用),不是"字段偏移错位"层面的问题(若偏移错位,会读垃圾值或在读取当下就崩,不会全部读完、JSON 组完才崩)。

本版新增(全部只在崩溃/流水日志里追加信息,不改控制流)

  1. pc= / lr= 模块归因:崩溃日志追加 pc=<模块>+<十六进制偏移> lr=<模块>+<偏移>。
    模块表在启动时从 /proc/self/maps 解析(hlpatch / il2cpp / native / libc / main / 其他),信号处理器只读静态表、零分配。
    → 下一份日志直接指认"故障指令在哪条 SO、偏移多少"。
  2. 线程标签:push / http / init 三线程自注册(环形表,最近 16 个永不丢);崩溃行追加 thr=push | http | init | ?。
  3. 细窗口标记(7 个):S:enter、S:exit、H:ret、H:saved、P:read_done、P:push_start、P:push_done。
    把"S:cache_done 之后到下个请求"这段黑盒切成单语句级网格。
  4. 崩溃消息缓冲 200→320 字节(容纳新字段)。

下一次崩溃怎么读(对照表)

日志尾巴 含义
S:cache_done → CRASH(无 S:exit) 崩在 read_summary 收尾(observe 之后 / 写缓存之后 / 返回之前)
S:exit → CRASH(无 H:ret) 崩在 /summary 路由返回与 handle_http 之间
H:ret → CRASH(无 H:saved) 崩在 save_endpoint_log 或响应写出
P:read_done → CRASH(无 P:push_start) 崩在 push 线程读后处理
行内 thr=http / thr=push 锁定故障线程;thr=? = 未注册线程(多半是游戏线程上执行的钩子代码)
pc=il2cpp+0x… 故障指令在游戏引擎里(若经我们钩子 trampoline 进入,会同时看到 lr=hlpatch+…)
pc=hlpatch+0x… 故障指令在插件里——结合 lr= 反推调用者

保留项与排除项(与 v3.28.5 一致)

保留:SIGSEGV 真判词、恢复窗口修复、4MiB 深栈、meta 直查控制台、纯数据管道(摘 AI)、动态版本号、全部数据端点/嗅探/发收包落盘。
不含:画面映射(截屏)、training_anim_skip、签名明文观测。

SHA256 见 SHA256SUMS / BUILD-MANIFEST.txt。

v3.28.5 崩溃取证升级 + 恢复窗口修复

Choose a tag to compare

@github-actions github-actions released this 21 Sep 00:15

v3.28.5 — 崩溃取证升级 + 恢复窗口修复(稳定性版)

基线沿革:v3.28.4 同款链(v3.28.0 提交 721c086 + cumulative 3.27.23 + SIGSEGV 防护 + 崩溃日志落盘修复 + 摘 AI + 动态版本 + meta 直查)。
本版只带稳定性修复,不动任何数据端点行为。

现场(v3.28.4 两次闪退的日志尾巴)

[100] S:json
CRASH at step 100 sig=11 RECOVERED
[1] S:wdm            ← 新进程(闪退后重启)
...
[82] S:json
CRASH at step 82 sig=11 RECOVERED

审计发现(读生成的源码,只读 CI)

  1. RECOVERED 是假话:崩溃处理器在检查恢复标志之前就把 " RECOVERED"
    无条件拼进日志。两次崩溃后计数归 [1](新进程)→ longjmp 兜底那条路
    并没有救回进程(或崩点落在了无保护窗口)。
  2. 恢复窗口有洞:read_summary 在 catch_unwind 返回后立刻清掉
    SIGSEGV_RECOVERY,然后才跑 observe_ramen_transition / 写缓存 / 返回。
    尾巴这一段裸奔——在那里崩,handler 只能 re-raise 杀进程。
    日志"S:json 之后没下一条"恰好落在这个窗口(S:json → 下一轮 S:wdm 之间)。

本版修正

  1. 崩溃日志说真话
    • 改用 sigaction(SA_SIGINFO | SA_ONSTACK) + sigaltstack;
    • 日志格式升级为:
      CRASH at step N sig=11 addr=0x<故障地址> tid=<故障线程> RECOVERED|FATAL
    • 只有 longjmp 真正兜住时才写 RECOVERED;进程将死时写 FATAL;
    • 全部手工拼接、零分配(沿用裸 syscall 纪律)。
  2. 收尾切 3 个细诊断点:S:json_built(JSON 组装完成)、S:obs_done、
    S:cache_done。下一次崩,日志尾巴直接告诉你崩在"组装内 / 尾巴 / 下一轮前导"。
  3. 补恢复窗口:SIGSEGV_RECOVERY 只在 read_summary 最末端清除,
    覆盖 observe / cache / return 全尾段。
  4. 深栈:push 线程与 HTTP 每请求线程改为 4 MiB 栈(/summary 就跑在这两类线程上;
    巨型 JSON 组装也在其中)。

怎么读下一次的崩溃日志

日志尾巴 含义
S:json → CRASH ... FATAL 崩在 JSON 组装内部(format! 读取某片段时)
S:json_built → CRASH ... FATAL 崩在组装完成→收尾之间
S:obs_done / S:cache_done → CRASH ... FATAL 崩在收尾 tail(本版已把这段纳入恢复窗口,若仍 FATAL 说明连窗口机制都没能拦住)
CRASH ... RECOVERED(且进程活着) 真兜住了(60s 冷却后自愈)
addr=0x0 或极小值 空指针/野指针读;addr 位于线程栈区间

验证

安装后:

  1. /health 应报 version=3.28.5;
  2. 正常玩一场,若曾必崩的场景不再崩 → 窗口/栈修复生效;
  3. 若仍崩:把 Android/media/jp.co.cygames.umamusume/hachimi/uma_predict.log 的最后 50 行给我,新格式会直接给出 地址+线程+真伪判词。

数据端点、嗅探、发收包落盘全部原样保留。

v3.28.4 meta 直查控制台(加密资源清单)+ 纯数据管道(摘 AI)

Choose a tag to compare

@github-actions github-actions released this 20 Sep 00:14

v3.28.4 — meta 直查控制台(读取加密资源清单)+ 纯数据管道

基线沿革:v3.28.2 同款链(v3.28.0 提交 721c086 + cumulative 3.27.23 + SIGSEGV 防护 + 崩溃日志落盘修复)。
在你手机当前验证能玩的 v3.28.2 谱系上,叠加本轮功能与既有加固,不动 main、不动 28.3。

本版变更

  1. 新增:/debug/resource_meta_query(只读 SQL 控制台)
    用游戏自带 libnative.so 的 sqlite3mc + 已捕获的钥匙(files/ura_meta_key.txt,内存捕获 META_KEY_HEX 兜底)直开 164MB 加密资源清单 /data/user/0/<pkg>/files/meta:

    • 不带参数 → 列出全部表/视图名;
    • ?table=NAME&limit=50&offset=M → 读某张表的行;
    • ?sql=SELECT... → 单条只读语句(列名字、列类型全认,BLOB 给大小,文本 300 字封顶)。
      实现要点:只以 SQLITE_OPEN_READONLY 打开(绝不在活文件上建 journal);三种喂钥匙姿势(key v1 / key_v2 空库名 / key_v2 "main")逐一用 SELECT count(*) FROM sqlite_master 验证,哪把真能解密就用哪把(响应里报 key_used);行数与总字节双封顶(默认 50 行、最多 500 行、响应约 900KB 上限),单次查询不可能挤爆 18765 通道。
      入口同时加进 /health 端点表、404 可用表与 ?dl=1 下载白名单。
  2. 摘除内置 AI(纯数据管道):/summary 的 ai 字段固定输出 null(源码层固化,不再依赖运行时);/event/recommend 停用(404)。决策全部归 jueceramen(uma-juece-ramen,对齐 umaai-rs MCTS)。数据端点一个不少;嗅探/发收包落盘(/api/sniff/*、hlpatch-observations 会话存储)经发布链断言原样保留(见下)。

  3. 动态版本号:/health 及各端点版本字段改读 PLUGIN_VERSION(Cargo 版本单源),不再出现「装了新包还报 3.22.91」的旧现象。

  4. 画面映射(截屏 hook)不装:按机主 2026-09-17 决定(卡顿源)暂停;发布链带反向校验(产物里发现 install_screen_mirror_hook 即红灯拒发),代码保留在 scripts/apply_screen_mirror_frame_a.py,需要时一行回植。

保留项:SIGSEGV 防护(hl_ptr_mapped)、崩溃日志落盘修复(Android/media//hachimi/uma_predict.log)、md5log/嗅探钩子、全部 142 个数据端点。
不含:training_anim_skip(闪退头号嫌疑,按 A/B 纪律持续排除)、签名明文观测(与 v3.28.2 保持一致的最小改动)。

与摘 AI 无关的机械证明(发布链断言)

  • grep -c '/api/sniff' ≥ 20;
  • fn install_api_sniff_hooks 在场;
  • hlpatch-observations 观察存储在场。
    任一缺失 → CI 红灯,拒绝发布。摘 AI 只动 /summary 的 ai_json 块与 /event/recommend 路由字面量,与嗅探/发收包是两条独立管线。

用法示例

# 表清单
http://127.0.0.1:18765/debug/resource_meta_query
# 读前 50 行
http://127.0.0.1:18765/debug/resource_meta_query?table=a&limit=50
# 单条查询
http://127.0.0.1:18765/debug/resource_meta_query?sql=SELECT%20name%20FROM%20sqlite_master%20LIMIT%205

前提:ura_sqlcipher_hooks.flag 已在(钥匙已捕获并落盘)。若报 no_key_captured → 带 flag 重启一次游戏。

SHA256 见 SHA256SUMS / BUILD-MANIFEST.txt。

v3.28.2 崩溃日志落盘修复(基线 v3.28.0)

Choose a tag to compare

@github-actions github-actions released this 13 Sep 11:09

基线严格等于 v3.28.0(workflow 里 pin 到发布提交 721c086,完整重放 cumulative 3.27.23 → SIGSEGV guard → 画面映射 A 阶段),之后只多打一个补丁:崩溃日志落盘路径。

根因

插件所有崩溃落盘点写的是:

/data/data/jp.pokemon.pokeuma/files/uma_predict.log

但 jp.pokemon.pokeuma 这个包名在机上根本不存在(真实包名从 /proc/self/cmdline 读到,是 jp.co.cygames.umamusume)。open() 一直失败,于是:

  • crash_signal_handler 的 CRASH at step N sig=11 记录 —— 丢
  • panic hook 的 PANIC: ... 记录 —— 丢
  • 每一条 log_predict_step —— 丢
  • /debug/crashlog 读不到东西,GitHub 自动上传也没内容可传

这就是「育成第五回合闪退、之后一进就闪退」在设备上一个字证据都没留下的原因。

修复

崩溃日志改写到一个应用无需任何权限就能写的目录 —— 也就是 ura_boot.log 已经验证可写的同一个地方:

/sdcard/Android/media/<pkg>/hachimi/uma_predict.log
  • 旧路径保留为兜底,行为不会比现在更差;
  • 信号处理函数用的是启动时预热好的静态缓冲区,处理崩溃时不分配内存(async-signal-safe,避免在 malloc 里崩掉导致死锁);
  • /debug/crashlog 现在合并读取三个落点,哪个有内容看哪个;
  • ura_boot.log 会新增一行 crash_log=<实际路径>,一眼确认补丁生效。

已知现象(不是回归)

本包不含 training_anim_skip(v3.28.1 的头号嫌疑),也不含 version_single_source,所以各端点返回的 version 字段仍会显示历史硬编码值 3.22.91。认这个包请看 ura_boot.log 里的 crash_log= 行,或比对 SHA256SUMS。

用途

这是 A/B 的 B 组:v3.28.0 行为 + 崩溃日志能落盘。

  • 装上后如果育成不再闪退 → 与 v3.28.0 一致,说明问题在 v3.28.1 的 anim_skip;
  • 如果仍然闪退 → 这次会在 Android/media/<pkg>/hachimi/uma_predict.log 留下 CRASH at step N sig=11,直接定位到具体步骤。

v3.28.1 训练动画跳过 + 版本号单源化

Choose a tag to compare

@github-actions github-actions released this 13 Sep 04:05

基线=v3.28.0。新增:①训练动画跳过——AI选定训练后自动跳过育成演出动画(钩训练演出Init、调游戏自带SkipRuntime),/api/training/anim_skip、/on、/off 三端点可查可开关,游戏未初始化完也能用;②版本号单源化——此前用Agora查询会看到旧版本号(21处端点硬编码3.22.91),现在插件上报版本与发布版本一致(3.28.1),加载日志同步动态;③补上签名明文观测端点(signup plaintext)。SHA256见SHA256SUMS。

v3.28.0 训练画面映射 A 阶段

Choose a tag to compare

@github-actions github-actions released this 04 Sep 00:16

基线 v3.27.23 + SIGSEGV guard + 画面映射 A 阶段:hook eglSwapBuffers 抓帧(150ms 限频,BMP 降采样 1/2);GET /api/frame 返回帧+X-Frame-* 头;POST /api/touch 收归一化坐标(记录,B 阶段注入);GET /api/frame_toggle 采集开关;三端点 BOOT_SAFE。umawork 侧 TrainingMirrorPanel 轮询显示+点击上报(Tab 训练映射)。

云盘散件备份 2026-09-06

Choose a tag to compare

@xf8410 xf8410 released this 06 Sep 14:01

主对话云盘清理前的散件备份(2026-09-06)。这些文件版本未见于历史 Release,上传留档。