[M20] D2GS 纯客户端:接入 PvPGN + D2GS 1.13c 服务端联机 (1.13c Ground Truth)

把 diablo2-web 扩展出一个可登录 PvPGN、进入 D2GS 1.13c 服务端游戏的纯客户端模式。D2GS 由原版 D2Game.dll 驱动,服务端权威:战斗结算、掉落、刷怪、AI、物品生成全部在服务端;客户端只承担 D2Client 职责——按种子生成地图、渲染表现、本地移动预测、把 UI 操作转成上行包并以服务端回执为唯一真相。单机模式保留不动。

本里程碑先作规划,按 P0–P7 拆 issue 后再实施。

一、现状差距(代码审计结论)

类别 内容
✅ 可复用 MPQ / DCC / DC6 / DT1 / COF 解码,WebGL 渲染与图集(实体 / 飞弹 / 叠加层 / UI),UI 面板,字体,物品 Tooltip 与 i18n,自动地图,bnet.html 七阶段前置流程 UI(src/frontend/flow-state.ts)
⚠️ 需剥离 / 解耦 GameEngine、战斗、掉落、刷怪、AI、词缀生成在 D2GS 下都是服务端职责;src/scene/act-scene.ts(10,753 行)与它们直接耦合
❌ 不可复用 src/net/(锁步 P2P + relay):其前提“D2 联机不是服务端权威”(lockstep.ts 顶部注释)与 D2GS 模型相反
🔴 关键路径 D2GS 只下发地图种子(0x03 D2GS_LOADACT:act / mapId / area)与房间坐标(0x07 / 0x08),客户端必须用同一种子逐位一致生成 DRLG。现 D2MOO 1:1 移植仅覆盖 Act I(DRLG_AllocDrlg 对其他幕直接 throw),且只用于离线烘焙;运行时从固定烘焙副本中挑选(WORLD_VARIANT_SEED)
🆕 全新 BNCS / MCP / D2GS 三层协议,D2GS 下行 Huffman 解压,约 150 种下行包 / 约 100 种上行包,物品位流,客户端单位表与移动预测,D2Client 侧技能 / 飞弹表现函数,WS↔TCP 桥

二、连接链路

浏览器 ⇄ WS↔TCP 桥 ⇄ bnetd(BNCS,TCP 6112)→ d2cs(MCP,TCP 6113)→ D2GS(TCP 4000)。d2dbs 是服务端内部组件,客户端不接触。D2GS 只接受经 MCP_JOINGAME 授权(game hash + token)的连接,不能绕过 realm 直连。

三、架构选型

  • 运行形态:推荐「浏览器 + 自建 WS↔TCP 桥」(只透传字节、目标白名单、防开放代理);备选 Electron / Tauri 原生 TCP(免桥,但失去纯 Web 部署)。另建 Node headless 协议栈,用于抓包回放与在线冒烟(与浏览器共用同一份协议代码)。
  • 场景架构:推荐抽出只读 WorldView 接口,单机 GameEngine 与联机 ClientWorld 各自实现,渲染与 UI 只依赖接口。不推荐在 act-scene.ts 内加 if (net) 分支(耦合继续加深),也不推荐另写一套联机场景(渲染代码重复两份)。

四、阶段划分

P0 联机基建与测试床(#508)

  • 自建测试服:PvPGN(bnetd + d2cs + d2dbs)+ D2GS 1.13c(Windows 程序,可 Wine;需原版 1.13c 文件)。
  • WS↔TCP 桥;传输抽象(Node 直连 TCP / 浏览器走桥)。
  • 抓包回放框架:录制原版 1.13c Game.exe ↔ D2GS 会话,作为解析器黄金用例。

P1 登录链路(BNCS + MCP)(#509)

  • BNCS:SID_AUTH_INFO (0x50)、SID_PING (0x25)、SID_AUTH_CHECK (0x51)(CheckRevision + D2 / LoD 双 CD-Key 哈希)、SID_LOGONRESPONSE2 (0x3A)、SID_QUERYREALMS2 (0x40)、SID_LOGONREALMEX (0x3E)。
  • MCP:MCP_STARTUP (0x01)、MCP_CHARLIST2 (0x19)、MCP_CHARCREATE (0x02)、MCP_CHARLOGON (0x07)、MCP_CREATEGAME (0x03)、MCP_JOINGAME (0x04)、MCP_GAMELIST (0x05)。
  • 接线 bnet_connecting / bnet_login / char_create / char_select / bnet_lobby 各阶段;选角画面按 statstring 渲染角色外观。

P2 D2GS 协议核心(#510)

  • 握手:0xAF(压缩协商)→ 上行 0x68 D2GS_GAMELOGON → 0x6B 进入游戏环境;0x6D / 0x8F 心跳。
  • 下行包固定码表 Huffman 解压 + 长度头分帧;定长 / 变长包长表以 1.13c 客户端二进制为准。
  • 包分发框架;未知包 ID / 长度不符一律 fail-fast,并落盘原始字节。

P3 客户端世界(Act I)→ 档 A(#511)

  • DRLG 运行时化:收到 0x03 后在 Worker 中按种子与难度(取自 0x01)生成当前幕;0x07 / 0x08 增删房间;房间瓦片改用运行时 DT1 图集(替代按场景烘焙的页)。
  • 单位表:0x59 / 0x5B / 0x5C 玩家、0xAC 怪物与 NPC、0x51 物件、0x09 关卡传送点、0x0A 移除。
  • 移动:本地玩家预测(移植 D2Common 寻路,D2MOO Path/)+ 0x15 纠偏;他人 0x0F / 0x10、怪物 0x67 / 0x68 / 0x69 / 0x6D 插值与动作模式同步(复用 animdata / anim-dispatcher)。
  • 外观:0x59 + 已装备物品 → COF 组件合成(复用 char-appearance.ts);聊天(下行 0x26 / 上行 0x15)。

P4 物品与界面交互(#512)

  • 0x9C(世界)/ 0x9D(持有)物品位流解码,对标 D2Common 物品位流编解码(D2MOO Items.cpp,需核对 1.13c 差异),映射到现有 Item 以复用 Tooltip。
  • 背包 / 仓库 / 方块 / 腰带 / 光标持物改为「上行请求 → 服务端 0x9D 回执」:0x16 拾取、0x17 丢弃、0x18–0x1F 容器与装备栏移动、0x20 使用、0x23–0x26 腰带、0x50 丢金币。
  • NPC:0x13 交互、0x2F / 0x30 开关对话、0x32–0x35 买 / 卖 / 鉴定 / 修理;传送点 0x49;回城门与关卡入口交互。
  • 属性与技能:0x1D–0x20 属性、0x94 技能等级;加点 0x3A / 0x3B、选技能 0x3C。

P5 战斗表现与状态 → 档 B(#513)

  • 0x4C / 0x4D 单位施法 → 执行 Skills.txt cltstfunc / cltdofunc 对应的客户端技能函数与 cltmissile*;客户端飞弹按 Missiles.txt pCltDoFunc / pCltHitFunc / CltSubMissile* / CltHitSubMissile* 演算(纯表现,伤害只认服务端);复用现有 missile / overlay 图集与渲染。
  • 0x0C 受击、死亡与尸体模式、0x95 生命法力、0xA7–0xA9 状态(含状态属性列表)与光环 / 诅咒叠加层。
  • 上行 0x05–0x11 左 / 右键技能(坐标 / 单位 / shift 变体)。

P6 DRLG 第二至五幕(可与 P3–P5 并行)(#514)

  • 前置:#496 #497 #498 #499(第二至五幕户外关卡)、#502(瓦片变体规则)。另需:DRLG_AllocDrlg 第二、三幕种子滚动(staff / boss tomb、jungle interlink)、全部 maze / preset 关卡走移植路径、运行时(非烘焙)生成;tools/d2moo-oracle 扩展到全部 136 关卡。
  • 瓦片变体规则已定案(单机与联机统一,见 #502):两种模式不搞例外,一律使用 D2 原版选法(DRLGROOMTILE_GetTileCache:按房间种子 pDrlgRoom.pSeed + D2CMP_10081_GetTileRarity 抽取,drlg-roomtile.ts 已实现),DRLG 关卡不再用 SplitMix64 pickVariant 二次重抽,从根本上消除变体间子格碰撞差异导致的预测分叉。

P7 五幕完整对等 → 档 C(#515)

  • 各幕特殊关卡与物件表现(塔拉夏古墓、秘法圣殿、崔凡克、混沌避难所、0xA4 巴尔波次、远古人等;服务端权威,客户端只做表现)。
  • 任务状态(0x28 / 0x29)→ NPC 对话与任务日志;传送点菜单 0x63。
  • 组队(0x8B–0x8D、0x7F / 0x90)、交易(0x77–0x79)、雇佣兵(0x81、0x9E–0xA0)。
  • 音效(0x2C、技能 / NPC 语音);断线、超时与重连;大量单位时的性能。

五、验收

  • 档 A(技术验证):两名玩家同进罗格营地,互相可见走动与聊天;在线 oracle——每个 0x07 房间坐标 100% 命中本地 DRLG 房间;headless 回放全部抓包零解析错误;进房 60 分钟不断线。
  • 档 B(Act I 可玩):Act I 捡 / 丢 / 穿脱 / 买卖 / 存取 / 鉴定 / 传送 / 打怪全流程,上行包字节与原版 Game.exe 同操作抓包一致;七职业技能的施法 / 飞弹 / 命中表现与原版同屏录像逐帧对齐(抽帧比对)。
  • 档 C(五幕完整):tools/d2moo-oracle + scripts/drlg-diff.ts 对 136 关卡 × 多种子 × 3 难度全阶段 0 差异,且在线 0x07 命中率 100%;本客户端与原版 Game.exe 同局通关五幕,全程无断线、无解析错误、无位置分叉。
  • 全程:npm run typecheck 0 errors、vitest 全量通过、单机模式 136 关卡 headless 审计零回归。

六、粗估人力(1 名熟练工程师全职,单位:人周)

阶段 人周 累计
P0 基建与测试床 1–2
P1 登录链路 2–3
P2 D2GS 协议核心 2–3
P3 客户端世界(Act I) 4–6 档 A ≈ 9–14 周(约 2–3.5 人月)
P4 物品与界面交互 5–7
P5 战斗表现与状态 6–9 档 B ≈ 20–30 周(约 5–7.5 人月)
P6 DRLG 第二至五幕 6–10 可与 P3–P5 并行
P7 五幕完整对等 14–22 档 C ≈ 40–62 周(约 10–15.5 人月)

agent 驱动可大幅压缩编码时间;真正的瓶颈在对真实 D2GS 的抓包比对与在线验证。

七、风险

  1. DRLG 逐位一致(最大风险):任何分叉都会导致碰撞与位置和服务端不一致。缓解:D2MOO oracle 逐阶段比对 + D2GS 在线 0x07 oracle(后者代表真实 1.13c D2Common.dll)。
  2. 客户端技能 / 飞弹函数:D2Client 侧逻辑以 1.13c 反汇编为准;D2MOO 以 D2Common / D2Game 为主,客户端函数覆盖度待确认,是工作量最大的不确定项。
  3. Warden(0xAE):若服务端启用需单独评估;测试服默认关闭。
  4. 版本校验:CheckRevision 需要真实 1.13c 客户端文件,或在测试服放宽 PvPGN 版本检查。
  5. WS↔TCP 桥安全:生产部署需鉴权、限流与目标白名单。
  6. act-scene.ts 解耦回归:拆分期间单机模式须保持全量测试通过。

八、不在范围(YAGNI)

Battle.net 聊天频道 / 好友 / 天梯榜 UI、1.14+ 与 D2R 兼容、D2GS 服务端改造、Warden 模块执行、任何外挂式功能(如全图显示)。

约束

严格以 1.13c 反汇编(D2Client.dll / D2Common.dll / D2Net.dll)与官方 MPQ 数据表为准绳,禁止编造规则与静默降级兜底;客户端不做任何权威结算;所有改动遵守 worktree 隔离规则。

No due date
100% Completed