.版本 2 ' ──────────────────────────────────────── ' 小助手调用 v3.2(2026-09-18) ' 本版改动: ' 1) 机器码核心抽成独立子程序 助手_发命令,只有 1 个参数、0 个局部变量, ' 机器码只读 [ebp+8] —— 不再依赖易语言局部变量的排布顺序(上一版 ④ 死在这) ' 2) ④ 小助手_点控件 改成纯易语言取控件指针 + 调 助手_发命令 ' 3) ②③ 保持 v3.1 原样(实机已验证可用,不动) ' 4) 所有说明文字一律 ' 注释,无裸行 ' ──────────────────────────────────────── ' ' ▎机制真相(本次静态逆向新结论,v3.2 的依据) ' 点击 = 把「动作码 0x3F4 + 控件ID」发给自己窗口的虚表槽#1: ' 助手窗口虚表 = 0x10A20F8 槽0 = 0xA2FBF0(构造) 槽1 = 0xA3F3D0(命令分派) ' 0xA3F3D0 只认 6 个动作码,其余一律忽略: ' 3 → 转发 0xA41740 6 → 转发 0xA427E0 7 → 布局刷新 8 → 每帧刷新 ' 0x3EE → 需 arg3=1 才转发(状态通知) ' 0x3F4 → ★ 点控件专用:把 (控件ID, lParam) 转发给 0xA3F700 ' 0x3F4 那一支原文: ' 0xA3F6DB mov edx,[ebp+0x10] / push edx ; lParam ' mov eax,[ebp+0xc] / push eax ; 控件ID ' mov ecx,[ebp-4] / call 0xA3F700 ; this = 助手窗口 ' → 所以「控件 ID」就是 0xA3F700 的「选项码」,两者是同一个编号体系。 ' → 你旧代码直接 call 0xA3F700 传 0x31,与真实点击 0x31 走到的是同一个函数。 ' ' ▎控件 ID 有效性(解开 0xA3F700 内部 154 项跳转表 0xA41588 / 索引表 0xA41698) ' 有独立实现(38 个,实机 74 控件里): ' 0B 0C 0D 0E 12 13 14 15 28 29 2A 2B 2C 2D 2F 31 32 33 34 35 36 37 39 3B ' 53 54 55 56 98 99 9A 9B 9C 9E 9F A0 A3 A4 ' 走公共尾部 0xA41204(也是真实现,按区间批量处理勾选框/页签): ' 0x1C~0x27 → 12 个勾选框,逐位取反 + 控件状态刷新 ' 0x46~0x52 → 13 个互斥页签 ' 0x65~0x6D → 9 个 ' 0x80~0x88 / 0x89~0x90 → 9 + 8 个 ' 0xA1 / 0xA2 → 2 个 ' 0x9D → 落在尾部但不在任何区间 = 真正空转 ' 超范围直接忽略(0x0B~0xA4 之外): ' 0x01(那 4 个「ID=01」槽)、0x425、0x4DE、0x1C9 ' 结论:你实机 74 个控件里,只有 0x01 / 0x9D 和 3 个超范围 ID 点了没反应, ' 其余都有实现 —— 所以「其他均无效」不是游戏不支持,是 ④ 的机器码取错了值。 ' ' ▎链路(全程静态,不用特征码、不用 call 基址) ' 助手窗口 = [[#NPC基址值] + 0x414] ' 游戏在 0x9E73CE 写入 ' 按钮 = [助手窗口 + 偏移] ' 父窗口 = [按钮 + 4] ' 实测 == 助手窗口本身 ' 命令ID = [按钮 + 0x364] ' 点击 = push 0 → push 命令ID → push 0x3F4 → ecx=父窗口 → call [ecx虚表+4] ' ' ▎偏移表(实机遍历 + 静态登记双确认) ' 3938 开始挂机(0x31) 393C 停止挂机(0x32) ' 37F8 设置(0x33) 37FC 关闭窗口(0x34) ' 3940 保存(0x35) 3800 取消(0x36) ' 39E8 弹窗确定(0xA3) 39EC 弹窗取消(0xA4) ' ' ▎参数写法 ' 小助手_点控件 ("393C") ← 对 ' 小助手_点控件 (按钮编辑框2.内容) ← 对(编辑框里填 393C / 3938 / 37F8) ' 小助手_点控件 (十六到十 (编辑框.内容)) ← 多余,别套(内部已 十六到十) ' ' ▎机器码字节规则(踩过的坑) ' 104 = 0x68 = push imm32 → 68 F4 03 00 00 = push 0x3F4 ✓ ' 106 = 0x6A = push imm8 → 6A F4 + 03 00 变成 add eax,[eax],整段错位 ✗ ' (push 0x31 这类 1 字节立即数用 106 才对) ' ──────────────────────────────────────── ' ① 遍历控件(开一次收集全表,只读、不发命令) ' ──────────────────────────────────────── .子程序 遍历助手控件, , 公开, 把助手窗口里所有控件指针槽的 ID 打进日志 .局部变量 偏移, 整数型 .局部变量 指针, 整数型 .局部变量 ID, 整数型 .局部变量 助手窗口, 整数型 助手窗口 = 内存_读整数型 (全局_进程句柄, 内存_读整数型 (全局_进程句柄, #NPC基址值) + 十六到十 ("414")) .如果真 (助手窗口 = 0 或 助手窗口 = -1) 写日志 (自定义时间 () + "遍历失败【助手窗口未创建,先在游戏里打开助手】" + #换行符) 返回 () .如果真结束 偏移 = 十六到十 ("36C0") .判断循环首 (偏移 + 4 < 十六到十 ("3B00")) 指针 = 内存_读整数型 (全局_进程句柄, 助手窗口 + 偏移) .如果真 (指针 > 65536 且 指针 ≠ -1) ID = 内存_读整数型 (全局_进程句柄, 指针 + 十六到十 ("364")) .如果真 (ID > 0 且 ID < 4096) 写日志 (自定义时间 () + "控件【偏移=" + 十到十六 (偏移) + " ID=" + 十到十六 (ID) + "】" + #换行符) .如果真结束 .如果真结束 偏移 = 偏移 + 4 .判断循环尾 () ' ──────────────────────────────────────── ' ② 开始挂机(ID 0x31,偏移 3938)—— 实机已验证可用,保持不动 ' ──────────────────────────────────────── .子程序 小助手开_新路, , 公开, 等价于点「开始挂机」 .局部变量 ECX, 整数型 .局部变量 按钮, 整数型 .局部变量 父窗口, 整数型 ECX = 内存_读整数型 (全局_进程句柄, 内存_读整数型 (全局_进程句柄, #NPC基址值) + 十六到十 ("414")) .如果真 (ECX = 0 或 ECX = -1) 写日志 (自定义时间 () + "小助手开失败【助手窗口未创建,先在游戏里打开助手】" + #换行符) 返回 () .如果真结束 按钮 = 内存_读整数型 (全局_进程句柄, ECX + 十六到十 ("3938")) .如果真 (按钮 = 0 或 按钮 = -1) 写日志 (自定义时间 () + "小助手开失败【开始按钮=0 助手窗口=" + 到文本 (ECX) + "】" + #换行符) 返回 () .如果真结束 父窗口 = 内存_读整数型 (全局_进程句柄, 按钮 + 4) .如果真 (父窗口 = 0 或 父窗口 = -1) 写日志 (自定义时间 () + "小助手开失败【父窗口=0】" + #换行符) 返回 () .如果真结束 写日志 (自定义时间 () + "链路[v3.2]【窗口=" + 到文本 (ECX) + " 按钮=" + 到文本 (按钮) + " 父窗口=" + 到文本 (父窗口) + " 虚表=" + 十到十六 (内存_读整数型 (全局_进程句柄, 父窗口)) + " 槽1=" + 十到十六 (内存_读整数型 (全局_进程句柄, 内存_读整数型 (全局_进程句柄, 父窗口) + 4)) + "】" + #换行符) ECX = 父窗口 ' ★ 父窗口放进第一个局部变量,机器码 mov ecx,[ebp-4] 才取对 置入代码 ({ 96 }) ' pushad 置入代码 ({ 106, 0 }) ' push 0 lParam 置入代码 ({ 106, 49 }) ' push 0x31 wParam=开始挂机 置入代码 ({ 104, 244, 3, 0, 0 }) ' push 0x3F4 msg 置入代码 ({ 139, 77, 252 }) ' mov ecx,[ebp-4] 置入代码 ({ 139, 17 }) ' mov edx,[ecx] 置入代码 ({ 255, 82, 4 }) ' call [edx+4] → 助手窗口虚表槽#1 = 0xA3F3D0 置入代码 ({ 97 }) ' popad 写日志 (自定义时间 () + "使用[v3.2]【小助手开·新路】" + #换行符) 返回 () ' ──────────────────────────────────────── ' ③ 停止挂机(ID 0x32,偏移 393C)—— 实机已验证可用,保持不动 ' ──────────────────────────────────────── .子程序 小助手关_新路, , 公开, 等价于点「停止挂机」 .局部变量 ECX, 整数型 .局部变量 按钮, 整数型 .局部变量 父窗口, 整数型 ECX = 内存_读整数型 (全局_进程句柄, 内存_读整数型 (全局_进程句柄, #NPC基址值) + 十六到十 ("414")) .如果真 (ECX = 0 或 ECX = -1) 写日志 (自定义时间 () + "小助手关失败【助手窗口未创建】" + #换行符) 返回 () .如果真结束 按钮 = 内存_读整数型 (全局_进程句柄, ECX + 十六到十 ("393C")) .如果真 (按钮 = 0 或 按钮 = -1) 写日志 (自定义时间 () + "小助手关失败【停止按钮=0 助手窗口=" + 到文本 (ECX) + "】" + #换行符) 返回 () .如果真结束 父窗口 = 内存_读整数型 (全局_进程句柄, 按钮 + 4) .如果真 (父窗口 = 0 或 父窗口 = -1) 写日志 (自定义时间 () + "小助手关失败【父窗口=0】" + #换行符) 返回 () .如果真结束 ECX = 父窗口 置入代码 ({ 96 }) ' pushad 置入代码 ({ 106, 0 }) ' push 0 置入代码 ({ 106, 50 }) ' push 0x32 wParam=停止挂机 置入代码 ({ 104, 244, 3, 0, 0 }) ' push 0x3F4 msg 置入代码 ({ 139, 77, 252 }) ' mov ecx,[ebp-4] 置入代码 ({ 139, 17 }) ' mov edx,[ecx] 置入代码 ({ 255, 82, 4 }) ' call [edx+4] 置入代码 ({ 97 }) ' popad 写日志 (自定义时间 () + "使用[v3.2]【小助手关·新路】" + #换行符) 返回 () ' ──────────────────────────────────────── ' ⑤ 机器码核心(唯一含置入代码的子程序;只有 1 个参数、0 个局部变量) ' 机器码只读 [ebp+8] —— 参数位置是调用约定的硬约定,与局部变量排布无关 ' ──────────────────────────────────────── .子程序 助手_发命令, 逻辑型, 公开, 传控件指针;命令ID自动从 [控件+0x364] 读 .参数 控件地址, 整数型 .如果真 (控件地址 ≤ 65536 或 控件地址 = -1) 返回 (假) .如果真结束 置入代码 ({ 96 }) ' pushad 置入代码 ({ 106, 0 }) ' push 0 lParam 置入代码 ({ 139, 69, 8 }) ' mov eax,[ebp+8] 控件(唯一参数) 置入代码 ({ 139, 128, 100, 3, 0, 0 }) ' mov eax,[eax+0x364] 命令ID 置入代码 ({ 80 }) ' push eax wParam=命令ID 置入代码 ({ 104, 244, 3, 0, 0 }) ' push 0x3F4 msg 置入代码 ({ 139, 69, 8 }) ' mov eax,[ebp+8] 置入代码 ({ 139, 72, 4 }) ' mov ecx,[eax+4] 父窗口 置入代码 ({ 139, 17 }) ' mov edx,[ecx] 置入代码 ({ 255, 82, 4 }) ' call [edx+4] → 0xA3F3D0 置入代码 ({ 97 }) ' popad 返回 (真) ' ──────────────────────────────────────── ' ④ 通用:点任意控件(传偏移十六进制字符串,如 "393C") ' 只负责「算出控件指针」,发命令交给 ⑤ ' ──────────────────────────────────────── .子程序 小助手_点控件, 逻辑型, 公开, 传十六进制字符串 "3938"/"393C"/"37F8"… .参数 控件偏移文本, 文本型 .局部变量 控件, 整数型 .局部变量 文本, 文本型 .局部变量 候选1, 整数型 .局部变量 候选2, 整数型 .局部变量 助手窗口, 整数型 助手窗口 = 内存_读整数型 (全局_进程句柄, 内存_读整数型 (全局_进程句柄, #NPC基址值) + 十六到十 ("414")) .如果真 (助手窗口 = 0 或 助手窗口 = -1) 写日志 (自定义时间 () + "点控件失败[v3.2]【助手窗口未创建,先在游戏里打开助手】" + #换行符) 返回 (假) .如果真结束 文本 = 到大写 (删首尾空 (控件偏移文本)) .如果真 (取文本左边 (文本, 2) = "0X") 文本 = 取文本右边 (文本, 取文本长度 (文本) - 2) .如果真结束 .如果真 (取文本左边 (文本, 2) = "&H") 文本 = 取文本右边 (文本, 取文本长度 (文本) - 2) .如果真结束 候选1 = 十六到十 (文本) 候选2 = 到整数 (文本) 控件 = 助手_取槽控件 (助手窗口, 候选1) .如果真 (控件 = 0 且 候选2 ≠ 候选1) 控件 = 助手_取槽控件 (助手窗口, 候选2) .如果真结束 .如果真 (控件 = 0) 写日志 (自定义时间 () + "点控件失败[v3.2]【输入=" + 控件偏移文本 + " → 试过 16进制=" + 十到十六 (候选1) + " / 10进制=" + 十到十六 (候选2) + ",两处都没有控件】" + #换行符) 写日志 (自定义时间 () + "可用偏移:3938开始 393C停止 37F8设置 37FC关闭 3940保存 3800取消 39E8确定 39EC取消" + #换行符) 返回 (假) .如果真结束 写日志 (自定义时间 () + "链路[v3.2]【控件=" + 十到十六 (控件) + " ID=" + 十到十六 (内存_读整数型 (全局_进程句柄, 控件 + 十六到十 ("364"))) + " 父窗口=" + 十到十六 (内存_读整数型 (全局_进程句柄, 控件 + 4)) + " 虚表=" + 十到十六 (内存_读整数型 (全局_进程句柄, 内存_读整数型 (全局_进程句柄, 控件 + 4))) + "】" + #换行符) .如果真 (助手_发命令 (控件) = 假) 写日志 (自定义时间 () + "点控件失败[v3.2]【控件指针非法,未发命令】" + #换行符) 返回 (假) .如果真结束 写日志 (自定义时间 () + "使用[v3.2]【点控件 " + 控件偏移文本 + "】" + #换行符) 返回 (真) ' ──────────────────────────────────────── ' ④-A 辅助:校验某个偏移上真的有控件 ' 判据:指针有效 + [指针] 指向 exe 代码段 + [指针+0x364] 是 1~4095 的 ID ' ──────────────────────────────────────── .子程序 助手_取槽控件, 整数型, , 传窗口与偏移,有效返回控件指针,无效返回 0 .参数 助手窗口, 整数型 .参数 偏移, 整数型 .局部变量 指针, 整数型 .局部变量 虚表, 整数型 .局部变量 ID, 整数型 指针 = 内存_读整数型 (全局_进程句柄, 助手窗口 + 偏移) .如果真 (指针 ≤ 65536 或 指针 = -1) 返回 (0) .如果真结束 虚表 = 内存_读整数型 (全局_进程句柄, 指针) .如果真 (虚表 < 十六到十 ("400000") 或 虚表 > 十六到十 ("1200000")) 返回 (0) .如果真结束 ID = 内存_读整数型 (全局_进程句柄, 指针 + 十六到十 ("364")) .如果真 (ID < 1 或 ID > 4095) 返回 (0) .如果真结束 返回 (指针) ' ──────────────────────────────────────── ' 测试顺序 ' ──────────────────────────────────────── ' 1. 游戏里先打开助手窗口([#NPC基址值+0x414] 是窗口创建时才写入的) ' 2. 调 小助手开_新路 → 开始挂机;调 小助手关_新路 → 停止(这两个 v3.1 已验证可用) ' 3. 调 小助手_点控件 ("393C") → 应停止挂机,日志出现「链路[v3.2]【控件=… ID=32 父窗口=… 虚表=10A20F8】」 ' 4. 调 小助手_点控件 ("37F8") → 弹出设置面板 ' 5. 调 小助手_点控件 ("37FC") → 关闭助手面板 ' 6. 日志里 虚表 必须是 10A20F8;不是就说明控件取错了,把整行发我 ' 7. 若第 3 步仍无反应,把 ⑤ 里两条 139, 69, 8([ebp+8])改成 139, 69, 252([ebp-4]) ' 并给 ⑤ 加一行「.局部变量 控件, 整数型」+「控件 = 控件地址」,即可切换成局部变量版对照