热血江湖助手 · 分析资料站← 报告总目录助手主页
分区:15_助手版本迭代说明源文件:跨图读取加固_改动说明.md篇幅:8.9 KB收录:2026-10-19 17:10

跨图飞图崩溃加固 + 读取节奏改 10 秒 · 改动说明


一、问题定位

你说的「跨图飞图导致崩溃,应该是读取地图崩溃的」,在代码里挖到 4 处直接成因。

1. 跨图瞬间就去读地图内存(主因)

等待到达地图 原来的节奏是:

发包飞图 (...)              ← 刚把跨图封包发出去
刷新地图 ()                 ← 立刻读地图内存!游戏此时正在卸载旧地图
程序_延时 (1500)            ← 等 1.5 秒
已等 = 1500
循环: 每 500ms 刷新地图()   ← 还在读

发包的那一瞬间,游戏正在拆旧地图、建新地图,这时候读地图基址拿到的很可能是半成品指针。<br>1.5 秒也不够——客户端切图往往要好几秒。

2. 刷新坐标 没有空指针保护(直接崩点)

坐标基址 = 内存_读整数型 (全局_进程句柄, #坐标基址值)
当前坐标x = 到整数 (内存_读小数型 (全局_进程句柄, 坐标基址 + #x坐标偏移值))   ← 基址=0 时读 0+偏移

跨图时人物对象会被重建,坐标基址 读出来就是 0。原来的代码拿 0 + 偏移 去读内存,<br>而 等待离开原地 在跨图流程里正好会循环调它。

顺带纠正一处注释错误:等待离开原地 里写着「坐标读失败时会得到 (0,0),跳过本轮」——
这个前提在 刷新坐标 没保护的情况下根本不成立,读失败不是得到 0,是拿垃圾地址去读。

3. 发包飞图 的保护被注释掉了

人物基址 = 内存_读整数型 (全局_进程句柄, #人物基址值)
' .如果真 (人物基址 = 0)          ← 三段保护被整块注释掉
    ' 写日志 (...)
    ' 返回 ()
' .如果真结束

参数1 = 内存_读字节集 (全局_进程句柄, 人物基址 + 20, 2)   ← 基址=0 时读地址 20 → 空
封包内容 = 参数1 + { 0, 0, 1, 50, 8, 0 } + ...            ← 封包直接短 2 字节
发包Call (封包内容, 23)                                      ← 畸形包发给游戏

封包短一截,游戏按固定结构去解就是越界读——这类畸形包最容易把客户端打崩。

4. 取地图ID 的兜底会飞错图

.判断开始 (地图名称 = “泫渤派”)
    返回 (101)
... 各分支 ...
.默认
                                    ← 空着,什么都不做
.判断结束
返回 (101)                          ← 任何没登记的地图名,一律当成泫渤派

_飞地图按钮_被单击 直接把组合框里的文字喂给它,名字打错一个字就会飞去泫渤派。


二、改了什么

① 先等 10 秒再读地图(你的要求)

等待到达地图 的开头,把「先读后等」改成「先等后读」,等待时间 1.5 秒 → 10 秒:

    .局部变量 首次等待, 整数型

    ' 【跨图保护】发包瞬间游戏正在卸载旧地图,这时候读地图内存是崩溃诱因
    ' 先给游戏 10 秒把新地图加载完,再开始读地图
    首次等待 = 10000
    .如果真 (超时毫秒 < 首次等待)     ← 超时比 10 秒还短时自动收窄,不会白等
        首次等待 = 超时毫秒
    .如果真结束
    程序_延时 (首次等待)
    已等 = 首次等待
    刷新地图 ()
    .判断循环首 (已等 < 超时毫秒)
        ... 之后仍每 500 毫秒读一次,不变
调用点传的是 超时毫秒 = 30000,所以节奏变成:等 10 秒 → 之后每 0.5 秒读一次,最多再读 20 秒。
想再保守就改 首次等待 = 10000 这个数字(单位毫秒)。

② 刷新坐标 加空指针保护

坐标基址 = 内存_读整数型 (全局_进程句柄, #坐标基址值)
' 【跨图保护】基址为 0 说明人物对象还没建好,绝不能拿 0+偏移 去读内存
.如果真 (坐标基址 = 0)
    当前坐标x = 0
    当前坐标y = 0
    返回 ()
.如果真结束
当前坐标x = ...

为什么失败时置 (0,0) 而不是直接返回:等待离开原地 依赖「读到 (0,0) 就跳过本轮」这个判断,<br>置 0 能让它按原设计跳过,不用改调用方。

③ 发包飞图 三道闸

' 【跨图保护】地图ID 无效就别发包了,历史上这里发过目标=0 的畸形包
.如果真 (地图ID ≤ 0)
    写日志 (... “【发包飞图】地图ID无效(” + ... + “),跳过本次飞图” ...)
    返回 ()
.如果真结束
人物基址 = 内存_读整数型 (全局_进程句柄, #人物基址值)
' 【跨图保护】原代码这里的三行保护被注释掉了:人物基址=0 时仍会去读 0+20
.如果真 (人物基址 = 0)
    写日志 (... “【发包飞图】人物基址读取失败,跳过本次飞图” ...)
    返回 ()
.如果真结束
参数1 = 内存_读字节集 (全局_进程句柄, 人物基址 + 20, 2)
' 读到的标识必须是完整的 2 字节,否则后面拼出来的封包会短一截
.如果真 (取字节集长度 (参数1) ≠ 2)
    写日志 (... “【发包飞图】人物标识读取失败,跳过本次飞图” ...)
    返回 ()
.如果真结束
封包内容 = ...

放在 发包飞图 里而不是各个调用点:这个函数有 20 多个调用点(回补、环龙、修炼传送、一键回城…),<br>闸设在函数里,所有调用点一次全受保护。

④ 取地图ID 不再兜底

.默认
    ' 【修复】原写法这里空着,末尾兜底 返回(101)=泫渤派,任何没登记的地图名都会飞错图
    返回 (0)
.判断结束

返回 0 = 未知地图。(刷新地图 里那 14 处调用传的都是硬编码的已知名字,不受影响。)

⑤ 飞地图按钮:名字不认识就别发包

.子程序 _飞地图按钮_被单击
    .局部变量 目标ID, 整数型

    目标ID = 取地图ID (飞地图组合框.内容)
    ' 组合框里填的名字必须能在 取地图ID 的表里查到,查不到就不要发包
    .如果真 (目标ID ≤ 0)
        写日志 (... “【飞地图】无法识别地图名:” + 飞地图组合框.内容 + “,请检查是否有错别字” ...)
        返回 ()
    .如果真结束
    发包飞图 (目标ID)

以前打错字是静默飞去泫渤派,现在会明确告诉你哪个名字不认识。


三、验证结果

检查项结果
t2e 回转生成 .e0 错误 / 0 警告
分层保真 — 本次改动的 5 个子程序L1 代码级 0 差异;L2 注释 0 丢失
分层保真 — 全工程 466 个子程序L1 代码级 0 差异
静态自检声明区完整、5 个子程序块结构配平、关键语句齐全
二进制确认首次等待 4 处、【跨图保护】 4 处、三条新日志各 1 处
项目.etprj已还原(t2e 会改写它)
本次新增的注释全部写成独立行,所以 L2 一条都没丢(行尾注释会被 e2txt 吞掉,这是已知损耗)。

四、还发现但没动的两个点

1. 取地图名称 有同样的兜底,而且更隐蔽

.默认
                                      ← 空
.判断结束
返回 (“泫渤派”)                       ← 认不出的地图ID,一律报成泫渤派

影响面:

要不要一起改?(改成返回 “未知地图” 之类,但这会改变界面显示,所以先问你一声。)

2. 两张地图表对不上

ID取地图ID 表取地图名称 表
26100燕妃家燕妃阁
26200剑皇圣地剑皇圣地 ✓

26100 在两张表里名字不一致。反查方向(ID→名)和正查方向(名→ID)都能用,<br>但如果你哪天想用「取地图名称」的结果去反查 ID,就会查不到。<br>另外 取地图ID 表里没有 30000/30100/30200/30300(北海/南林/东陵/虎峡谷修炼地区),<br>而 取地图名称 表里有——所以这四个修炼地区的名字不能拿去 取地图ID。<br>如果挂机配置里允许填这些名字,会走到现在的「返回 0、不发包、写日志」这条路。


五、提醒

本页由 Markdown 报告自动转排;如需原始 md,可在原会话工作区取用。