5.9 KiB
阶段 3(0.1.15)M0 版本适配诊断与 M4 主动发送事务审计记录
日期:2026 年 10 月 12 日
客户端版本:国际服 2026.09.15.0000.0000
目标二进制:ffxiv_dx11.exe(SHA256 5BBC501DD5C7F22FD61A11D08C25356041D878DB7CD83203ADAE393E4DFACC44)
1. M0 统一版本适配清单与门控(Core/AdaptationManifest.cs)
针对全部 24 项保留功能及 M4 主动发送通道建立统一适配清单,按模块记录版本锁、SDK 偏移要求、原生签名及 RVA 证据:
诊断分类 (AdaptationIssue) |
判定条件 | 门控行为 (AdaptationManifest.ApplyGate) |
|---|---|---|
None |
实现存在、版本/偏移与签名全部匹配且未发生运行异常 | 清除不可用门控,允许启用 |
MissingImplementation |
模块尚未注册实现 | 保持不可用,拒绝启用 |
VersionMismatch |
VersionLocked == true 的模块(2, 7, 8, 14, 16, 36 及 M4 主动发送)在非 2026.09.15.0000.0000 客户端运行 |
自动关闭并标记不可用;非版本锁定模块(如 1, 3, 9, 10, 11, 37)与离线导入/导出不受影响 |
OffsetMismatch |
RequiresSdkOffsets == true(模块 8:依赖 Character.StatusManager 0x23B0 与 GameObject.EntityId 0x78)偏移不匹配 |
仅门控对应模块,不影响其他模块 |
SignatureMissing |
任一必需签名在当前客户端匹配数为 0 |
自动关闭并标记不可用,显示缺失签名名称与已核对 RVA |
MultipleMatches |
RequireUnique == true 的签名在 .text 段出现 >1 处匹配 |
拒绝盲挂,标记不可用并显示匹配数量(模块 15 0x8D6F30 显式声明 RequireUnique = false 使用首匹配包装函数) |
RuntimeFault |
运行期捕获异常并标记为 ModuleState.Failed |
保留具体异常详情并显示在 M0 诊断面板 |
本机 ffxiv_dx11.exe 签名扫描核验:21 条唯一签名(RequireUnique = true)在 .text 段均精确匹配 1 处;模块 15 包装签名匹配首处 0x8D6F30。
2. M4 原生主动发送链路反汇编核实
2.1 为什么不沿用旧 IC/NetManager.cs 的 0x4810E0 包装函数
反汇编 0x4810E0(旧签名 48 8B 49 08 48 85 C9 74 08 45 33 C9):
0x4810E0:mov rcx, [rcx+8](从NetworkModuleProxy*+0x08读取ReceiverCallback*)0x4810E4:test rcx, rcx; jz 0x4810F10x4810E9:xor r9d, r9d; xor r8d, r8d; call 0x1CFD1400x4810F1:mov al, 1; add rsp, 28h; ret
缺陷:0x4810E0 在调用 0x1CFD140 后无条件执行 mov al, 1,即使 ReceiverCallback* 为空或底层 ZoneUp 返回 0(断线/未就绪),也会恒定返回 1,把发送失败伪装成成功。
2.2 已核实的原生发送链(保留真实返回值)
Framework.Instance()->NetworkModuleProxy(偏移+0x1678,对应原生 getter0xD28F0)。ReceiverCallback* = *(nint*)((nint)networkModuleProxy + 0x08)。ReceiverSendPacket(RVA0x1CFD140,唯一签名48 83 EC 38 48 8B 89 70 0A 00 00 ...,1 处匹配):- 读取
ZoneClient* = *(nint*)(receiver + 0xA70);若为空直接xor al, al; ret返回0。 - 非空时以
priority = 0调用ZoneClient.SendPacket(RVA0x1D03040,唯一签名48 83 EC 28 48 8B 89 98 00 00 00 ...,1 处匹配)。
- 读取
ZoneClient.SendPacket(0x1D03040):- 检查
*(nint*)(zoneClient + 0x98) != 0,调用ZoneUp(0x1D718C0),并将ZoneUp的真实byte返回码原样返回给调用方。
- 检查
ZoneUp(0x1D718C0)在0x1D7198C读取发送缓冲区契约:+0x00:uint Opcode+0x08:ulong TotalSize(payload.Length + 0x10,即含 16 字节 IPC 头)+0x20:byte[payload.Length]业务载荷- 原生调用方(如
0xB8A7AAUP_EventStart、0xB352F0UP_ClientTrigger)均在栈上分配0xF60字节清零缓冲区传入;NetworkManager.SendNative同样采用至少0xF60字节的清零固定缓冲区调用0x1CFD140。
2.3 为什么不用合成 UP_PingReq 做主动发包测试
反汇编 0x1D02776(UP_PingReq 构造)与 0x1D022E1(DOWN_PingRsp 消费)证实:客户端在发送 UP_PingReq 与接收 DOWN_PingRsp 时会直接读写 NetworkModule 内部延迟测量状态([r14+0BCCh]、[r14+0C3Ch])。插件在外部合成 UP_PingReq 会干扰客户端原生 Ping 状态机。因此 M4 仅提供经过结构审计的业务包编码器(UP_ActionSend、UP_ClientTrigger、UP_EventStart、UP_EventFinish* 8 档、UP_EventAction* 8 档)。
3. M4 主动发送调度与请求响应事务(Core/ActivePacketSender.cs)
- 来源标注(
ActiveSendScope):ActivePacketDispatcher.Pump调用原生发送时开启线程本地ActiveSendScope,NetworkPipeline.Read自动将该次ZoneUp捕获标记为PacketOrigin.PluginActiveSend,与游戏自身触发的PacketOrigin.NativeZone明确区分。 - 主线程调度与重入防护:仅在
Framework.Update主线程调用PumpActiveSends;NetworkPipeline.InCallback与inReceiveCallback禁止在任何网络回调中递归调用Pump。 - 有界队列、限速与会话绑定:默认最大排队
32条、最小发送间隔100 ms、排队超时2000 ms、响应超时5000 ms。每次入队与发送均核对SendSession(角色在场、非切区、非占卜回放、Territory、LocalAddress、LocalEntityId一致);切区或会话变更自动取消未完成请求。 - 显式事务状态机:区分
Queued、SentNative、RejectedNative、Confirmed、TimedOut、Canceled。Cancel仅能取消仍处于Queued的请求,绝不把取消排队伪装成撤回已发送原生包。 - 请求响应关联:
UP_ActionSend自动等待同RequestId的DOWN_Effect(0x313),UP_EventStart/UP_EventFinish*支持等待同EventId的DOWN_EventStart/DOWN_EventFinish,并在收到响应时计算真实往返耗时RoundTripMs。