来源:程序集
遍历(易语言)。这份代码把游戏里几乎所有"列表型数据"都遍历了一遍。
结论先行:你之前用 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")=在线状态。
#队员基址 真实地址:去工程 常量.e.txt 查 #队员基址 的值(格式同 #怪物基址=62799464)。拿到后用 CE 读 #队员基址 处 8 个 4 字节指针,再逐个 +#组队_队员名称 验证能读到队友名字 → 队伍结构即坐实。枚举队员名称偏移,对单个队员对象从 y=0 起按 4 字节步进,配合「已知等级/血量/职业」反查偏移,即可拿到等级/血/队长标志等字段(之前 CE 因 AOBScan 卡死没做成,而这段代码已经给了数组入口,反而比盲目扫内存简单)。+0x08类型 / +0x0CID / +0x14发包值 / +0x18名字 与之前 CE 实测一字不差,邀请组队的 +0x14 用法无误。来源:程序集
发包(发包Call 前的内存_读*取数)、内存修改(补丁地址)。这两份把"角色/队员/物品"对象的字段布局彻底露了出来,正好补上第三节队伍数组的"对象内部"空白。
角色基址(自己角色)| 偏移 | 写法 | 含义 | 读取 |
|---|---|---|---|
+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。
队伍数组 = #队员基址(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 字节步进,对照已知等级/血量反查偏移)。
物品位置 = 从 背包首址 / 装备栏基址 / 仓库首址 + 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 |
所在位置(卖出用) |
玩家序号 = 内存读字节集 (#游戏基址 + 4 × (#组队_窗口出现 + 职业偏移) - 4 + 884, 2)
#游戏基址 + 4 × (#组队_窗口出现 + 职业偏移) - 4+0x374 (884) = 2 字节「对方玩家序号」(接受/拒绝组队封包 0x34 用)发包组人:
参数 = 内存_读字节集 (玩家位置 + 20, 2) ' +0x14,2 字节
封包 = 机器码到字节集 ("000000003000060001000000") + 参数
+20 = +0x14,读 2 字节 → 正是之前 CE 实测「邀请封包填 +0x14」的结论,两份代码互相印证,邀请组队用对象 +0x14(全服ID)无误。(发包打怪 也用 怪物位置 + 20 作怪物全服ID,说明 +0x14 是对象的「全服ID」字段,玩家/怪物同位置。)
内存修改 程序集绝大多数写的是 代码段地址(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对方玩家序号 |