热血江湖 YGOnline.exe —— 「窗口是否弹出」参数防呆 + 初始化放在哪里 时间:2026-09-28 15:5x 目标:D:\rxjh2025\client\YGOnline.exe(32位,无ASLR,模块基址恒 0x400000) ================================================================================ 【这次的问题:日志里 槽 0 (0x0)】 15:55:37 [窗判] 槽 0 (0x0) (类名读取失败) 对象 17701228 开始扫描 15:55:37 [窗判] 槽 0 结论:未弹出 两个读数各说明一件事: ① 对象 17701228 = 0x010E196C 这是 **IFManager 对象自己的虚表指针**(0x010E196C 就是 IFManager 的 vtable)。 它出现在"对象"栏,是因为取对象用的是 `接口管理 + 槽偏移`,而槽偏移 = 0, 于是读到 IFManager 对象的 +0x000,正好是虚表。 → `#IFManager基址` 读对了、`接口管理` 拿到了真对象,**内存链路完全正常**。 ② 槽 0 —— 槽偏移是 0 → `窗口.交易` 这个自定义数据类型成员**没有值**。 根因只有一个:**没有调用 `窗口槽位表_初始化 ()`**。 易语言自定义数据类型的 `.成员` **不能带默认值**,声明出来就是 0, 必须在进入游戏后手动赋一次值。漏了 → 所有成员都是 0 → 判定永远返回假。 次要可能(按可能性排序): · 调了初始化,但调用点在 `自动处理交易` 之后(如写在登录成功回调里,还没跑); · `窗口` 变量声明在 A 程序集、赋值写在 B 程序集,实际赋的是另一个变量; · 初始化子程序根本没被任何地方调用(写完忘了接)。 旁证:日志里 `(类名读取失败)` 是必然的 —— 槽偏移 = 0 时对象是虚表指针, RTTI 链当然读不出类名。它不是独立故障,是同一个原因的下游表现。 ================================================================================ 一、常量表(唯一一份,先粘这块) ================================================================================ .版本 2 .常量 IFManager基址, "25740828" .常量 窗口可见标志, "64" .常量 控件类型偏移, "8" .常量 面板类型码, "24" .常量 控件ID偏移, "12" .常量 窗口子槽起点, "768" .常量 窗口子槽终点, "1532" .常量 窗口子槽个数, "192" .常量 窗口主面板偏移, "816" .常量 交易窗口槽, "1236" .常量 组队窗口槽, "1256" .常量 通知窗口槽, "1028" .常量 交易接受, "908" .常量 交易拒绝, "916" .常量 组队接受, "952" .常量 组队拒绝, "960" ================================================================================ 二、修正 ①:判定子程序加参数合法性检查 ================================================================================ 槽偏移的合法范围是 768 ~ 1532(0x300 ~ 0x5FC),即 192 个槽 × 4 字节。 不在这个范围里,一定是传参问题、不是内存问题 —— 直接判假并写日志,不用白扫 192 次。 以后日志里再出现「槽偏移非法」,一眼就知道是没初始化,不用猜。 .版本 2 .子程序 窗口是否弹出, 逻辑型 .参数 槽偏移, 整数型 .局部变量 接口管理, 整数型 .局部变量 窗口对象, 整数型 .局部变量 子对象, 整数型 .局部变量 i, 整数型 .如果真 (槽偏移 < #窗口子槽起点 或 槽偏移 > #窗口子槽终点) 挂机信息.加入文本 (自定义时间 () + “[窗判] 槽偏移非法:” + 到文本 (槽偏移) + “(合法 768~1532),多半是没调 窗口槽位表_初始化 ()” + #换行符) 返回 (假) .如果真结束 接口管理 = 内存_读整数型 (全局_进程句柄, #IFManager基址) .如果真 (接口管理 ≤ 65536) 挂机信息.加入文本 (自定义时间 () + “[窗判] IFManager 未读到(” + 到文本 (接口管理) + “)” + #换行符) 返回 (假) .如果真结束 窗口对象 = 内存_读整数型 (全局_进程句柄, 接口管理 + 槽偏移) .如果真 (窗口对象 ≤ 65536) 返回 (假) .如果真结束 .计次循环首 (#窗口子槽个数, i) 子对象 = 内存_读整数型 (全局_进程句柄, 窗口对象 + #窗口子槽起点 + (i - 1) × 4) .如果真 (子对象 > 65536) .如果真 (内存_读整数型 (全局_进程句柄, 子对象 + #控件类型偏移) = #面板类型码) .如果真 (内存_读整数型 (全局_进程句柄, 子对象 + #窗口可见标志) = 1) 返回 (真) .如果真结束 .如果真结束 .如果真结束 .计次循环尾 () 返回 (假) ================================================================================ 三、修正 ②:初始化改成幂等 ================================================================================ 新增一个全局变量(放「全局变量」表里): .版本 2 .全局变量 全局_槽位表就绪, 逻辑型 -------------------------------------------------------------------------------- 初始化子程序本身加一道就绪判断,变成幂等的 —— 调多少次都只有第一次真正赋值: .版本 2 .子程序 窗口槽位表_初始化 窗口.主界面 = 940 窗口.聊天框 = 944 窗口.状态栏 = 948 窗口.背包 = 952 窗口.中央消息 = 960 窗口.武功栏 = 964 窗口.选项 = 972 窗口.小地图 = 976 窗口.帮助 = 980 窗口.退出 = 984 窗口.任务 = 988 窗口.信使 = 1008 窗口.染色 = 1016 窗口.脱色 = 1020 窗口.幸运箱 = 1024 窗口.通知 = 1028 窗口.网吧福利 = 1036 窗口.邮寄包裹 = 1040 窗口.计时器 = 1044 窗口.寻路 = 1048 窗口.门派召集 = 1052 窗口.血战 = 1056 窗口.转服 = 1064 窗口.别号 = 1068 窗口.助手 = 1072 窗口.变身宠物 = 1076 窗口.美发 = 1084 窗口.四柱 = 1096 窗口.福袋 = 1100 窗口.制作 = 1112 窗口.制药 = 1120 窗口.改名 = 1128 窗口.组队搜索 = 1136 窗口.商城道具 = 1144 窗口.商城选择 = 1148 窗口.属性石 = 1164 窗口.礼券 = 1172 窗口.英雄武器 = 1180 窗口.传送到队伍 = 1196 窗口.祈愿 = 1200 窗口.宠物背包 = 1208 窗口.宠物信息 = 1212 窗口.四柱系统 = 1220 窗口.门派战 = 1224 窗口.换装 = 1228 窗口.回城 = 1232 窗口.交易 = 1236 窗口.属性石强化 = 1240 窗口.宏 = 1244 窗口.邮件 = 1248 窗口.武功学习 = 1252 窗口.组队 = 1256 窗口.个人商店 = 1260 窗口.商城窗口 = 1268 窗口.攻城战 = 1272 窗口.网页 = 1276 窗口.网页商城 = 1280 窗口.护符 = 1284 窗口.门派 = 1288 窗口.副本队伍 = 1300 窗口.社区 = 1304 窗口.角色信息 = 1308 窗口.制造 = 1312 窗口.选项UI = 1316 窗口.魂宠进化 = 1332 窗口.魂宠强化 = 1336 窗口.魂宠仓库 = 1340 窗口.魂宠封印 = 1344 窗口.表情 = 1348 窗口.弹珠进化 = 1352 窗口.门派改名 = 1356 窗口.副本脚本 = 1360 返回 () ================================================================================ 四、★ 初始化到底放哪里(这是本文件重点) ================================================================================ 先说结论:**放在挂机主循环的第一行**,或者次选 `_启动窗口_创建完毕`。 理由:`窗口槽位表_初始化` 只做一件事 —— 给全局变量 `窗口` 的 72 个成员赋常量值。 它 **不读游戏内存、不依赖登录、不依赖窗口是否打开**,所以什么时候调都正确。 唯一的硬约束只有一条:**必须早于任何 `窗口.xxx` 的使用**。 位置 什么时候生效 评价 -------------------------------------------------------------------------- 挂机主循环第一行 循环首次执行时 ★ 推荐,永远不会漏 _启动窗口_创建完毕 程序启动瞬间 好,一劳永逸 每个用到的子程序开头 该功能首次被调 能跑,但新增子程序容易忘 ---------------------------------- 放在 窗口是否弹出 之后 ✗ 错:用的时候还没值 为什么推荐"主循环第一行": · 不管你从哪条路径进挂机逻辑,主循环一定先跑,所以不可能漏; · 加了就绪标志后,每轮只多一次逻辑型判断(≈ 0 开销),可以完全不管它; · 多线程同时首次进入也无害 —— 两个线程赋的是同一批常量,值恒定。 为什么不要"每个子程序开头都调":能跑,但这是**靠记性**的方案。 以后新增一个处理函数忘了加,就又会出现「槽 0」这种静默错误 —— 而它不报错、不崩溃,最难查。 为什么不要放"登录成功后":不必要。初始化不碰游戏内存,跟登录没关系; 绑在登录回调用,反而多一个"回调没触发"的失败面。 备选写法示意(主循环版,按自己工程的结构改): .版本 2 .子程序 挂机主循环 .局部变量 继续跑, 逻辑型 窗口槽位表_初始化 () 继续跑 = 真 .判断循环首 (继续跑) 挂机逻辑 () 延时 (1000) .判断循环尾 () 返回 () 如果你用的是"时钟周期"事件,就把 `窗口槽位表_初始化 ()` 写在时钟事件的第一行, 或者写在 `_启动窗口_创建完毕` 里 —— 两者都行,推荐后者更干净。 ================================================================================ 五、修正 ③:自动处理交易(正确顺序 + 把初始化接上) ================================================================================ 你原来的写法逻辑没错,只差第一行的初始化。 .版本 2 .子程序 自动处理交易 .局部变量 接口管理, 整数型 .局部变量 交易窗, 整数型 .局部变量 按钮, 整数型 窗口槽位表_初始化 () .如果真 (窗口是否弹出 (窗口.交易) = 假) 返回 () .如果真结束 接口管理 = 内存_读整数型 (全局_进程句柄, #IFManager基址) 交易窗 = 内存_读整数型 (全局_进程句柄, 接口管理 + 窗口.交易) 按钮 = 内存_读整数型 (全局_进程句柄, 交易窗 + #交易接受) .如果真 (按钮 > 65536) 按钮点击call (内存_读整数型 (全局_进程句柄, 按钮 + #控件ID偏移)) .如果真结束 返回 () ================================================================================ 六、备选 A:完全不用初始化 —— 直接传十进制数字 ================================================================================ 嫌"必须记着调初始化"容易忘,就干脆不用数据类型,直接传数字。 最省心,适合只用几个窗口的脚本。 .版本 2 .子程序 自动处理交易_常量版 .局部变量 接口管理, 整数型 .局部变量 交易窗, 整数型 .局部变量 按钮, 整数型 .如果真 (窗口是否弹出 (1236) = 假) 返回 () .如果真结束 接口管理 = 内存_读整数型 (全局_进程句柄, #IFManager基址) 交易窗 = 内存_读整数型 (全局_进程句柄, 接口管理 + 1236) 按钮 = 内存_读整数型 (全局_进程句柄, 交易窗 + #交易接受) .如果真 (按钮 > 65536) 按钮点击call (内存_读整数型 (全局_进程句柄, 按钮 + #控件ID偏移)) .如果真结束 返回 () 用工程常量也一样:`.常量 窗口_交易, "1236"` → `窗口是否弹出 (#窗口_交易)`。 ================================================================================ 七、备选 B:就地赋值(数据类型照用,只是每次现赋) ================================================================================ .版本 2 .子程序 自动处理交易_就地版 .局部变量 窗口, 窗口槽位表 .局部变量 接口管理, 整数型 .局部变量 交易窗, 整数型 .局部变量 按钮, 整数型 窗口.交易 = 1236 .如果真 (窗口是否弹出 (窗口.交易) = 假) 返回 () .如果真结束 接口管理 = 内存_读整数型 (全局_进程句柄, #IFManager基址) 交易窗 = 内存_读整数型 (全局_进程句柄, 接口管理 + 窗口.交易) 按钮 = 内存_读整数型 (全局_进程句柄, 交易窗 + #交易接受) .如果真 (按钮 > 65536) 按钮点击call (内存_读整数型 (全局_进程句柄, 按钮 + #控件ID偏移)) .如果真结束 返回 () ================================================================================ 八、槽偏移合法值速查(合法范围 768 ~ 1532,且都能被 4 整除) ================================================================================ 940 主界面 1236 交易 1256 组队 944 聊天框 1248 邮件 1260 个人商店 948 状态栏 1232 回城 1288 门派 952 背包 1028 通知 1360 副本脚本 964 武功栏 1072 助手 1276 网页 972 选项 1280 网页商城 1220 四柱系统 完整 72 项见《窗口槽位表_数据类型_0928.txt》与《窗口偏移总表_可搜索.html》。 非法值的特征:0 / 几十几百的小数字 / 超过 1532 的数。 只要日志出现「槽偏移非法」,就一定是传参没值 —— 不用怀疑内存。