热血江湖 YGOnline.exe —— 「直接告诉我现在弹出的是什么窗口」子程序 时间:2026-09-28 15:0x 目标:D:\rxjh2025\client\YGOnline.exe(32位,无ASLR,模块基址恒 0x400000) ================================================================================ 【一句话】调用 `当前弹出窗口 ()`,返回当前屏幕上所有弹着的窗口名,例: InterMAIN 槽940 面板+836 InterOption 槽972 面板+832 InterExchange 槽1236 面板+820 —— 不用维护任何窗口偏移表,游戏更新也不用改代码。名字是从对象自己的 C++ 虚表反查 RTTI 类名读出来的,是"它自己说自己是谁"。 -------------------------------------------------------------------------------- 一、本次活体实测(pid 14256) -------------------------------------------------------------------------------- IFManager 对象 = 1120125408 (0x42C5B7A0) 交易窗 RTTI 链路逐级读数(这条链就是 `读控件类名` 的全部内容): 窗口对象 = 1205504664 0x47DA... ([IFManager + 0x4D4]) 虚表 = 17719912 0x010E6738 (读整数(窗口对象 + 0x00)) 定位器 = 18688952 (读整数(虚表 - 4)) ← 完整对象定位器 类型描述符 = 19162724 (读整数(定位器 + 0x0C)) +0x08 处字符串 = "?AVInterExchange@@" ← 原始字节 读文本(类型描述符 + 8) 得 ".?AVInterExchange@@", 去掉开头 4 字节 ".?AV" 与结尾 2 字节 "@@" → "InterExchange" 实测输出(80 个非空槽,只有 3 个真的有内容面板在显示): 槽0x3AC (940) InterMAIN 对象id=108 +0x344(111, 50x252) +0x494(320, 369x180) +0x4A0(338, 114x190) +0x4E8(207, 67x240) 槽0x3CC (972) InterOption 对象id=1654 +0x340(1739, 20x446) 槽0x4D4 (1236) InterExchange 对象id=6695 +0x334(6696, 220x300) 非空槽 = 80 其中有内容面板在显示的 = 3 -------------------------------------------------------------------------------- 二、常量(先加进常量表) -------------------------------------------------------------------------------- .版本 2 .常量 IFManager基址, "25740828" .常量 窗口可见标志, "64" .常量 控件类型偏移, "8" .常量 面板类型码, "24" .常量 窗口子槽起点, "768" .常量 窗口子槽个数, "192" -------------------------------------------------------------------------------- 三、子程序 ① 读控件类名(RTTI 反查,返回"InterExchange"这样的纯类名) -------------------------------------------------------------------------------- .版本 2 .子程序 读控件类名, 文本型 .参数 对象, 整数型 .局部变量 虚表, 整数型 .局部变量 定位器, 整数型 .局部变量 类型描述符, 整数型 .局部变量 名, 文本型 .局部变量 长度, 整数型 .如果真 (对象 ≤ 65536) 返回 (“”) .如果真结束 虚表 = 内存_读整数型 (全局_进程句柄, 对象) .如果真 (虚表 ≤ 65536) 返回 (“”) .如果真结束 定位器 = 内存_读整数型 (全局_进程句柄, 虚表 - 4) .如果真 (定位器 ≤ 65536) 返回 (“”) .如果真结束 类型描述符 = 内存_读整数型 (全局_进程句柄, 定位器 + 12) .如果真 (类型描述符 ≤ 65536) 返回 (“”) .如果真结束 名 = 内存_读文本型 (全局_进程句柄, 类型描述符 + 12) 长度 = 取文本长度 (名) .如果真 (长度 > 2) .如果真 (取文本右边 (名, 2) = “@@”) 名 = 取文本左边 (名, 长度 - 2) .如果真结束 .如果真结束 返回 (名) -------------------------------------------------------------------------------- 四、子程序 ② 当前弹出窗口(★ 主用,直接回答"弹出的是什么") -------------------------------------------------------------------------------- .版本 2 .子程序 当前弹出窗口, 文本型 .局部变量 接口管理, 整数型 .局部变量 i, 整数型 .局部变量 j, 整数型 .局部变量 窗口对象, 整数型 .局部变量 子对象, 整数型 .局部变量 窗口名, 文本型 .局部变量 结果, 文本型 接口管理 = 内存_读整数型 (全局_进程句柄, #IFManager基址) .如果真 (接口管理 ≤ 65536) 返回 (“”) .如果真结束 .计次循环首 (#窗口子槽个数, i) 窗口对象 = 内存_读整数型 (全局_进程句柄, 接口管理 + #窗口子槽起点 + (i - 1) × 4) .如果真 (窗口对象 > 65536) 窗口名 = 读控件类名 (窗口对象) .计次循环首 (#窗口子槽个数, j) 子对象 = 内存_读整数型 (全局_进程句柄, 窗口对象 + #窗口子槽起点 + (j - 1) × 4) .如果真 (子对象 > 65536) .如果真 (内存_读整数型 (全局_进程句柄, 子对象 + #控件类型偏移) = #面板类型码) .如果真 (内存_读整数型 (全局_进程句柄, 子对象 + #窗口可见标志) = 1) 结果 = 结果 + 窗口名 + “ 槽” + 到文本 (#窗口子槽起点 + (i - 1) × 4) + “ 面板+” + 到文本 (#窗口子槽起点 + (j - 1) × 4) + #换行符 跳出循环 () .如果真结束 .如果真结束 .如果真结束 .计次循环尾 () .如果真结束 .计次循环尾 () 返回 (结果) -------------------------------------------------------------------------------- 五、子程序 ③ 只要名字(更短,逗号分隔一行) -------------------------------------------------------------------------------- .版本 2 .子程序 当前弹出窗口名, 文本型 .局部变量 接口管理, 整数型 .局部变量 i, 整数型 .局部变量 j, 整数型 .局部变量 窗口对象, 整数型 .局部变量 子对象, 整数型 .局部变量 窗口名, 文本型 .局部变量 结果, 文本型 接口管理 = 内存_读整数型 (全局_进程句柄, #IFManager基址) .如果真 (接口管理 ≤ 65536) 返回 (“”) .如果真结束 .计次循环首 (#窗口子槽个数, i) 窗口对象 = 内存_读整数型 (全局_进程句柄, 接口管理 + #窗口子槽起点 + (i - 1) × 4) .如果真 (窗口对象 > 65536) 窗口名 = 读控件类名 (窗口对象) .如果真 (窗口名 ≠ “”) .计次循环首 (#窗口子槽个数, j) 子对象 = 内存_读整数型 (全局_进程句柄, 窗口对象 + #窗口子槽起点 + (j - 1) × 4) .如果真 (子对象 > 65536) .如果真 (内存_读整数型 (全局_进程句柄, 子对象 + #控件类型偏移) = #面板类型码) .如果真 (内存_读整数型 (全局_进程句柄, 子对象 + #窗口可见标志) = 1) 结果 = 结果 + 窗口名 + “,” 跳出循环 () .如果真结束 .如果真结束 .如果真结束 .计次循环尾 () .如果真结束 .如果真结束 .计次循环尾 () 返回 (结果) -------------------------------------------------------------------------------- 六、子程序 ④ 监视新弹出的窗口(差分,只报"这次新出现的") -------------------------------------------------------------------------------- 需要先声明程序集/全局变量:全局_上次可见窗口 (文本型) .版本 2 .全局变量 全局_上次可见窗口, 文本型 .版本 2 .子程序 监视窗口弹出, 文本型 .局部变量 当前, 文本型 .局部变量 行数组, 文本型, , "0" .局部变量 k, 整数型 .局部变量 新弹出, 文本型 当前 = 当前弹出窗口 () 新弹出 = “” 行数组 = 分割文本 (当前, #换行符, ) .计次循环首 (取数组成员数 (行数组), k) .如果真 (行数组 [k] ≠ “”) .如果真 (寻找文本 (全局_上次可见窗口, 行数组 [k], , 假) = -1) 新弹出 = 新弹出 + 行数组 [k] + #换行符 .如果真结束 .如果真结束 .计次循环尾 () 全局_上次可见窗口 = 当前 返回 (新弹出) 用法:挂机循环里每隔 1 秒调一次,返回非空就是这一刻新弹了什么窗口。 首次调用会把当前所有窗口都当成"新弹出",可先空跑一次丢弃。 -------------------------------------------------------------------------------- 七、怎么调用(三种写法,挑一个) -------------------------------------------------------------------------------- 1) 写进挂机日志 挂机信息.加入文本 (自定义时间 () + “当前窗口:” + #换行符 + 当前弹出窗口 () + #换行符) 2) 直接弹个框看(调试用) 信息框 (当前弹出窗口 (), 0, ) 3) 挂在循环里自动喊新弹窗 .局部变量 新窗, 文本型 新窗 = 监视窗口弹出 () .如果真 (新窗 ≠ “”) 挂机信息.加入文本 (自定义时间 () + “★新弹窗:” + #换行符 + 新窗 + #换行符) .如果真结束 -------------------------------------------------------------------------------- 八、必须知道的四个坑 -------------------------------------------------------------------------------- ① **InterChat(944) 判不出来** —— 它的子对象是类型 20/23,没有类型码 24 的面板。 但它是常驻聊天框,本来也不用判。 ② **InterNotice(1028) 判不出来** —— 通知弹窗的面板在"孙辈",要走两级: +0x404 → 通知对象 → +0x338 弹窗对象 → +0x330 面板 → +0x40。 而且同层 +0x330/+0x334 是常驻竖条,其子面板 vis 恒为 1,拿它判会误报。 ③ **窗口对象自己的 +0x40 不能用**(粘滞,关窗不清零)。 实证:交易窗已关闭时 InterExchange 的 +0x40 仍 = 1。 子按钮的 +0x40 也不能用(没打开过的窗口,按钮 vis 也常是 1)。 **唯一干净的是"类型码 24 = InterWindow 的内容面板"的 +0x40。** ④ **面板偏移不统一(78 种)**,所以本方案用"扫"而不是"查表": 窗口对象 + 0x300 ~ 0x5FC 逐 4 字节取指针,判类型码。绝大多数是 +0x330, 但还有 +0x334/+0x338/+0x340/+0x344/+0x350/+0x3A4/+0x498/+0x540/+0x58C… 写死一个偏移必漏。实测单次约 3.7 万次读内存,易语言里约 0.1~0.3 秒,够用。 -------------------------------------------------------------------------------- 九、性能与替代方案 -------------------------------------------------------------------------------- · 只想判某一个已知窗口 → 用 `窗口是否弹出 (1236)`(约 200 次读,快 100 倍) · 想枚举全部窗口名 → 用本文 `当前弹出窗口 ()`(通用、免维护) · 想更快 → 外层只扫 0x3AC ~ 0x554(实测有对象的范围),内层扫满 0x300~0x5FC 实测基准:本机 80 个非空槽,其中真的"有面板且在显示"的只有 3 个(主界面/选项/交易)。 ================================================================================