# `[ecx*4 + 0x03C35758]` 指针数组 —— 结构分析 > 分析时间:2026-09-25 | 目标:`D:\rxjh2025\client\YGOnline.exe` > 结论全部来自 exe 静态反汇编(游戏进程当时未运行),可直接指导易语言侧读写。 --- ## 1. 它是什么 ``` [ecx * 4 + 0x03C35758] ``` | 项 | 值 | 说明 | |---|---|---| | 基址 | `0x03C35758` = **63133528** | 就是一直在用的「全局大列表基址」 | | 索引 | `ecx` | 32 位无符号整数,来自另一层对象 | | 步长 | `4` | dword,元素是指针(4 字节) | | 元素含义 | **控件对象指针** | 即我们在池里读到的那个 `控件=1335282848` | | 段归属 | 本 exe 模块内 | ImageBase `0x400000` → RVA `0x3835758` | **本质:一个 15000 项的 dword 指针数组(控件对象池的索引表)。** CE 里 `[ecx*4+03C35758]` 展开出来的每个值,往下读 `+0x368` 就是控件名称 —— 与实测完全吻合。 --- ## 2. exe 里引用它的代码(铁证) 全文件去重后共 **401 处**直接引用 `0x03C35758`。典型三型: ### 型 A:索引来自「上层对象 +0xF8」 ```asm 0056DF42 mov eax, dword ptr [edx + 0xf8] ; 取索引 0056DF48 mov ecx, dword ptr [eax*4 + 0x3c35758]; 查池 ``` ### 型 B:判空 + 二次取(说明「元素为 0 = 空槽」) ```asm 0056EDC5 cmp dword ptr [eax*4 + 0x3c35758], 0 ; 元素为 0 ? 0056EDCD je 0x16e20e ; 是 → 跳过(空槽) 0056EDCF mov ecx, dword ptr [0x128485c] ; 全局游标 0056EDD5 mov edx, dword ptr [ecx*4 + 0x3c35758] ; 取控件对象 → 交给分派器 ``` 这条最关键:**`edx` 拿到的控件对象,紧接着被 `push 0 / push 0 / push 0x403` 送往某个分派函数。** 这跟 v3.2 记录的点击链路(`push 0 → push 命令ID → push 0x3F4`)是同一族,只是第三个参数不同。 ### 型 C:基址当常量存栈上 ```asm 0056C556 mov dword ptr [ebp - 8], 0x3c35758 ; 把数组基址当常量 0056C55D lea ebp, [eax + edx + 8] ; 算指针 ``` --- ## 3. 容量:是 15000,不是 14998 | 容量 | 数组范围 | |---|---| | 14998(旧用) | `0x3C35758 .. 0x3C441AC` | | **15000(建议)** | **`0x3C35758 .. 0x3C441B4`** | 搜索 exe 内的 4 字节常量:`14998` 出现 **0** 次,`15000` 出现 **0** 次(作为独立 imm32)。 更硬的旁证是几何关系: ``` 0x03C441BC − 0x03C35758 = 0x EA64 = 60004 字节 = 恰好 15001 个 dword ``` 数组若占满 **15000** 项,末尾正好落在 `0x3C441B4`,而 `0x3C441BC` 紧随其后 —— 说明那个「恒 0 的管理器指针」是**数组之后的下一个全局变量**,不是数组元素。旧代码用 14998 兜底没出错,但少扫了 2 格。 --- ## 4. `0x03C441BC` 的真实身份(顺带澄清) 它是**独立全局变量**,与数组无关。exe 里的用法: ```asm 00559AF4 cmp dword ptr [0x3c441bc], 0 ; 可为 0 00559B10 mov eax, dword ptr [0x3c441bc] 00559B15 cmp dword ptr [eax + 0x2a74], 2 ; 指向的对象有 +0x2A74 00559B24 mov ecx, dword ptr [0x3c441bc] 00559B2A mov edx, dword ptr [ecx + 0x2a70] ; 指向的对象有 +0x2A70 00559B30 movsx eax, byte ptr [edx + 0x122d020] ``` 结构线索:它指向一个含 **`+0x2A70` / `+0x2A74`** 的对象。 用户体检读到 `-1`(`0xFFFFFFFF`),两条路径都会解引用到一个低位地址 → 这就是之前 `开关内挂丸(31)` 秒崩的根。 --- ## 5. 给你的三个实用改进 ### ① 自洽校验(一次确认基址和步长对不对) 控件 `+0x0C` 存的就是它自己的槽号(此前活体实测:槽 1376 的控件 `+0x0C = 1376`)。 那么必然有 **`池[控件+0x0C] == 控件`**,这是免费的校验: ``` 池基址 = 63133528 控件 = 读(池基址 + 槽号×4) 索引 = 读(控件 + 12) 回读 = 读(池基址 + 索引×4) 若 回读 == 控件 → 基址、步长、索引语义 全部确认 ``` ### ② 空槽判定要跟游戏一致 游戏自己用 `cmp dword ptr [idx*4+TBL], 0` 判空。所以: `控件 = 0` 即空槽;`控件 = -1` 是哨兵(别当有效对象)。 **不要**再用 `控件 ≠ 65535` 这种猜的哨兵。 ### ③ 别再 15000 次单读 一次 `ReadProcessMemory` 读 `15000 × 4 = 60000` 字节,比 15000 次单读快几十倍。 精易模块若提供 `内存_读字节集 (句柄, 地址, 长度)`,就一次读完缓存成字节集再按 4 字节取;没有的话分 10 块各读 6000 字节。 --- ## 6. 待验证(需要游戏进程活着) 型 B 那段揭示了一条线索,值得下一次开游戏时确认: ``` edx = [ecx*4 + 0x3C35758] ' 取出控件对象 push 0 / push 0 / push 0x403 ' ecx = 索引(不是控件对象!) ``` **`ecx` 传的是索引,不是控件对象指针。** 我们此前几版都传「控件对象本身」,可能一直是错的。 下一次开游戏请顺手验证这行日志: ``` ecx(索引) = 读(控件 + 0x0C) edx(控件) = 读(池基址 + ecx×4) 日志应打印两者相等 ```