[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 物品位流编解码(D2MOOItems.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.txtcltstfunc / cltdofunc对应的客户端技能函数与cltmissile*;客户端飞弹按 Missiles.txtpCltDoFunc / 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 关卡不再用 SplitMix64pickVariant二次重抽,从根本上消除变体间子格碰撞差异导致的预测分叉。
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 typecheck0 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 的抓包比对与在线验证。
七、风险
- DRLG 逐位一致(最大风险):任何分叉都会导致碰撞与位置和服务端不一致。缓解:D2MOO oracle 逐阶段比对 + D2GS 在线
0x07oracle(后者代表真实 1.13cD2Common.dll)。 - 客户端技能 / 飞弹函数:D2Client 侧逻辑以 1.13c 反汇编为准;D2MOO 以 D2Common / D2Game 为主,客户端函数覆盖度待确认,是工作量最大的不确定项。
- Warden(
0xAE):若服务端启用需单独评估;测试服默认关闭。 - 版本校验:CheckRevision 需要真实 1.13c 客户端文件,或在测试服放宽 PvPGN 版本检查。
- WS↔TCP 桥安全:生产部署需鉴权、限流与目标白名单。
act-scene.ts解耦回归:拆分期间单机模式须保持全量测试通过。
八、不在范围(YAGNI)
Battle.net 聊天频道 / 好友 / 天梯榜 UI、1.14+ 与 D2R 兼容、D2GS 服务端改造、Warden 模块执行、任何外挂式功能(如全图显示)。
约束
严格以 1.13c 反汇编(D2Client.dll / D2Common.dll / D2Net.dll)与官方 MPQ 数据表为准绳,禁止编造规则与静默降级兜底;客户端不做任何权威结算;所有改动遵守 worktree 隔离规则。