Files
IC.Rebuild/diagnostics/2026-10-12-stage3-m0-m4.md

5.9 KiB
Raw Permalink Blame History

阶段 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 0x4810F1
  • 0x4810E9: xor r9d, r9d; xor r8d, r8d; call 0x1CFD140
  • 0x4810F1: mov al, 1; add rsp, 28h; ret

缺陷:0x4810E0 在调用 0x1CFD140 后无条件执行 mov al, 1,即使 ReceiverCallback* 为空或底层 ZoneUp 返回 0(断线/未就绪),也会恒定返回 1,把发送失败伪装成成功。

2.2 已核实的原生发送链(保留真实返回值)

  1. Framework.Instance()->NetworkModuleProxy(偏移 +0x1678,对应原生 getter 0xD28F0)。
  2. ReceiverCallback* = *(nint*)((nint)networkModuleProxy + 0x08)。
  3. ReceiverSendPacket(RVA 0x1CFD140,唯一签名 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(RVA 0x1D03040,唯一签名 48 83 EC 28 48 8B 89 98 00 00 00 ...,1 处匹配)。
  4. ZoneClient.SendPacket(0x1D03040):
    • 检查 *(nint*)(zoneClient + 0x98) != 0,调用 ZoneUp(0x1D718C0),并将 ZoneUp 的真实 byte 返回码原样返回给调用方。
  5. ZoneUp(0x1D718C0)在 0x1D7198C 读取发送缓冲区契约:
    • +0x00: uint Opcode
    • +0x08: ulong TotalSize(payload.Length + 0x10,即含 16 字节 IPC 头)
    • +0x20: byte[payload.Length] 业务载荷
    • 原生调用方(如 0xB8A7AA UP_EventStart、0xB352F0 UP_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。