← 返回总索引遍历代码 · 数组结构分析 2026-09-16-13-19-49\遍历代码_数组结构分析.md
分类:内存逆向与基址定位来源会话:2026-09-16原件:2026-09-16-13-19-49\遍历代码_数组结构分析.md

遍历代码 · 数组结构分析

来源:程序集 遍历(易语言)。这份代码把游戏里几乎所有"列表型数据"都遍历了一遍。
结论先行:你之前用 CE 找的「队伍基址」就是 #队员基址,是一个 8 元素指针数组,每个元素指向一个"队员对象",对象里含名字(偏移 #组队_队员名称)。
所有基址都是常量(如 #怪物基址),真实数值在工程的 常量.e.txt 里;#怪物基址 已知 = 62799464 = 0x03BE3E68


一、总览表

数组 基址来源 元素数 步长 元素解引用 关键字段
怪物/玩家/物品对象数组 #怪物基址(直接地址) #怪数组大小(读整数) 4 读出的 4 字节 = 对象指针 怪首地址 +8类型 / +#玩家名称名字 / +#怪物IDID / +#玩家等级等级 / 坐标 / 距离
队伍(队友)数组 #队员基址(直接地址) 固定 8 4 读出的 4 字节 = 队员对象指针 队员位置 +#组队_队员名称 名字
背包数组 #背包基址(读指针:内存_读整数型(#背包基址) 变量 背包格子(有的循环用 36 / 66) 4 +#背包位置 读物品指针 名字/数量/编号/类型/合成/强化/石…
任务包 #背包基址 + 8(读指针) 背包格子 4 +#背包位置 同上
快捷栏 #快捷栏基址(读指针) 10 4 +#背包位置 图标名/编号
技能栏(攻击) #技能栏基址(读指针) 28 4 +#背包位置 技能名/编号/等级/已学
技能栏(辅助) #辅助栏基址(读指针) 28 4 +#背包位置 同上
仓库(个人/综合) 取个人仓库基址() / 取综合仓库基址()(读指针) 60 4 +#背包位置 同背包
状态栏 #状态栏基址(读指针);异口同声处用 #快捷栏基址 - 4 30 4 +#背包位置 图标名
聊天/喊话 #游戏基址 + 4×#聊天_聊天窗口(读指针) 50 / 100 4 +#聊天_喊话位置 读消息指针 频道/内容
结婚窗口 #游戏基址 + 4×#结婚窗口 + 职业偏移 直接读 情侣名/在线状态

通用规律:所有"对象型"数组(怪物/背包/队友/快捷/技能/仓库)都是
基址 + 索引×4(某些从 (索引-1)×4 起)读出 4 字节 指针,再 指针 + 字段偏移 读具体字段。
这正是之前 CE 验证过的对象数组模式(#怪物基址 里每个槽位存对象指针)。


二、核心对象数组(#怪物基址)—— 全部遍历的基础

怪数量 = 内存_读整数型 (全局_进程句柄, #怪数组大小)
.计次循环首 (怪数量, x)
    怪首地址 = 内存_读整数型 (全局_进程句柄, x × 4 + #怪物基址)   ' 读出 对象指针
    .如果真 (内存_读整数型 (全局_进程句柄, 怪首地址 + 8) = #玩家标识)  ' 类型判定
        玩家名称 = 取怪物原始名称 (内存_读文本型 (全局_进程句柄, 怪首地址 + #玩家名称, 30))

字段偏移(与之前 CE 实测完全吻合)

偏移 常量名 含义 读取方式
+0x08 (代码里写魔法数 8 对象类型:#玩家标识=玩家 / #怪物标识=怪物NPC 整数
+0x0C #怪物ID 对象 ID(玩家也用此字段;遍历玩家_取指定玩家信息 里直接写 +12 整数
+0x14 (发包值,未在遍历里用,之前 CE 确认) 邀请组队封包填的就是这个 整数
+0x18 #玩家名称 / #怪名称 BIG5 名字(内联串) 文本
+#玩家正邪 正邪(取玩家正邪) 整数
+#玩家职业 职业(取玩家职业) 整数
+#玩家门派 门派 文本
+#玩家等级 / +#怪等级 等级 整数
+#玩家当前X1 当前 X 坐标(float) 小数
+#玩家当前X1 + 8 当前 Y 坐标(float) 小数
+#玩家目标X 目标 X 坐标(float) 小数
+#玩家目标X + 8 目标 Y 坐标(float) 小数
+#怪距离 距离(float) 小数
+#怪编号 怪物/NPC 编号 整数

注意:x × 4 + #怪物基址 中 x 从 1 起 → 索引 0(基址处)通常空或存元数据;玩家集中在数组后段(之前 CE 实测槽位 8000+)。


三、队伍数组(你之前一直在找的!)—— #队员基址

.子程序 遍历队员_取所有队友, 整数型, , 返回队友人数
.参数 所有队友名单, 文本型, 参考 可空
.局部变量 队员位置, 整数型
.局部变量 x, 整数型
.局部变量 队员名称, 文本型
.局部变量 队员人数, 整数型

.计次循环首 (8, x)                                  ' ★ 最多 8 个队员
    队员位置 = 内存_读整数型 (全局_进程句柄, (x - 1) × 4 + #队员基址)  ' 读出 队员对象指针
    .如果真 (队员位置 ≤ 0)
        到循环尾 ()
    .如果真结束
    队员名称 = 内存_读文本型 (全局_进程句柄, 队员位置 + #组队_队员名称, 20)  ' 读出 名字
    .如果真 (队员名称 = “”)
        到循环尾 ()
    .如果真结束
    队员人数 = 队员人数 + 1
    ...
.计次循环尾 ()
所有队友名单 = 队员名单
返回 (队员人数)

结构结论
- 基址 #队员基址 是一个静态地址(和 #怪物基址 同性质,在主模块数据区)。
- 它是一个 8 元素、步长 4 的指针数组#队员基址 + (x-1)*4 读出 4 字节 = 队员对象指针。
- 队员对象里目前代码只用了名字字段 +#组队_队员名称(读 20 字节)。
- 队伍人数 = 循环里「指针>0 且 名字非空」的计数(最多 8)。
- 第 1 个元素(x=1,即 #队员基址 本身)通常是自己/队长,可结合此点判断谁是队长。

作者自己在暴力找名字偏移枚举队员名称偏移 子程序只取 #队员基址 第 1 个元素,从 y=100 起步每次 +100 扫名字出现的位置:

队员位置 = 内存_读整数型 (全局_进程句柄, #队员基址)
.计次循环首 (1000, y)
    y = y + 100
    队员名称 = 内存_读文本型 (全局_进程句柄, 队员位置 + y)
    .如果真 (寻找文本 (“/” + 所有玩家, “/” + 队员名称 + “/”, , 假) ≠ -1)
        输出调试文本 (队员名称 + “   队员名称偏移:” + 到文本 (y))
        返回 (y)

→ 说明 #组队_队员名称 的真实偏移在 100~1100 之间,是用这种笨办法试出来的。

目前的缺口(你之前想找的「队长名 / 队伍等级 / 队员血」这里都没暴露)
代码只读了"名字"。要拿到队长标志 / 等级 / 当前血 / 职业等,需要继续枚举队员对象的字段——把上面那个 枚举队员名称偏移 改造一下,对每个队员对象扫 y = 0,4,8,… 找:
- 整型字段(等级、血、标志位)→ 用 CE 或 内存_读整数型(队员位置 + y) 看哪个值等于已知等级/血量;
- 队长标志 → 看 8 个队员里哪个对象在某偏移是 1 而其他是 0
- 名字固定先在 #组队_队员名称,以此为锚点向前后推字段。


四、其他数组(速查)

背包 / 任务包 / 仓库(物品对象,结构相同)

背包首址 = 内存_读整数型 (全局_进程句柄, #背包基址)        ' 先读指针
背包位置 = 内存_读整数型 (全局_进程句柄, 背包首址 + 4×(x-1) + #背包位置)  ' 物品指针

物品字段:#背包名称(名) / #背包数量(数量) / #背包编号(编号) / #物品类型(1衣2护手3武器4鞋5内甲6项链7耳环8戒指9披风10石11符12强化19属性) / #合成次数 / #强化次数 / #禁止状态(=1为装备) / #防具等级 / #防具正邪 / #防具职业 / #石头类型 / #石头大小 / #属性类型 / #百宝药量
- 背包格数变量 背包格子;清理/空格类固定 36 格;保存备注类 66 格;仓库 60 格。

快捷栏(#快捷栏基址,10 格)

快捷栏首址 + 4×(x-1) + #背包位置 → 图标对象;字段 #背包名称(图标名) / #背包编号(编号)。

技能栏(#技能栏基址 攻击 / #辅助栏基址 辅助,各 28 格)

技能首址 + 4×(x-1) + #背包位置 → 技能对象;字段 #背包名称(技能名) / #背包编号(编号) / #技能等级(等级) / #技能已学(字节,=1已学)。
注意跳过 /1/5/9/13/17/21/25/ 这些空位序号。

聊天 / 喊话(#游戏基址 + 4×#聊天_聊天窗口,50~100 条)

喊话首址 + (x-1)×4 + #聊天_喊话位置(520) → 消息对象;字段 #聊天_喊话频道(876) / #聊天_喊话内容(556,读 56~60 字节)。
频道号含义:8=异口同声、9=系统提示、13/10/20=被杀提示。

结婚窗口(#游戏基址 + 4×#结婚窗口 + 职业偏移)

直接读:十六到十("3100740")=情侣名;十六到十("330")=在线状态。


五、下一步建议

  1. 确认 #队员基址 真实地址:去工程 常量.e.txt#队员基址 的值(格式同 #怪物基址=62799464)。拿到后用 CE 读 #队员基址 处 8 个 4 字节指针,再逐个 +#组队_队员名称 验证能读到队友名字 → 队伍结构即坐实。
  2. 补充队伍字段:改造 枚举队员名称偏移,对单个队员对象从 y=0 起按 4 字节步进,配合「已知等级/血量/职业」反查偏移,即可拿到等级/血/队长标志等字段(之前 CE 因 AOBScan 卡死没做成,而这段代码已经给了数组入口,反而比盲目扫内存简单)。
  3. 对象数组字段已完全验证+0x08类型 / +0x0CID / +0x14发包值 / +0x18名字 与之前 CE 实测一字不差,邀请组队的 +0x14 用法无误。

六、补充:发包 / 内存修改 里暴露的对象结构

来源:程序集 发包(发包Call 前的 内存_读* 取数)、内存修改(补丁地址)。这两份把"角色/队员/物品"对象的字段布局彻底露了出来,正好补上第三节队伍数组的"对象内部"空白。

6.1 角色基址 角色基址(自己角色)

偏移 写法 含义 读取
+0x14 (20) 角色基址 + 20 人物全服ID(2 字节,几乎所有发包的首字段) 字节集 2
+#玩家当前X1 当前坐标 X/Y/Z(12 字节 = 3 个 float) 字节集 12
+#玩家当前X1 + 8 当前 Y 坐标 float
+#玩家职业 职业(1刀2剑3枪4弓5医6刺7乐8韩9谭10格) 整数
+#玩家称号 称号显示标志 整数
+#对话的NPCID + 4(读 4 字节) 当前对话 NPC 编号 字节集 4
#人物名称 + #人物金钱 + 8 金钱(长整数 8 字节) 长整数
#人物名称 + 242 + 40 加点数据 字节集 40
#当前血量 + 112 当前血量 字节型
#地图ID基址 当前地图 ID 整数
#人物历练 历练值 整数

几乎每个发包(发包打坐/移动地图/轻功/辅助/打怪/送花/接花/学习技能/复活)都以 内存_读字节集(角色基址 + 20, 2) 作为封包第 1 字段 = 人物全服ID。

6.2 ★ 队员对象结构(队伍数组坐实)

队伍数组 = #队员基址(8 元素指针数组,见第三节)。本批 发包转移队长 / 发包踢出队友 把队员对象字段露出来了:

' 发包转移队长
参数 = 内存_读字节集 (队员位置 + #组队_队员名称 - 4, 2)   ' 队员序号/ID(2 字节)
参数 = 参数 + 内存_读字节集 (队员位置 + #组队_队员名称, 12) ' 名字(12 字节)
' 发包踢出队友
参数 = 内存_读字节集 (队员位置 + 十六到十("208"), 2)   ' = #组队_队员名称 - 4
参数 = 参数 + 内存_读字节集 (队员位置 + 十六到十("20C"), 12) ' = #组队_队员名称

#组队_队员名称 = 0x20C (524);其前 4 字节(+0x208)是 2 字节的队员序号/ID(转移队长、踢人封包都用它拼接成 14 字节参数)。

队员对象布局(目前已知):
| 偏移 | 含义 |
|---|---|
| +0x208 | 队员序号/ID(2 字节) |
| +0x20C | 队员名字(BIG5,常用 12 字节;遍历队员_取所有队友 读 20 字节) |

之前你想找的「队长名 / 队伍等级 / 队员血」:名字已确认为 +0x20C;队长看第 1 个元素(x=1,#队员基址 本身通常是自己/队长);等级/血需继续在队员对象里枚举(用 枚举队员名称偏移 同法,从 0 起按 4 字节步进,对照已知等级/血量反查偏移)。

6.3 物品对象(背包 / 装备栏 / 仓库 通用)

物品位置 = 从 背包首址 / 装备栏基址 / 仓库首址 + 4×格子 + #背包位置 读出的指针(物品结构三者通用)。本批新增偏移:

偏移 写法 含义
+#背包名称 名字
+#背包编号 + 4(读 4 字节) 物品编号
+#背包数量 数量
+#背包数量 + 8 物品类型(1 字节)
+0x50 (80) 物品位置 + 80, 8 参数(卖出 31007 版)
+0x54 (84) 物品位置 + 84, 8 参数(交易放物)
+0x5C (92) 物品位置 + 92, 8 参数(卖出/存入/取出/开店放物)
+0x6C (108) 物品位置 + 108, 8 捡物定位数据
+0xAC (172) 物品位置 + 172 正邪
+0xB4 (180) 物品位置 + 180 等级
+0x2FC (764) 物品位置 + 764, 1 所在位置(卖出用)

6.4 组队请求窗口结构

玩家序号 = 内存读字节集 (#游戏基址 + 4 × (#组队_窗口出现 + 职业偏移) - 4 + 884, 2)

6.5 ★ 关键确认:邀请目标 = 对象 +0x14

发包组人

参数 = 内存_读字节集 (玩家位置 + 20, 2)   ' +0x14,2 字节
封包 = 机器码到字节集 ("000000003000060001000000") + 参数

+20 = +0x14,读 2 字节 → 正是之前 CE 实测「邀请封包填 +0x14」的结论,两份代码互相印证,邀请组队用对象 +0x14(全服ID)无误。(发包打怪 也用 怪物位置 + 20 作怪物全服ID,说明 +0x14 是对象的「全服ID」字段,玩家/怪物同位置。)

6.6 内存修改里的"数据型"地址(非代码补丁)

内存修改 程序集绝大多数写的是 代码段地址(0x004xxxxx ~ 0x007xxxxx,按 #游戏版本 分支),属于功能开关补丁(无限视野 / 穿墙 / 快速攻击 / 技能优化 / 隐藏人怪建筑 / 关渲染 / 强制PK 等),不是数组结构。这里只列其中指向"数据标志"的常量(写 0/1 字节切换):

#隐藏人物 #隐藏怪物(写 0/1)、#无限视野#血战显名1/2#穿墙地址1/2#快速攻击1/2/3#定点远攻#隐藏建筑#关闭渲染#强制辅助#畅游穿墙#畅游打怪#普箭群攻

游戏版本常量(决定用哪套补丁 / 基址):#游戏版本 = 8002 / 8012 / 21016 / 21019 / 31007 / 31016 / 31017。


七、对象字段总表(合并三份代码)

基址常量 元素数 步长 对象指针 已知字段(偏移→含义)
#怪物基址 #怪数组大小 4 x×4+基址 +0x08类型 / +0x0C对象ID / +0x14全服ID(邀请目标) / +0x18名字 / +#玩家当前X1坐标 / +#怪当前血 / +#怪坐标x
#队员基址 8 4 (x-1)×4+基址 +0x208队员ID / +0x20C名字
#背包基址(指针) 背包格子(36/66) 4 首址+4×(x-1)+#背包位置 +#背包名称/#背包编号/#背包数量/#背包数量+8类型 / +0x50 +0x54 +0x5C参数 / +0x6C / +0xAC正邪 / +0xB4等级 / +0x2FC位置
#装备栏基址(指针) 4 同背包 同物品对象
取个人/综合仓库基址()(指针) 60 4 同背包 同物品对象
#技能栏基址/#辅助栏基址(指针) 28 4 首址+4×(x-1)+#背包位置 +#背包编号技能编号 / +#背包名称技能名
#快捷栏基址(指针) 10 4 同技能 +#背包名称图标名 / +#背包编号
#状态栏基址(指针) 30 4 同技能 +#背包名称
#游戏基址 + 4×#聊天_聊天窗口(指针) 50~100 4 +#聊天_喊话位置 +#聊天_喊话频道 / +#聊天_喊话内容
角色基址 1(自己) 直接 +0x14全服ID / +#玩家当前X1坐标 / +#玩家职业 / +#玩家称号 / +#对话的NPCID
#游戏基址 + 4×(#组队_窗口出现+职业偏移) - 4 1(组队窗) 直接 +0x374对方玩家序号