目标文件:D:\rxjh2025\client\YGOnline.exe ImageBase = 0x00400000(所有地址均为 VA / 内存虚拟地址)。依据你提供的易语言子程序(特征码 A、特征码 B、call 0xA3F700)在主程序中静态定位。
| 名称 | 内存地址 (VA) | 含义 / 确认情况 |
|---|---|---|
| 特征码ANPC对话基址 | 0x00668955 | 命中 1 处(主程序 .text,非助手模块)。紧邻前一条指令 mov ecx,[0x183c52c] 揭示了 NPC 管理器全局 = 0x183c52c |
| NPC 管理器全局 | 0x183c52c | 特征码 A 前一条立即解出的全局指针,保存 NPC 对话对象容器 |
| 特征码B小助手call基址 | 0x00a3f676 | 命中 1 处,位于包裹函数 sub_a3f3d0 内部 |
| 包裹函数 prologue | 0x00a3f3d0 | 含特征码 B 的"助手命令路由"函数起点(ret 0xc,止于 0xa3f6f5) |
| 包裹函数内 call 0xA3F700 | 0x00a3f6e6 | E8 15 00 00 00 → 目标 0xA3F700。即游戏本体启用助手的官方调用点(特征码 B +0x70) |
| 助手中央分发器 | 0x00A3F700 | 你硬编码的 call 目标。已确认正确。ecx=this,[ebp+8]=命令字 |
| 命令字 0x31 处理函数 | 0x00a406b4 | 跳转表索引 0x0f 指向的"小助手开"实现体。可直接 CALL 它(ecx=对话对象)省去分发 |
| 分发跳转表(函数地址) | 0x00a41588 | 256×4 字节,命令 handler 表 |
| 分发索引表(字节) | 0x00a41698 | 256 字节,命令字-0xb 作下标取 handler 索引 |
你代码里 ECX = [NPC_base + 0x414] 取到的"对话对象",正是传给 0xA3F700 的 this。游戏在 0xA3F700 一开头就读 [this+0x34c9](0xa3f75c)作开关校验——对话对象打开时该字节非零,校验即通过,所以必须传对话对象而非随便一个指针。NPC 管理器全局实测为 0x183c52c。
对应关系:特征码 B 在 0xa3f676,其内部 call 0xa3f700 在 0xa3f6e6(偏移 +0x70),call 的目标正是 0xA3F700(偏移 +0x8A)。你用特征码 B 定位助手 call、再硬编码 0xA3F700 的写法完全成立。差异仅在于:包裹函数用"助手管理器(this+0x34b4)"作为 ecx,而你用"对话对象"作为 ecx——两者都能过 0xA3F700 入口的 this+0x34c9 校验。
命令字范围约 0x0b–0xa4。实测部分命令→handler(节选):
| 命令字 | 索引 | handler | 说明 |
|---|---|---|---|
| 0x28 | 0x08 | 0xa3fac0 | — |
| 0x29 | 0x09 | 0xa3faf2 | — |
| 0x2a | 0x0a | 0xa3fb24 | — |
| 0x2b | 0x0b | 0xa3fb56 | — |
| 0x2c | 0x0c | 0xa3fb88 | — |
| 0x2d | 0x0d | 0xa3fbec | — |
| 0x2f | 0x0e | 0xa3fc1e | — |
| 0x31 | 0x0f | 0xa406b4 | ★ 小助手开(启用) |
| 0x32 | 0x10 | 0xa40ca9 | — |
| 0x33 | 0x11 | 0xa40d4e | — |
| 0x34 | 0x12 | 0xa40da5 | — |
| 0x35 | 0x13 | 0xa40d28 | — |
| 0x37 | 0x15 | 0xa3fc50 | — |
| 0x39 | 0x16 | 0xa3fc82 | — |
| 0x3b | 0x17 | 0xa40424 | — |
| 你的代码 | 定位结论 |
|---|---|
| ECX = 读整数([#NPC基址值] + 0x414) | NPC 管理器全局实测 0x183c52c;+0x414 得到对话对象,作为 0xA3F700 的 this。正确 |
| 特征码 A(NPC对话基址) | 命中 0x00668955,链路 [mgr+0x3ec]→[+0x390]→[+0x6c8] 与你的 +0x414 取对话对象是同一对象的两种访问方式。正确 |
| 特征码 B(小助手call基址) | 命中 0xa3f676,其所在函数 0xa3f3d0 在 0xa3f6e6 处 call 0xA3F700。正确 |
| mov eax,0xA3F700 ; call eax | 0xA3F700 = 助手中央分发器,命令字取自 [ebp+8]=0x31。正确 |
| push 0 ; push 0x31 | 0x31 = 启用助手命令 → handler 0xa406b4。正确 |
| mov ecx,[ebp-4](对话对象给 ecx) | 满足 0xA3F700 入口 this+0x34c9 校验(对话打开时非零)。关键前提:调用时必须有 NPC 对话窗口打开,否则 0x34c9 为 0 会走 0xcb55c0 分支 |
CALL 0xa406b4(ecx=对话对象,无需 push 0 / push 0x31),更短更稳。但注意 0xa406b4 是 jmp 进来的 handler(自带 ret),直接 call 后需确认其栈平衡(stdcall,无额外参数,ret 即可)。E8 的 rel32 反推 call 目标,这样即使 exe 重打包导致 0xA3F700 漂移也能自适应。