热血江湖 YGOnline.exe —— 「窗口上的按钮,取法是固定的吗?」 2026-09-28 16:4x 目标:D:\rxjh2025\client\YGOnline.exe(32 位 / 无 ASLR / 模块基址恒 0x400000) 本次实测:同一份 EXE 两个进程对照 PID 2436 与 PID 16060(CE 桥活体扫描) ================================================================================ 【一句话回答】 偏移分三层,别混: ① 同一份 EXE 内(今天这个版本)—— **固定**。两个进程逐条比对,槽偏移完全一致。 ② 跨窗口之间 —— **不通用**。交易窗和组队窗是两个不同的 C++ 类,各有一套自己的偏移。 ③ 跨版本更新 —— **可能变**。类里多声明一个成员,后面全部平移。 而**控件ID 每次都变**(不是固定的),必须从按钮对象 +0x0C 现读。 一、两进程逐条对照(同一 EXE,决定性证据) ================================================================================ 交易窗 InterExchange(槽 1236 = 0x4D4) -------------------------------------------------------------------------------- 按钮槽偏移 PID 2436 PID 16060 结论 +0x338 id 6804 210,60 17x17 id 6801 210,60 17x17 偏移同 / ID 不同 +0x358 id 6711 213,436 18x34 id 6708 213,436 18x34 偏移同 / ID 不同 +0x35C id 6712 258,26 12x11 id 6709 258,26 12x11 偏移同 / ID 不同 +0x37C id 6701 119,178 18x60(中) id 6698 119,178 18x60(中) 偏移同 / ID 不同 +0x38C id 6703 85,178 18x60(左) id 6700 85,178 18x60(左) 偏移同 / ID 不同 +0x394 id 6704 156,178 18x60(右) id 6701 156,178 18x60(右) 偏移同 / ID 不同 +0x3A8 id 6808 129,320 30x100 id 6805 129,320 30x100 偏移同 / ID 不同 +0x3AC id 6809 265,320 30x100 id 6806 265,320 30x100 偏移同 / ID 不同 → 8 个按钮,偏移与坐标两进程**逐字节一致**,只有 ID 和 en/act 标志不同。 组队窗 InterParty(槽 1256 = 0x4E8) -------------------------------------------------------------------------------- 按钮槽偏移 PID 2436 PID 16060 结论 +0x3A8 id 7035 119,178 18x60(中) id 7032 119,178 18x60(中) 偏移同 / ID 不同 +0x3B8 id 7037 85,178 18x60(左) id 7034 85,178 18x60(左) 偏移同 / ID 不同 +0x3C0 id 7038 156,178 18x60(右) id 7035 156,178 18x60(右) 偏移同 / ID 不同 +0x400 id 7313 0,0 15x70 id 7310 0,0 15x70 偏移同 / ID 不同 +0x404 id 7314 0,28 15x70 id 7311 0,28 15x70 偏移同 / ID 不同 → 5 个按钮,同样逐条一致。 变的那部分(每进程都不同,绝不能写死) -------------------------------------------------------------------------------- IFManager 对象 2436 → 1092302624 16060 → 1012722608 窗口对象 2436 → 1057323040 16060 → 1170841976 控件 ID 2436 → 6703/6704 16060 → 6700/6701 en / act 标志 2436 → 左 en=0 act=1 16060 → 左 en=1 act=1 (随窗口状态变) 二、为什么两个窗口的偏移差那么多(差 0x2C ≈ 11 个成员) ================================================================================ 因为这些偏移**不是"按钮表",而是"窗口对象内部那排成员指针里的第几个槽"**。 槽从 +0x330 开始,每 4 字节一格,格子里是什么由**这个 C++ 类自己的声明顺序**决定: InterExchange 类(交易窗)→ 按钮成员落在 +0x338 / +0x358 / +0x35C / +0x37C / +0x38C / +0x394 / +0x3A8 / +0x3AC InterParty 类(组队窗)→ 按钮成员落在 +0x3A8 / +0x3B8 / +0x3C0 / +0x400 / +0x404 两个类的成员个数、顺序都不一样 → 偏移自然对不上。**和按钮在界面上的位置无关**, 纯粹是"编译这个类的时候,它按顺序排到了第几格"。 ★ 铁证:拿组队窗的 +0x3B8 去读交易窗 —— 组队窗 +0x3B8 = 1448629752(正是 7034 按钮对象,type=34) 交易窗 +0x3B8 = 2543863649(读出来的 +8 类型码是 nil,根本不是对象) → 一个窗口的偏移套到另一个窗口上,取到的是**垃圾**,不会报错但点了没反应。 这就是"为什么不能写一套偏移通用"。 ★ 顺便:交易窗和组队窗的"左/中/右"三个按钮**坐标完全相同** (左 85,178 中 119,178 右 156,178 ,都是 18x60)—— 因为两个窗口用的是**同一套对话框模板**,只是各自的类把它挂在了不同槽位上。 三、按钮对象内部结构(本次挖全,全部按钮一致) ================================================================================ 以交易窗左按钮(PID 16060,id 6700)为例: 按钮对象 + 0x00 = 0x010F1EE4 虚表(ButtonClass) + 0x04 = 父窗口对象 + 0x08 = 34 类型码(34 = ButtonClass,稳定判据) + 0x0C = 控件ID ★点按钮就用这个(现读,别写死) + 0x10 = 19 + 0x14 = 0xFFFFFFFF + 0x1C = 1 + 0x28 = 85 ★x 坐标(相对所属面板) + 0x2C = 178 ★y 坐标 + 0x30 = 18 ★宽 + 0x34 = 60 ★高 + 0x38 = 所属面板坐标字段地址(该值 − 0x28 = 所属面板对象基址) + 0x40 = 可用(enabled) 会变:PID2436 左=en0 / PID16060 左=en1 + 0x48 = 生效(active) 会变 + 0x4C = 所属面板的控件ID ★可用来筛"这个按钮属于哪个页" ★ 关键:**类型码 34 是按钮的稳定判据**(两进程、所有按钮、两个窗口全部成立); 而 +0x40 / +0x48 会随状态变,不能当"是不是按钮"的判据,只能当"现在能不能点"的判据。 ★ 另外:窗口自身的子槽里也有 +0x320 / +0x324 这种"看着像指针其实是浮点常量"的格子 (obj=1065353216 = 1.0f = 0x3F800000),扫描时必须排除,否则会算出假对象。 四、三种取法,按场景选 ================================================================================ ① 固定偏移表(最简单,本版本内 100% 可靠) 交易窗左 = 窗口+908 右 = 窗口+916 (0x38C / 0x394) 组队窗左 = 窗口+952 右 = 窗口+960 (0x3B8 / 0x3C0) 缺点:每个窗口都要单独记一条;游戏更新后可能整体平移。 ② 扫 + 按坐标认(推荐,跨窗口通用、不依赖记偏移) 扫 窗口对象 + 0x300 ~ +0x600(192 格),挑 类型码==34 的; 再比 x 坐标:85 = 左,156 = 右(119 = 中,通常未启用)。 优点:交易窗 / 组队窗 / 其它同模板窗口全通用;游戏更新平移也不受影响。 缺点:一次要读约 200 个格子(可接受)。 ③ 扫 + 按所属面板筛(要精确到"某个页签里的按钮"时用) 先找到目标面板(类型码 24 + 已知面板ID),再扫按钮,挑 +0x4C == 该面板ID 的。 五、易语言子程序(照用户工程风格,纯语句体) ================================================================================ 依赖:内存_读整数型(全局_进程句柄, 地址)、挂机信息.加入文本()、#IFManager基址 常量 新增常量(十进制,直接当整数用): 类型码偏移 8 / 控件ID偏移 12 / 子槽起点 768 / 按钮类型码 34 按钮X 40 / 按钮Y 44 / 按钮宽 48 / 按钮高 52 / 可用 64 / 生效 72 / 所属面板ID 76 -------------------------------------------------------------------------------- ① 按坐标取按钮ID(扫,通用) -------------------------------------------------------------------------------- .版本 2 .子程序 取窗口按钮ID_按横坐标, 整数型, , 传窗口槽偏移和横坐标(85=左 156=右),返回控件ID;找不到返回 0 .参数 槽偏移, 整数型 .参数 横坐标, 整数型 .局部变量 接口管理, 整数型 .局部变量 窗口对象, 整数型 .局部变量 i, 整数型 .局部变量 子对象, 整数型 接口管理 = 内存_读整数型 (全局_进程句柄, #IFManager基址) .如果真 (接口管理 ≤ 65536) 返回 (0) .如果真结束 窗口对象 = 内存_读整数型 (全局_进程句柄, 接口管理 + 槽偏移) .如果真 (窗口对象 ≤ 65536) 返回 (0) .如果真结束 .计次循环首 (192, i) 子对象 = 内存_读整数型 (全局_进程句柄, 窗口对象 + 768 + (i - 1) × 4) .如果真 (子对象 > 65536) .如果真 (内存_读整数型 (全局_进程句柄, 子对象 + 8) = 34) .如果真 (内存_读整数型 (全局_进程句柄, 子对象 + 40) = 横坐标) 返回 (内存_读整数型 (全局_进程句柄, 子对象 + 12)) .如果真结束 .如果真结束 .如果真结束 .计次循环尾 () 返回 (0) -------------------------------------------------------------------------------- ② 点窗口按钮(扫 + 派发事件 0x409,一条龙) -------------------------------------------------------------------------------- .版本 2 .子程序 点窗口按钮_按横坐标, 逻辑型 .参数 槽偏移, 整数型 .参数 横坐标, 整数型 .局部变量 控件ID, 整数型 控件ID = 取窗口按钮ID_按横坐标 (槽偏移, 横坐标) .如果真 (控件ID ≤ 0) 返回 (假) .如果真结束 按钮点击call (控件ID) 返回 (真) -------------------------------------------------------------------------------- ③ 按钮表诊断(游戏更新后跑一次,重新定位偏移用) -------------------------------------------------------------------------------- .版本 2 .子程序 打印窗口按钮表, , , 把该窗口所有按钮的 偏移/ID/坐标/大小 写进挂机日志 .参数 槽偏移, 整数型 .局部变量 接口管理, 整数型 .局部变量 窗口对象, 整数型 .局部变量 i, 整数型 .局部变量 子对象, 整数型 接口管理 = 内存_读整数型 (全局_进程句柄, #IFManager基址) 窗口对象 = 内存_读整数型 (全局_进程句柄, 接口管理 + 槽偏移) 挂机信息.加入文本 (“=== 窗口槽 ” + 到文本 (槽偏移) + “ 按钮表 ===” + #换行符) .如果真 (窗口对象 > 65536) .计次循环首 (192, i) 子对象 = 内存_读整数型 (全局_进程句柄, 窗口对象 + 768 + (i - 1) × 4) .如果真 (子对象 > 65536) .如果真 (内存_读整数型 (全局_进程句柄, 子对象 + 8) = 34) 挂机信息.加入文本 (“槽 ” + 到文本 (768 + (i - 1) × 4) + “ ID=” + 到文本 (内存_读整数型 (全局_进程句柄, 子对象 + 12)) + “ 坐标” + 到文本 (内存_读整数型 (全局_进程句柄, 子对象 + 40)) + “,” + 到文本 (内存_读整数型 (全局_进程句柄, 子对象 + 44)) + “ 大小” + 到文本 (内存_读整数型 (全局_进程句柄, 子对象 + 48)) + “x” + 到文本 (内存_读整数型 (全局_进程句柄, 子对象 + 52)) + “ 可用=” + 到文本 (内存_读整数型 (全局_进程句柄, 子对象 + 64)) + “ 生效=” + 到文本 (内存_读整数型 (全局_进程句柄, 子对象 + 72)) + “ 面板ID=” + 到文本 (内存_读整数型 (全局_进程句柄, 子对象 + 76)) + #换行符) .如果真结束 .如果真结束 .计次循环尾 () .如果真结束 六、结论速记 ================================================================================ · 从窗口对象按固定偏移拿按钮对象 → 方法对,但**偏移只对本窗口有效** · 同一版本两个进程 → 偏移一模一样(固定);控件ID 每次都不一样(现读) · 交易窗 / 组队窗偏移不同 → 因为它们是两个不同的类,不是"大小不一样" · 想省心就用「扫 + 按坐标 85/156 认左右」,跨窗口通用、更新不怕平移 · 类型码 34 = 按钮,是唯一稳定的按钮判据;+0x40/+0x48 会变,别当判据用 本次脚本:D:\ce_mcp\_t44 系列(scan_winbtn.py / scan2.py,本机在 rxjh\_tmp\) ================================================================================