日期:2026-10-18 23:2x<br>样本:D:\rxjh2025\client\*.aut 共 110 个(全部 11812 字节)<br>目标程序:YGOnline.exe 15,072,256 B / i386 / ImageBase 0x400000
"开启的功能"不是存在 ini / 文本 / 注册表里,而是存进一个 11812 字节的定长内存结构体,<br>退出面板或改设置时整个结构体原样 dump 到 client\<账号名>.aut。
保存动作本身走的是 C 运行库标准 I/O:
fopen("<exe目录>\client\<账号名>.aut", "r+b") // 或 "wb"
fwrite(&g_AutoConfig, 1, 11812, fp) // 整块,不是逐项
fclose(fp) // 内部 fflush -> SetEndOfFile 截断到 11812
底下落到 Win32:
CreateFileA (GENERIC_READ|GENERIC_WRITE, OPEN_ALWAYS)
WriteFile (整块 11812 字节)
SetEndOfFile (把文件精确截到 11812)
CloseHandle
| # | 证据 | 出处 |
|---|---|---|
| 1 | .rdata 里明文存在 fopen 模式串 r+b | VA 0x00CE2A58 |
| 2 | .rdata 里明文存在路径格式串 %s\client\%s.aut | VA 0x00CE3918 |
| 3 | 代入后正好得到 D:\rxjh2025\client\<账号>.aut,与实际落盘路径完全一致 | 实证 |
| 4 | exe 导入 CreateFileA 36 处调用 / WriteFile 24 处 / SetFilePointer 20 处 / SetEndOfFile 全程序仅 1 处 | IAT 扫描 |
| 5 | 那唯一一处 SetEndOfFile 位于 0x00C4823F,上下文是 CRT 的 _flush 实现(0x1000 分块写、errno 0xD/0x16),证明是 fflush 在收尾截断 | 反汇编 |
| 6 | 110 个 .aut 文件大小完全相同(11812),加上 425 个 .scs/.mhslot 也都是定长(403 / 292) | 全量 stat |
| 7 | CRT 静态链接,所以导入表里看不到 fopen/fwrite,只能看到它底下的 kernel32 | 导入表 |
第 6 条是最硬的:任何文本配置都不可能在 110 个不同账号下保持同一字节长度。
唯一解释就是「sizeof(struct)固定 + 整块 WriteFile」。
┌──────────────────────────────────────────────┐
│ 0x0000 4 B 文件头 = 6A 00 00 00 (魔数 0x6A) │
├──────────────────────────────────────────────┤
│ 0x0004 2952 B 配置槽 0(预设 1) │
│ 0x0B8C 2952 B 配置槽 1(预设 2) │
│ 0x1714 2952 B 配置槽 2(预设 3) │
│ 0x229C 2952 B 配置槽 3(预设 4) │
└──────────────────────────────────────────────┘
合计 = 4 + 2952 × 4 = 11812 ✔
| 槽内偏移 | 长度 | 内容 |
|---|---|---|
+0x000 | 2 | 固定 01 01 |
+0x006 | 4 | 6C 42 01 01 |
+0x00C ~ +0x016 | 11 | 2 个 float + 1 字节(≈0.66 / 0.24) |
+0x024 ~ +0x067 | 9×8 | 9 项数组(float + 4B 尾) |
+0x074 ~ +0x096 | 35 | ★ 功能开关位图:每字节 1 个开关,0=关 / 1=开 |
+0x098 | 1 | 数量/档位(实测取值 0 / 3 / 5 / 50) |
+0x100 ~ +0x45C | ~12×78 | 78(0x4E) 字节定长项的列表 |
+0x4A8 ~ +0x550 | — | 第二组设置(+0x4B0=A、+0x4D0=A、+0x4D4/4D8=0/1、+0x4DC/4E4 float) |
+0x56C ~ +0x5A6 | 29×2 | ★ 29 项字节参数表(步长 2) |
+0x654 ~ +0x84C | ~6×78 | 又一个 78 字节列表 |
+0xB6C ~ +0xB84 | — | 尾部零散字段 |
110 个样本按「活动槽」统计 +0x074 ~ +0x096:
+0x075 → 57 关 / 53 开+0x077 → 49 关 / 61 开+0x092 → 29 关 / 81 开+0x083 → 108 关 / 2 开+0x070~0x073、+0x085~0x091、+0x097 及之后恒为 0 → 保留位两个样本实测:
chang1.aut 开关: 0 0 1 1 0 1 1 1 1 1 1 0 0 1 0 0 1 0 ... 1 1 1 0 0
已开启: [2,3,5,6,7,8,9,10,13,16,30,31,32] 档位=3
参数: 15 99 25 2 99 99 99 99 99 29 2 99 99 99 30 2 5 99 40 ...
baoma001.aut 开关: 0 1 1 1 0 1 1 1 0 0 1 1 0 0 0 0 0 0 ... 1 1 0 0 0
已开启: [1,2,3,5,6,7,10,11,30,31] 档位=0
参数: 全 0
「参数表全 0 但开关照样有 10 个开着」—— 只有开关位图和参数表分开存储才会出现这种组合。
D:/AI空间/rxjh/tools/_aut_cfg.py
from _aut_cfg import AutCfg
c = AutCfg(r"D:/rxjh2025/client/chang1.aut")
print(c.active_slot) # 当前生效槽(0..3)
print(c.switch_on()) # 已开启的开关序号列表
print(c.params()) # 29 项参数
c.set_switch(3, 1) # 打开 3 号开关
c.set_param(0, 20) # 参数表第 0 项 = 20
c.set_count(5) # 档位 = 5
c.save() # 原地写回,自动生成 .bak
命令行直接看:
python D:/AI空间/rxjh/tools/_aut_cfg.py D:/rxjh2025/client/chang1.aut
注意:游戏运行中改文件会被它内存里的旧结构体覆盖回去。要改必须先退游戏,<br>或者在游戏内改完再退(退出时会再写一次)。
35 个位里目前能确定"是开关",但哪一位对应面板上哪个勾选框,只能靠一次对照实验。
已架好零风险监控(不注入、不 hook、不改游戏代码):
D:/AI空间/rxjh/tools/_watch_save.py (正在后台跑)
日志:D:/AI空间/rxjh/_save_snap/_watch.log
快照:D:/AI空间/rxjh/_save_snap/
它每 0.25 秒比对 client\ 下所有 .aut/.mhslot/.scs 的大小与 mtime,<br>一有写入立刻存快照并算出「改了哪几个字节、字节区间在哪」。
标定步骤:
4 + 2952×活动槽 就是开关序号做 3~5 次就能把最常用的几个对上号。剩下的按位序推断。
| 扩展名 | 大小 | 性质 |
|---|---|---|
.mhslot | 292 B = 4 头 + 12 × 24 | 快捷键/宏槽,12 个 24 字节项 |
.scs | 403 B | 前 16 字节是 BIG5 角色名 + 其余定长字段 |
两者与 .aut 同一套机制:定长结构体 + 整块 WriteFile。<br>命名规则也印在 .rdata 里:%s~%d.scs / %s%d.scs / %s-%d.scs 三种槽位形式。