全部数据来自实机 CE 直读(YGOnline.exe,PID 38424,2026-10-18 23:30~23:40)。
工具:tools/rxjh_ctrl.lua(Cheat Engine Lua 库,415 行)
控件对象在控件池里能按内容地址直接定位,勾选状态是对象内一个 byte,读写都成立:
0x03C35758 + 控件池索引 × 4 → 控件对象指针
控件对象 + 0x360 (byte) → 勾选状态 0/1 ← 可读可写,实测持久不被覆盖
控件对象 + 0x364 (dword) → 控件ID
控件对象 + 0x368 (内联字节串) → 控件名称(BIG5,不是指针)
① 控件池基址 0x03C35758 ← 等于源码常量 #怪物基址值 = 63133528
② 池里找 ID=49 的对象 → 读它的 +0x004 得父窗口
③ 窗口对象 + 0x3000..0x3B00(步长 4)→ 逐个控件指针
④ 校验:控件+0x364 在 1..0x4000,虚表在已知 4 类内
本次实测值:
| 项 | 值 |
|---|---|
| 控件池基址 | 0x03C35758 |
| 内挂窗口对象 | 0x4C102178(虚表 0x010E3050) |
| 窗口内控件 | 99 个(88 控件 + 5 容器 + 5 子窗 + 1 分隔) |
⚠ 上一版报告里写的0x4C0655F8是换算错误 —— 那个地址全 0。
正确的窗口是0x4C102178,+0x3938正好是「啟動內掛丸」(ID49) 的指针。
窗口怎么认:控件池里 475 个不同的父对象,光看「含 ID 49」会误判 6 个。必须三条同时成立:
+0x3000..0x3B00 扫出 > 50 个有效控件| 偏移 | 类型 | 含义 | 实测样本(ID49) |
|---|---|---|---|
+0x000 | dword | 虚表 | 0x010F1EE4 |
+0x004 | ptr | 父窗口 | 0x4C102178 |
+0x008 | dword | 常量 | 0x22(34) |
+0x00C | dword | 池索引 | 4231 |
+0x04C | dword | 组编号 | 0x1086 |
+0x360 | byte | 勾选 0/1 | 0 |
+0x364 | dword | 控件ID | 49 |
+0x368 | bytes | 内联 BIG5 名称 | B1 D2 B0 CA A4 BA B1 BE A4 59(啟動內掛丸) |
四种虚表 = 四种控件类:
| 虚表 | 数量 | 类型 | +0x360 语义 |
|---|---|---|---|
0x010F1EE4 | 88 | 按钮 / 勾选框 / 静态文本 | 勾选位,安全可写 |
0x010FB2E0 | 5 | 容器 / 页签 | 不是勾选(读到 156、0x19B0xxxx 这类垃圾值),禁止写 |
0x010FAE94 | 5 | 子窗口(编辑框等) | 同上,禁止写 |
0x010F1F5C | 1 | 分隔条 | 无效 |
这是踩过的坑:容器类 +0x360 位置存的是别的东西,当成勾选写会破坏对象。工具里已按虚表白名单拦截。
脚本放在 D:/rxjh_out/rxjh_ctrl.lua。CE 里 Ctrl+Alt+L 打开 Lua 引擎:
dofile('D:/rxjh_out/rxjh_ctrl.lua')
ctrl.table() -- 99 个控件全表(偏移/ID/类型/池索引/勾选/名称)
ctrl.checked() -- 只列当前勾着的
ctrl.get(49) -- 单个:返回 table(obj/idx/check/group/woff/name)
ctrl.format(ctrl.get(49)) -- 格式化成一行
ctrl.readRange(11, 35) -- 读 ID11..45 的 35 个勾选位,输出 "0000???..."(? = 该ID不存在)
ctrl.set(49, 1) -- 勾上,返回 true/false, 说明
ctrl.set(49, 0) -- 取消
ctrl.setRange(11, {1,0,1,0,1}) -- 从 ID11 起批量按位图写
ctrl.verify(11, "00000000000000000000000000000000000") -- 对照校验
ctrl.watch(49, 2000) -- 每 2 秒打印一次,值变了才输出
ctrl.stopWatch() -- 结束
ctrl.find(49) -- 内挂窗口内(默认)
ctrl.findAny(49) -- 全池,会捞到别家窗口同号控件(只读用,别拿来写)
ctrl.findWindow() -- 返回窗口地址 + 控件数
| 测试 | 结果 | |
|---|---|---|
| 窗口定位 | 0x4C102178,99 个控件,耗时 0.19s | |
get(49) | ID=49 控件 chk=0 idx=4231 grp=0x1086 woff=0x3938 啟動內掛丸 | |
set(40,1) → 读回 → set(40,0) | 全部成功,0 -> 1 -> 0,进程无异常 | |
| 写入持久性 | 写 1 后连续观察 4.8 秒,值稳定为 1,游戏没有覆盖回去 | |
| 容器类拒绝 | set(1,1) → `false | ID=1 属于 容器 类,+0x360 不是勾选位,已拒绝` |
| 越界拒绝 | set(9999,1) → `false | 找不到控件 ID=9999`(只在窗口内找) |
| 非法值拒绝 | set(49,2) → `false | 只接受 0 或 1` |
当前勾选状态(23:38 快照):7 个
ID=88 chk=1 woff=0x33F8 啟動戀人之魂。
ID=89 chk=1 woff=0x3400 啟動刺客之魂。
ID=90 chk=1 woff=0x33FC 啟動商人之魂。
ID=91 chk=1 woff=0x3404 設定要撿取的道具種類。
ID=113 chk=1 woff=0x3408 設定赤蛇鬥關的使用間隔時
ID=155 chk=1 woff=0x399C 發動東嶺之魂。
ID=156 chk=1 woff=0x39A0 指定要使用的美高異常狀態
readRange(11, 35) 输出:
位置: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ...
值: 0 0 0 0 ? ? ? 0 0 0 0 ? ? ? ? ? ? 0 ...
ID: 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 ...
缺的是 ID 15/16/17 和 22/23/24/25/26/27(共 9 个)。<br>所以 不能 简单把 .aut 里那 35 位开关位图按 位 i ↔ ID(11+i) 直接对上去,中间有空洞。要建立映射只能靠对照实验。
+0x360 是控件自己的勾选状态,写入后持久(4.8 秒未被覆盖),说明它是真值而不是缓存。.aut 文件(tools/rxjh_aut.py,退出游戏后改)。要确认第 2 点,最省事的办法:ctrl.set(某个功能ID, 1),然后看游戏里对应的功能有没有真的开起来 / 面板上勾有没有亮。一次就能定性。
分析报告汇总/05_助手UI控件逆向/控件清单_20261018.tsv(99 行,Tab 分隔)
| 列 | 含义 |
|---|---|
woff | 窗口偏移(重启不变,可当锚点) |
id | 控件ID(发命令用) |
type | 控件/容器/子窗/分隔 |
idx | 池索引(会漂移,不可写死) |
chk | +0x360 当前值 |
group | 组编号 |
name | 控件名称(BIG5 解出) |