Repository navigation
Releases: xf8410/hlpatch
Release list
v3.28.2-recovery.3 崩溃日志落盘修复 + 隔离采集 Hook
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.ymlCI 每次编译 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
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 取证校准版(严格模块标签 + 线程实名 + 跨线程保护)
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
- "native" 标签是合并出来的假货:
libnative.so(游戏库,base 0x7xx…)与libnativewindow.so(系统图形库,base 0x1xxx…)被前缀规则并成一条,+0x1511833d0(≈5.6GB)这种偏移无法换算到函数——标签必须各归各行; thr=init不可全信:线程表无"最新注册优先"判定,pthread_t 回收后老标签会被新线程顶着出来——需带写入序号 + tid→标签的离线映射(写进日志);- 跨线程 longjmp 隐患(提前拆弹):旧逻辑只要全局恢复标志为真就
siglongjmp,哪怕崩溃发生在别的线程上——会跳到别人的栈上执行,属严重 UB。必须"武装线程 == 崩溃线程"才允许回收。
本版修正(全部为取证/安全,零数据路径变更)
- 模块表严格化:
native(libnative.so)/natwin(libnativewindow.so)/nathelp(libnativehelper.so)各自独立成行;同名段只有相距 ≤64MB 才合并;查表改为"最小包含区间优先"(不再被大跨度条目遮蔽)。 - 线程实名制:每槽带写入序号,同 tid 取最新注册标签;注册时把
T:init=<tid>、T:push=<tid>、T:http=<tid>直接写进 predict 日志——崩溃行的tid=可离线对上真身。 - armed-tid 双重门:
hl_arm_recovery()记录武装线程;handler 的 longjmp 与RECOVERED判词都要求"武装线程 == 崩溃线程"(两处门,grep 断言恰好 2)。 - 新字段:
fa=0x…(内核记录的故障地址,与 addr 互相校验)、c=<n>(si_code:1=野地址 MAPERR,2=权限/执行错误 ACCERR)。 - 标记带线程号:
S:enter tid=… / S:exit tid=…——/summary 读写线程身份直接印在轨迹里。 - 模块表容量 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 + 线程标签)
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
三条结论:
- JSON 组装段无罪——
S:json_built已打出,100KB 的 format! 完整跑完; - FATAL = 崩溃发生在"武装窗口"之外(
S:cache_done之后,或发生在未武装的线程上)——所以 SIGSEGV 防护没有生效不是防护失灵,是崩点不在它的射程内; - addr=0x72bf999f80 位于游戏堆段(0x72…)——这是"对象生死/线程时序"层面的问题(指针拿到时对象还活着,读取时已被释放/复用),不是"字段偏移错位"层面的问题(若偏移错位,会读垃圾值或在读取当下就崩,不会全部读完、JSON 组完才崩)。
本版新增(全部只在崩溃/流水日志里追加信息,不改控制流)
- pc= / lr= 模块归因:崩溃日志追加
pc=<模块>+<十六进制偏移> lr=<模块>+<偏移>。
模块表在启动时从/proc/self/maps解析(hlpatch / il2cpp / native / libc / main / 其他),信号处理器只读静态表、零分配。
→ 下一份日志直接指认"故障指令在哪条 SO、偏移多少"。 - 线程标签:push / http / init 三线程自注册(环形表,最近 16 个永不丢);崩溃行追加
thr=push | http | init | ?。 - 细窗口标记(7 个):
S:enter、S:exit、H:ret、H:saved、P:read_done、P:push_start、P:push_done。
把"S:cache_done 之后到下个请求"这段黑盒切成单语句级网格。 - 崩溃消息缓冲 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 崩溃取证升级 + 恢复窗口修复
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)
RECOVERED是假话:崩溃处理器在检查恢复标志之前就把" RECOVERED"
无条件拼进日志。两次崩溃后计数归[1](新进程)→ longjmp 兜底那条路
并没有救回进程(或崩点落在了无保护窗口)。- 恢复窗口有洞:
read_summary在catch_unwind返回后立刻清掉
SIGSEGV_RECOVERY,然后才跑observe_ramen_transition/ 写缓存 / 返回。
尾巴这一段裸奔——在那里崩,handler 只能 re-raise 杀进程。
日志"S:json之后没下一条"恰好落在这个窗口(S:json → 下一轮S:wdm之间)。
本版修正
- 崩溃日志说真话
- 改用
sigaction(SA_SIGINFO | SA_ONSTACK)+sigaltstack; - 日志格式升级为:
CRASH at step N sig=11 addr=0x<故障地址> tid=<故障线程> RECOVERED|FATAL - 只有 longjmp 真正兜住时才写
RECOVERED;进程将死时写FATAL; - 全部手工拼接、零分配(沿用裸 syscall 纪律)。
- 改用
- 收尾切 3 个细诊断点:
S:json_built(JSON 组装完成)、S:obs_done、
S:cache_done。下一次崩,日志尾巴直接告诉你崩在"组装内 / 尾巴 / 下一轮前导"。 - 补恢复窗口:
SIGSEGV_RECOVERY只在read_summary最末端清除,
覆盖 observe / cache / return 全尾段。 - 深栈: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 位于线程栈区间 |
验证
安装后:
/health应报version=3.28.5;- 正常玩一场,若曾必崩的场景不再崩 → 窗口/栈修复生效;
- 若仍崩:把
Android/media/jp.co.cygames.umamusume/hachimi/uma_predict.log的最后 50 行给我,新格式会直接给出 地址+线程+真伪判词。
数据端点、嗅探、发收包落盘全部原样保留。
v3.28.4 meta 直查控制台(加密资源清单)+ 纯数据管道(摘 AI)
v3.28.4 — meta 直查控制台(读取加密资源清单)+ 纯数据管道
基线沿革:v3.28.2 同款链(v3.28.0 提交 721c086 + cumulative 3.27.23 + SIGSEGV 防护 + 崩溃日志落盘修复)。
在你手机当前验证能玩的 v3.28.2 谱系上,叠加本轮功能与既有加固,不动 main、不动 28.3。
本版变更
-
新增:
/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下载白名单。
-
摘除内置 AI(纯数据管道):
/summary的ai字段固定输出null(源码层固化,不再依赖运行时);/event/recommend停用(404)。决策全部归 jueceramen(uma-juece-ramen,对齐 umaai-rs MCTS)。数据端点一个不少;嗅探/发收包落盘(/api/sniff/*、hlpatch-observations会话存储)经发布链断言原样保留(见下)。 -
动态版本号:/health 及各端点版本字段改读
PLUGIN_VERSION(Cargo 版本单源),不再出现「装了新包还报 3.22.91」的旧现象。 -
画面映射(截屏 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)
基线严格等于 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 训练动画跳过 + 版本号单源化
基线=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 阶段
基线 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
主对话云盘清理前的散件备份(2026-09-06)。这些文件版本未见于历史 Release,上传留档。