D:/AI空间/rxjh/旧版本更新_修复后.e旧版本更新_修复后.bak_跨图加固前_20261006_1712.e_patch/form.e.txt.bak_跨图加固前你说的「跨图飞图导致崩溃,应该是读取地图崩溃的」,在代码里挖到 4 处直接成因。
等待到达地图 原来的节奏是:
发包飞图 (...) ← 刚把跨图封包发出去
刷新地图 () ← 立刻读地图内存!游戏此时正在卸载旧地图
程序_延时 (1500) ← 等 1.5 秒
已等 = 1500
循环: 每 500ms 刷新地图() ← 还在读
发包的那一瞬间,游戏正在拆旧地图、建新地图,这时候读地图基址拿到的很可能是半成品指针。<br>1.5 秒也不够——客户端切图往往要好几秒。
刷新坐标 没有空指针保护(直接崩点)坐标基址 = 内存_读整数型 (全局_进程句柄, #坐标基址值)
当前坐标x = 到整数 (内存_读小数型 (全局_进程句柄, 坐标基址 + #x坐标偏移值)) ← 基址=0 时读 0+偏移
跨图时人物对象会被重建,坐标基址 读出来就是 0。原来的代码拿 0 + 偏移 去读内存,<br>而 等待离开原地 在跨图流程里正好会循环调它。
顺带纠正一处注释错误:等待离开原地里写着「坐标读失败时会得到 (0,0),跳过本轮」——
这个前提在刷新坐标没保护的情况下根本不成立,读失败不是得到 0,是拿垃圾地址去读。
发包飞图 的保护被注释掉了人物基址 = 内存_读整数型 (全局_进程句柄, #人物基址值)
' .如果真 (人物基址 = 0) ← 三段保护被整块注释掉
' 写日志 (...)
' 返回 ()
' .如果真结束
参数1 = 内存_读字节集 (全局_进程句柄, 人物基址 + 20, 2) ← 基址=0 时读地址 20 → 空
封包内容 = 参数1 + { 0, 0, 1, 50, 8, 0 } + ... ← 封包直接短 2 字节
发包Call (封包内容, 23) ← 畸形包发给游戏
封包短一截,游戏按固定结构去解就是越界读——这类畸形包最容易把客户端打崩。
取地图ID 的兜底会飞错图.判断开始 (地图名称 = “泫渤派”)
返回 (101)
... 各分支 ...
.默认
← 空着,什么都不做
.判断结束
返回 (101) ← 任何没登记的地图名,一律当成泫渤派
_飞地图按钮_被单击 直接把组合框里的文字喂给它,名字打错一个字就会飞去泫渤派。
等待到达地图 的开头,把「先读后等」改成「先等后读」,等待时间 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 回转生成 .e | 0 错误 / 0 警告 |
| 分层保真 — 本次改动的 5 个子程序 | L1 代码级 0 差异;L2 注释 0 丢失 |
| 分层保真 — 全工程 466 个子程序 | L1 代码级 0 差异 |
| 静态自检 | 声明区完整、5 个子程序块结构配平、关键语句齐全 |
| 二进制确认 | 首次等待 4 处、【跨图保护】 4 处、三条新日志各 1 处 |
项目.etprj | 已还原(t2e 会改写它) |
本次新增的注释全部写成独立行,所以 L2 一条都没丢(行尾注释会被 e2txt 吞掉,这是已知损耗)。
取地图名称 有同样的兜底,而且更隐蔽.默认
← 空
.判断结束
返回 (“泫渤派”) ← 认不出的地图ID,一律报成泫渤派
影响面:
_取当前坐标_被单击(第 78 行)、刷新地图 相关处会把未知地图显示成「泫渤派」;等待地图名 (...) 内部靠它比对,结果只是「继续等」,无害;要不要一起改?(改成返回 “未知地图” 之类,但这会改变界面显示,所以先问你一声。)
| ID | 取地图ID 表 | 取地图名称 表 |
|---|---|---|
| 26100 | 燕妃家 | 燕妃阁 |
| 26200 | 剑皇圣地 | 剑皇圣地 ✓ |
26100 在两张表里名字不一致。反查方向(ID→名)和正查方向(名→ID)都能用,<br>但如果你哪天想用「取地图名称」的结果去反查 ID,就会查不到。<br>另外 取地图ID 表里没有 30000/30100/30200/30300(北海/南林/东陵/虎峡谷修炼地区),<br>而 取地图名称 表里有——所以这四个修炼地区的名字不能拿去 取地图ID。<br>如果挂机配置里允许填这些名字,会走到现在的「返回 0、不发包、写日志」这条路。
首次等待 调到 6000~8000 折中。