热血江湖 YGOnline.exe —— 偏移会变化吗?稳定性总纲 + 本次版本更新影响实测 时间:2026-09-28 14:50 目标:D:\rxjh2025\client\YGOnline.exe (15072256 字节 · 文件时间 2026-09-17 14:48 · 无 ASLR · 模块基址恒 0x400000) 实测环境:双客户端同版本对照 pid 14256 / pid 16164 ================================================================================ 【一句话结论】 会变。但要分清是哪一类 —— 三类地址的稳定性完全不同,混淆它们就会写出"今天能用、 明天全废"的代码。本次实测:助手体系的对象内部偏移 12/12 原封不动,而 IFManager 的窗口槽偏移整体后移、所有绝对地址(全局变量/虚表/函数入口)全部失效。 ================================================================================ 一、必须先分清的三类"偏移" ================================================================================ ① 绝对地址 —— 全局变量、虚表、函数入口 例:IFManager 全局变量、助手命令分派函数、InterAutoPlay 虚表 稳定性:★ 每次 EXE 更新必变 —— 本次已实测作废 原因:链接期决定。源码里增删任意一个函数/变量,其后所有地址整体平移。 ② 结构体内部偏移 —— 对象内部的字段位置 例:助手对象 +0x3938(开始挂机槽)、控件 +0x364(功能号)、窗口对象 +0x40(可见) 稳定性:★★ 取决于源码有没有动那个结构体。小改动不动,加了成员就后移 本次实测:助手对象内部 12/12 全对;IFManager 窗口注册表整体 +0x1C(变了) ③ 运行时对象地址 —— new 出来的对象本身 例:助手窗口对象、交易窗口对象、按钮控件对象 稳定性:★★★ 每次开客户端都变(本来就是),只能链式读,绝不能写死 另外还有一个独立体系,别和三兄弟混: ④ 游戏窗口的"控件 ID"(控件 +0x0C) 会话内递增分配,每次开客户端整体平移(实测 6698 → 6701)。 而"助手的命令号"(+0x364)是编译期常量(0x31=开始挂机…),永不随会话变。 两者都叫"ID",性质相反 —— 这是最容易踩的坑。 ================================================================================ 二、本次实测铁证:新老地址逐条对照 ================================================================================ 2.1 绝对地址 —— 全部作废(会变,且已经变了) | 项目 | 旧值(9/18 记录,活体已证) | 现值(9/28 实测) | 结果 | |---------------------|----------------------------|------------------------------|----------| | IFManager 全局变量 | 0x0183C52C → 有效 | [0x0183C52C] = 0 | 已作废 | | (同上,新地址) | — | [0x0188C61C] = 42C3C1E0 | 新锚点 | | 助手虚表 | 0x010A20F8 | 0x010E3050 (.?AVInterAutoPlay@@) | 平移 | | 助手虚表(另一记录)| 0x01079A6C | 读不出 RTTI | 已作废 | | IFManager 虚表 | 0x010A05EC | 0x010E196C (.?AVIFManager@@) | 平移 | | 助手命令分派 slot1 | 0x00A3F3D0 | 0x00A82550 | 平移 0x43180 | 旧 slot1 地址 0x00A3F3D0 现在的字节是 FF 8B 8D E8 FE FF FF 8D —— 已经不是函数开头; 新 slot1 0x00A82550 的字节是 55 8B EC 83 EC 20 56 89 —— 与旧记录里 0xA3F3D0 的序言 (push ebp / mov ebp,esp / sub esp,0x20 / push esi / mov [ebp-4],ecx)逐字节同款。 → 函数体没变,地址平移了。这是"绝对地址会变"的最干净证据。 (.text 与 .data 是两个独立段,平移量不同: 代码段 +0x43180,数据段 +0x500F0,所以不能用"统一加一个数"去修,只能重新定位。) 2.2 结构体内部偏移 2.2.1 没变的 —— 助手对象内部(12/12 全部命中) | 助手对象 + 偏移 | 控件对象 | 读 +0x364 | 期望 | 结果 | |-----------------|-----------|-----------|------|------| | +0x3938 开始挂机 | 4D27CCB0 | 0x31 | 0x31 | 一致 | | +0x393C 停止挂机 | 4D284660 | 0x32 | 0x32 | 一致 | | +0x37F8 设置 | 4D27D238 | 0x33 | 0x33 | 一致 | | +0x37FC 关闭 | 4D284BE8 | 0x34 | 0x34 | 一致 | | +0x3940 保存 | 4D915E68 | 0x35 | 0x35 | 一致 | | +0x3800 取消 | 4D91D818 | 0x36 | 0x36 | 一致 | | +0x39E8 弹窗确定 | 4D907090 | 0xA3 | 0xA3 | 一致 | | +0x39EC 弹窗取消 | 4D902320 | 0xA4 | 0xA4 | 一致 | | +0x36F4 对话1 | 4D91E8B0 | 0x46 | 0x46 | 一致 | | +0x38C4 攻击模式 | 4D291890 | 0x0B | 0x0B | 一致 | | +0x38F0 辅助1 | 4D296600 | 0x1C | 0x1C | 一致 | | +0x36C8 容器 | 47DACD00 | 0x01 | 0x01 | 一致 | 另外:助手对象自身 +0x0C(创建序号)= 0x109B,而 9/19 的记录写"0x109A 起" —— 也对得上。 → 结论:**助手对象内部的控件槽偏移和功能号,跨版本稳定,可以放心写死**。 2.2.2 变了的 —— IFManager 的窗口注册表整体后移 0x1C | 窗口 | 旧槽(9/18) | 新槽(9/28) | 差值 | |------------------|--------------|--------------|-------| | InterMAIN | +0x390 | +0x3AC | +0x1C | | InterChat | +0x394 | +0x3B0 | +0x1C | | InterStatus | +0x398 | +0x3B4 | +0x1C | | InterInventory | +0x39C | +0x3B8 | +0x1C | | InterNpc | +0x3EC | +0x408 | +0x1C | | **InterAutoPlay**| **+0x414** | **+0x430** | +0x1C | | InterCouple | +0x444 | +0x45C | +0x18 | 6 项精确 +0x1C、1 项 +0x18 —— 这不是巧合,是 **IFManager 结构体在 0x390 之前 新增了 7 个成员(28 字节)**,导致后面所有已知窗口槽整体后移。 → 结论:**连"窗口注册表槽偏移"这类结构体偏移也会变**(只要源码往那个结构体里加东西)。 顺带说明:`+0x414` 现在指向 `.?AVInterTimer@@`(计时器),**不是助手**。 拿旧偏移去读助手,会读到一个活的、但完全不相干的对象 —— 不报错、不崩溃, 只是所有数据都是垃圾。这种"静默错位"最危险。 2.3 运行时对象地址 —— 每次都变(同版本双进程对照) | 项目 | pid 14256 | pid 16164 | |----------------|-------------|-------------| | IFManager 对象 | 42C3C1E0 | 428750A8 | | InterMAIN 对象 | 42D2E3B0 | 4268D9C0 | | 助手对象 | 326AAA78 | 4DEC7320 | | 助手槽偏移 | +0x430 | +0x430 | ← 结构体偏移:两进程一致 | InterMAIN 槽 | +0x3AC | +0x3AC | ← 同上 → **同版本内跨进程:结构体偏移稳定,对象地址全变。** 这就是"链式读取"必须存在的原因。 ================================================================================ 三、★ 直接影响你现有代码的两处(请立刻核) ================================================================================ 3.1 助手入口链已断 你工程里(E2TXT `遍历助手控件` / `小助手_点控件` 等)用的是: 助手窗口 = 内存_读整数型 (句柄, 内存_读整数型 (句柄, #NPC基址值) + 十六到十 ("414")) 现在实测:[0x0183C52C](= #NPC基址值 = 25412908)= **0** → 读 0 + 0x414 = 0x414 → 整条链断掉,助手功能会全废(遍历 0 个控件 / 点控件无反应)。 **新的正确写法(本次实测有效,双进程一致):** 助手窗口 = 内存_读整数型 (句柄, 内存_读整数型 (句柄, 25740828) + 1072) ' 25740828 = 0x0188C61C ← IFManager 全局变量(新锚点) ' 1072 = 0x430 ← 助手槽(新偏移) 而助手对象**内部**的偏移(0x3938 等)和功能号(0x31 等)**不用改**。 3.2 工程里那批"基址常量"本次读数全 0 对 E2TXT 常量表(9/18 生成)逐条实测: | 常量名 | 地址 | 现值 | 判定 | |------------------|--------------|----------|-------------| | 坐标/人物基址值 | 0x03BF28CC | 0 | 读不出数据 | | 怪物基址值 | 0x03BE3E68 | 0 | 读不出数据 | | 背包基址值 | 0x03B9DCE0 | 0 | 读不出数据 | | 当前血量值 | 0x03970A70 | 0 | 读不出数据 | | NPC基址值 | 0x0183C52C | 0 | 读不出数据 | | 仓库基址值 | 0x0182A2DC | 0 | 读不出数据 | | 发包Call_ECX值 | 0x017EF378 | 0 | 读不出数据 | | 地图基址值 | 0x011E0084 | 0x61054F | 有效 | | IFManager | 0x0188C61C | 有效指针 | 有效 ✓ | 内存区域活跃度(每 4 字节一个 dword,统计非 0): 0x03970000 血量区 : 0 / 16384 ← 整片全 0 0x03B90000 背包区 : 0 / 16384 ← 整片全 0 0x03BE0000 怪物区 : 190 / 16384 0x03BF0000 人物区 : 2961 / 16384 ← 有数据 0x010E0000 新虚表区: 985 / 1024 ← 代码区,正常 两个客户端(14256 / 16164)读数完全相同,不是进程选错。 ⚠ 这说明:这批 9/18 时代的绝对地址,在**当前这个 EXE**(2026-09-17 那个 15MB 版本) 上已经不再承载数据。若你的挂机脚本还在用它们,请**先跑一次体检**再判断。 (本清单只是客观读数;不排除你的工程里已有另一套"特征码自动更新基址"在生效。) ★ 排除"进程没进游戏"这个干扰项 —— 同一内存区域是活的: 静态名字表 0x03BF3848 起 6 个 slot(stride 0x50)全部读到 BIG5 角色名, slot0 与 slot6(0x03BF3A28) 均为 A8 53 A6 B3 A8 6B AA 42 A4 CD =「沒有男朋友」(逐字节 BIG5 核对,first dword = 0xB3A653A8),即本机角色名。 → 0x03BFXXXX 这片已提交且在用、角色已登录,所以 0x03BF28CC 读 0 不是"没进游戏",而是**这个全局变量地址在本版不再承载人物指针**。 顺带修正一条旧假设:把旧地址 +0x539A8 当作"本版平移量"不成立 (0x03C46274 / 0x03C471F0 / 0x03C473D0 三处探针全 0)。 ================================================================================ 四、怎么写"抗更新"的代码(三条铁律) ================================================================================ 铁律 1 —— 绝对地址一律不写死,用"锚点 + 特征"重新定位 · 能写死的只有 ② 类(结构体内部偏移)。 · ① 类(全局变量/虚表/函数)每次更新都要重新拿。 · 最低成本的更新方式:拿旧地址在 CE 里"找什么访问了它"→ 反查新地址; 或对模块做一次 AOB 特征扫描(你工程里已有 `进程_特征码搜索` 的用法)。 铁律 2 —— 拿到对象后先做"体检",别拿到就用 · 助手对象:读 [对象+0] 虚表 → 读 [虚表-4] → 读 [+0x0C] → 读 [+8] 得类名, 应当是 `.?AVInterAutoPlay@@`。 · 退一步的轻量判据:读 [对象+0x3938] 得按钮 → 读 [按钮+0x364], 应等于 0x31。**这条同时验证了"槽偏移对不对"和"对象是不是助手"**。 · 值域兜底:对象指针必须 > 0x10000;虚表必须落在 .text 段(0x400000~0x1200000)。 · 你工程里 `助手_取槽控件` 已有虚表区间校验,思路是对的,继续保持。 铁律 3 —— 槽偏移变了不要死改,改用"按类名找槽" 不要去记 +0x430 这个数,而是遍历 IFManager 的槽区(+0x300 ~ +0x600), 对每个非空槽读它的 RTTI 类名,命中 `InterAutoPlay` 即得。 这样无论源码往 IFManager 里插多少新窗口,你都找得到。 ================================================================================ 五、自检子程序(可直接粘贴) ================================================================================ .版本 2 .子程序 助手链路体检, 逻辑型, , 返回真=助手可用;顺带把每一步读数打进日志 .局部变量 接口管理, 整数型 .局部变量 助手窗口, 整数型 .局部变量 虚表, 整数型 .局部变量 开始按钮, 整数型 .局部变量 功能号, 整数型 接口管理 = 内存_读整数型 (全局_进程句柄, #IFManager基址) .如果真 (接口管理 < 65536) 挂机信息.加入文本 (自定义时间 () + “【体检】IFManager 读不到=” + 到文本 (接口管理) + #换行符) 返回 (假) .如果真结束 助手窗口 = 内存_读整数型 (全局_进程句柄, 接口管理 + #助手窗口槽) .如果真 (助手窗口 < 65536) 挂机信息.加入文本 (自定义时间 () + “【体检】助手窗口读不到=” + 到文本 (助手窗口) + “ 槽偏移可能变了” + #换行符) 返回 (假) .如果真结束 虚表 = 内存_读整数型 (全局_进程句柄, 助手窗口) .如果真 (虚表 < 4194304 或 虚表 > 18874368) 挂机信息.加入文本 (自定义时间 () + “【体检】助手虚表越界=” + 到文本 (虚表) + “ 对象可能不是助手” + #换行符) 返回 (假) .如果真结束 开始按钮 = 内存_读整数型 (全局_进程句柄, 助手窗口 + 14648) .如果真 (开始按钮 < 65536) 挂机信息.加入文本 (自定义时间 () + “【体检】开始按钮槽空=” + 到文本 (开始按钮) + #换行符) 返回 (假) .如果真结束 功能号 = 内存_读整数型 (全局_进程句柄, 开始按钮 + 868) 挂机信息.加入文本 (自定义时间 () + “【体检】助手 OK:窗口=” + 到文本 (助手窗口) + “ 虚表=” + 到文本 (虚表) + “ 开始按钮功能号=” + 到文本 (功能号) + #换行符) 返回 (功能号 = 49) 对应常量(十进制): #IFManager基址 = 25740828 ' 0x0188C61C ★ 本次更新后的新锚点 #助手窗口槽 = 1072 ' 0x430 ★ 本次更新后已从 0x414 后移 虚表合法区间 = 4194304 ~ 18874368 ' 0x400000 ~ 0x1200000(.text) 助手对象 + 14648 = 0x3938 ← 开始挂机槽(未变) 按钮 + 868 = 0x364 ← 功能号(未变,0x31 = 49) ================================================================================ 六、通用结论备忘 ================================================================================ · 写死"结构体偏移"是安全的 —— 前提是拿到对象后能验证对象身份。 · 写死"绝对地址"是不安全的 —— 每次更新必废,且失效时往往静默(读到 0 或别的对象)。 · 最抗更新的组合 = 特征码/AOB 定位锚点(①) + 写死结构体偏移(②) + RTTI 类名自检。 · 判断"偏移变没变"的标准动作:拿一个**同类已知值**去校验。 例:助手 → 检查开始按钮功能号是否仍 = 0x31; 交易窗 → 检查 +0x334 面板是否仍 220x300; 自己名字 → 检查静态表 slot6 是否仍是本机角色名。 校验通过 = 偏移没变;校验失败 = 别猜,重新定位。 · 本次更新(EXE 文件时间 2026-09-17 14:48,15072256 字节)造成的实际影响: - 全部绝对地址移位(代码段 +0x43180 / 数据段 +0x500F0) - IFManager 窗口注册表槽偏移 +0x1C - 助手对象内部偏移、功能号:未变 - 你工程里 0x0183C52C 这条助手入口链:断 ================================================================================ 附录:本次实测脚本 D:\ce_mcp\_t21.py 助手窗口定位 + 功能号对比(首次发现 +0x414 不是助手) D:\ce_mcp\_t22.py IFManager 全槽 RTTI 定名(找出 +0x430 = InterAutoPlay) D:\ce_mcp\_t23.py 助手对象内部 12 个控件偏移逐条校验(12/12 一致) D:\ce_mcp\_t24.py 旧虚表/旧函数地址现状探测(全部失效)+ 新旧分段平移量 D:\ce_mcp\_t25.py 工程 9 个基址常量体检 D:\ce_mcp\_t26.py 内存区域活跃度扫描 D:\ce_mcp\_t27.py 双进程(14256 / 16164)对照 D:\ce_mcp\_t28.py 内存区域状态查询(API 不可用) ================================================================================