游戏客户端:D:\rxjh2025\client\YGOnline.exe(32 位,ImageBase = 0x400000)
验证方式:Python 解析 PE + capstone 反汇编 基址更新 子程序里 道具遍历基址 特征命中点及其调用函数。
道具遍历基址 是「地面/掉落物品」的世界对象容器(global container),不是背包物品、也不是副本道具。
*(0x1801170 + 4) 指向一个 6400 槽的指针数组,每槽 = 一个「地上物品」结构指针element+8 关联回怪物/实体数组 0x03BE3E68(即你已知的 怪物基址值),证明它是「怪物/场景身上的掉落物」而非玩家背包也就是说:这个基址遍历的是躺在地上、可被拾取的世界物品(掉落物 / 场景道具),和「背包里的物品(背包基址值 0x03B9DCE0)」「副本里的道具(副本道具基址 0x9539F5)」是三套完全不同的数据。
基址更新 里特征:
8B 95 E0 7F FF FF 83 C2 01 89 95 E0 7F FF FF 81 BD E0 7F FF FF 00 19 00 00
YGOnline.exe 二进制中精确命中于 文件偏移 0x368122(VA 0x768D22)临时地址 = 命中点 + 0x27 → VA 0x768D49,该处是 mov ecx, 0x1801170(b9 70 11 80 01)中间字节ReadIntAddr(handle, 0x768D49) 读出 4 字节 = 0x01801170 → 即 道具遍历基址[OK] 道具遍历基址 target(mod_rel+adj)=0x368D49)0x768D16 mov dword ptr [ebp-0x8020], 0 ; i = 0
0x768D22 mov edx, [ebp-0x8020] ; —— 特征起点 ——
0x768D28 add edx, 1
0x768D2B mov [ebp-0x8020], edx
0x768D31 cmp [ebp-0x8020], 0x1900 ; i < 6400 ?
0x768D3B jge 0x76914c ; 退出
0x768D41 mov eax, [ebp-0x8020] ; i
0x768D47 push eax
0x768D48 mov ecx, 0x1801170 ; —— 容器 this ——
0x768D4D call 0x769ba0 ; GetAt(i)
0x768D52 mov [ebp-0x8018], eax ; element = 指针
0x768D63 mov ecx, [ebp-0x8018]
0x768D69 cmp dword ptr [ecx+8], 0x3a98 ; element+8 < 15000 ?
0x768D72 cmp dword ptr [edx+8], 0 ; element+8 >= 0 ?
0x768D80 mov ecx, [eax+8]
0x768D89 cmp dword ptr [ecx*4 + 0x3be3e68], 0 ; ★ 怪物基址数组(0x03BE3E68)该索引处非 NULL?
0x768D9D cmp dword ptr [edx+0x10], 0 ; element+0x10 必须 != 0(有效性)
0x768DA7 mov ecx, [eax] ; element+0
0x768DAF cmp ecx, [0x1801104] ; ★ 与“当前目标实体”比较
要点:
- 循环上限 0x1900 = 6400 槽
- element+8 被直接用作怪物基址数组 0x03BE3E68 的下标([ecx*4 + 0x3be3e68])—— 说明每件地上物品都绑定一个实体(怪/NPC)
- element+0 与全局 [0x1801104](当前选中/目标实体)比较;相等时走“邻近拾取”分支(计算坐标 = [0x1801108]+0xC8 附近)
- 还访问全局位域 [ecx + 0x17eb718] & 0x80(ecx=element+0)—— 这是“对象是否存活/激活”的位表,证明 element+0 是对象 id
0x769BA0 push ebp
0x769BA4 mov [ebp-4], ecx ; this = 0x1801170
0x769BA7 cmp [ebp+8], 0
0x769BAB jge 0x769bba
0x769BAD cmp [ebp+8], 0x1900 ; 越界检查同样 6400
0x769BBA mov eax, [ebp+8] ; i
0x769BBD mov ecx, [ebp-4] ; this
0x769BC0 mov eax, [ecx + eax*4 + 4] ; return *(this+4 + i*4)
→ 容器布局:this+0 = 未知(vtable/计数),this+4 = 指向 6400 个指针的数组。
→ 元素 stride = 4 字节(指针),不是扁平结构体数组;元素本身是堆上的结构(指针)。
| 偏移 | 含义 | 证据 |
|---|---|---|
| +0x00 | 对象 id / 类型(取值 0x78/0x79/0x80/0x81/0x82…) | 多处 cmp [ecx], 0x78…;与 [0x1801104] 比较 |
| +0x08 | 怪物基址数组下标(指向 0x03BE3E68 实体) | cmp [ecx*4 + 0x3be3e68], 0 |
| +0x0C | 处理/占用标志(0/1 来回置) | mov [ecx+0xc], 1 / cmp [eax+0xc], 0 |
| +0x10 | 有效性标志(必须 != 0) | cmp [edx+0x10], 0 |
类型字段 +0 的 5 个取值(0x78/0x79/0x80/0x81/0x82)决定后续调用
0xddce90时用参数 7 还是 3(不同拾取/交互动作 id),属对象子类型区分。
| 基址 | 运行时值 | 是什么 | 数据结构 |
|---|---|---|---|
| 道具遍历基址 | 0x1801170(容器) | 地上掉落物 / 场景物品(世界对象) | 6400 槽指针数组,元素绑定怪物基址数组下标 |
| 背包列表基址 / 背包基址值 | 0x03B9DCE0 | 玩家背包物品(在身) | 你 遍历背包 用的大结构(+0x64 名称/+0x54 编号/+0xD4C 数量…) |
| 副本道具基址 | 0x9539F5 | 副本内道具 | 另一套(与 NPC/副本 CALL 同源 0x952DC8) |
三套互不相干,偏移不能混用。
' 道具遍历基址 已确认 = 0x1801170(模块基址 0x400000 时)
' 直接读容器首指针,再按 6400 槽遍历
数组指针 = 内存_读整数型 (窗口句柄, 道具遍历基址 + 4) ' *(this+4) -> 指针数组
.计次循环首 (6400, i)
元素指针 = 内存_读整数型 (窗口句柄, 数组指针 + (i - 1) × 4)
.如果 (元素指针 = 0)
到循环尾 ()
.如果结束
类型ID = 内存_读整数型 (窗口句柄, 元素指针 + 0) ' +0
实体下标 = 内存_读整数型 (窗口句柄, 元素指针 + 8) ' +8 -> 怪物基址数组下标
处理标志 = 内存_读整数型 (窗口句柄, 元素指针 + 0x0C) ' +0xC
有效标志 = 内存_读整数型 (窗口句柄, 元素指针 + 0x10) ' +0x10
写日志 (“槽” + 到文本 (i) + “ 类型=” + 到文本 (类型ID) + “ 实体下标=” + 到文本 (实体下标))
.计次循环尾
提醒:元素首指针(堆对象)后面字段可能还有名字/物品模板 id,但本循环没读到名字字符串——名字可能要走「物品模板表(按类型ID查)」。若你要读物品名字/编号,告诉我,我再去反查
0xddce90调用链或模板表基址。
道具遍历基址 验证为 地面掉落物/场景物品的世界对象容器(0x1801170,6400 槽指针数组),每件物品用 +0x8 绑定到怪物基址数组 0x03BE3E68 的某个实体。它不是背包物品,也不是副本道具—— 你的 遍历背包 那套大结构(0x03B9DCE0)对此不适用。