diablo2-web/README.md

249 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Diablo II: Lord of Destruction (v1.13c) — Web Port
## 1. 项目概述
本项目是 **《暗黑破坏神 II:毁灭之王》(Diablo II: Lord of Destruction v1.13c)** 的高精度 Web 移植实现。项目严格遵循原版 1.13c 反编译二进制汇编(`D2Common.dll`、`D2Client.dll`、`D2Game.dll`、`D2Net.dll`、`Bnclient.dll`、`D2MCPClient.dll`、`Fog.dll`)与官方 MPQ 数据表(`Levels.txt`、`LvlTypes.txt`、`SuperUniques.txt`、`Skills.txt`、`TreasureClassEx.txt` 等)作为唯一事实基准(Ground Truth):
- **1:1 原生网络协议兼容**:完整实现 BNCS(战网登录/聊天,端口 `6112`)、MCP(Realm 角色管理/建房加房,端口 `6113`)与 D2GS(Huffman 压缩帧、167 个 S2C 操作码、C2S 指令及物品比特流,端口 `4000`),浏览器客户端可通过轻量 TCP-to-WebSocket 桥直接连入原版 C++ **PvPGN + D2GS (1.13c)** 战网服务器。
- **种子驱动的确定性 DRLG 地图重建**:1:1 复刻 `D2Common.dll` 64 位进位乘法随机数 (`0x6FDAA9E0`) 与三大 DRLG 拓扑算法(Preset 预设、Maze 地牢迷宫、Outdoors 野外生成),客户端仅凭 D2GS 下发的 32 位 `mapSeed` 即可在浏览器端还原与 C++ 服务端逐格一致的瓦片地图与碰撞网格。
- **WebGL2 高性能等距渲染与离线资产管线**:通过 Node.js 离线烘焙管线 (`src/baker`) 将原版 MPQ 中的 DT1/DS1 瓦片、DC6/DCC 角色与怪物动画、COF 复合图层、PL2 调色板与 TBL 字符串表预编译为静态图集与二进制包,配合 Service Worker 增量缓存与 WebGL2 合批渲染实现 60fps 流畅体验。
- **严格的模块化架构隔离**:全仓按原版 DLL 职责划分为五大核心代码模块与独立工具集,由多套独立 `tsconfig.*.json` 与架构边界测试(`tests/arch/boundaries.test.ts`)强制保障零跨层污染。
---
## 2. 模块概述与 README 链接
项目源码按职责边界严格拆分为以下模块,点击各模块的 `README.md` 可查看详细的子目录结构、核心算法与验证命令:
| 模块路径 | 对标原版组件 | 运行环境与边界约束 | 核心职责简述 | 详细文档 |
| :--- | :--- | :--- | :--- | :--- |
| [`src/common`](./src/common) | `D2Common.dll` `Storm.dll` `D2Lang.dll` | 纯 TypeScript (`ES2022`),零 DOM / 零 Node.js 依赖 | MPQ 与原生二进制格式解析器 (DC6/DCC/DT1/DS1/COF/PL2/TBL)、`D2DataRegistry` 数据表中心、1.13c DRLG 随机地图生成算法、64 位 `D2Rng` 与烘焙校验契约 (`pack-contract`) | [src/common/README.md](./src/common/README.md) |
| [`src/netproto`](./src/netproto) | `D2Net.dll` `Bnclient.dll` `D2MCPClient.dll` `Fog.dll` | 纯协议栈模块,零外部模块依赖,唯一入口 `src/netproto/index.ts` | BNCS (`6112`) / MCP (`6113`) / D2GS (`4000`) 二进制协议编解码、XSHA-1 与 CheckRevision 认证、Fog.dll Huffman 解压、167 个 S2C 操作码解析、物品比特流与 WebSocket/TCP 传输层抽象 | [src/netproto/README.md](./src/netproto/README.md) |
| [`src/client`](./src/client) | `D2Client.dll` `D2Gfx.dll` `D2Win.dll` `D2Launch.dll` | 纯浏览器客户端 (`DOM` + `WebGL2`),零 Node / 零 `server` / 零 `baker` 依赖 | 唯一页面入口 `play.html`、战网登录/选人/大厅前端界面、`OnlineSession` 与 `ClientWorld` 状态镜像、种子驱动的实时 DRLG 地图重建、移动预测与内插、WebGL2 渲染器、全套经典 HUD 面板与抓包检查器 | [src/client/README.md](./src/client/README.md) |
| [`src/server`](./src/server) | `D2Game.dll` | 纯确定性模拟 (`ES2022` + `WebWorker`),禁 DOM / 禁 Node / 禁非确定性随机与时钟 | 25fps (40ms/tick) 权威游戏循环、1.13c 战斗伤害与抗性管线、7 职业 221 个技能执行器、投射物与光环引擎、怪物 AI 与房间流式激活、TC 掉落管线与 `.d2s` 存档读写 | [src/server/README.md](./src/server/README.md) |
| [`src/baker`](./src/baker) | 离线资源构建管线 | 纯 Node.js CLI 环境 (`types: ["node"]`),禁 DOM / 禁 `client` / 禁 `server` | 从原版 1.13c MPQ 归档中提取并烘焙瓦片图集 (`tiles`)、角色/怪物/NPC 实体 (`entities`)、投射物 (`missiles`)、覆盖层 (`overlays`)、HUD/前端 UI、动画表 (`animdata`) 与 SHA-256 资产清单 (`asset-manifest.json`) | [src/baker/README.md](./src/baker/README.md) |
| [`tools/`](./tools) | 开发者与 QA 工具集 | Node.js / Playwright CLI | 无头协议测试机器人 (`d2-bot.ts`)、`.d2cap` 抓包离线重放器 (`netproto-replay.ts`)、`.d2s` 存档全解锁工具 (`d2s-unlock.ts`)、全谱系 136 关卡浏览器审计 (`audit-levels-browser.ts`) 与 DRLG 差分对比工具 | [tools/README.md](./tools/README.md) |
### 模块依赖拓扑图
```text
┌──────────────────┐ ┌────────────────────┐
│ src/common │ │ src/netproto │
│ (Pure TS Base) │ │ (1.13c Wire Stack) │
└────────┬─────────┘ └─────────┬──────────┘
│ │
┌─────────┼──────────────────┐ │
▼ ▼ ▼ ▼
┌─────────────┐ ┌──────────────┐ ┌─────────────────────────┐
│ src/baker │ │ src/server │◄──┤ src/client │
│ (Node CLI) │ │ (D2Game Sim) │ │ (Browser WebGL2 Client) │
└─────────────┘ └──────────────┘ └─────────────────────────┘
*(注:src/client 与 src/server 之间完全解耦,仅各自依赖 src/common 与 src/netproto)*
```
---
## 3. 单客户端模式使用方法(Single-Client Mode Guide)
在**单客户端模式**下,Web 前端作为纯粹的 1.13c 在线客户端运行([`play.html`](./play.html)),所有战网账号认证、角色存档、房间管理与游戏权威战斗逻辑均由远端原版 **PvPGN (`bnetd` + `d2cs` + `d2dbs`) + D2GS (v1.13c)** 服务器承载。
完整跑通单客户端模式包含以下三个核心步骤:
---
### 3.1 第一步:在 D2GS / PvPGN 机器上设置 TCP-to-WebSocket 桥
由于浏览器只支持 WebSocket (`ws://` / `wss://`) 而不支持直接建立原始 TCP Socket,需要在运行 PvPGN 与 D2GS 的服务器(或同内网网关机)上部署 **`websockify` + Nginx** 反向代理,将浏览器的三路 WebSocket 子路径桥接到对应的原生 TCP 端口:
```text
浏览器 Web 客户端 (src/netproto WsStream)
│
├─ wss://<host>/d2net/bnet ──► Nginx :443 ──► websockify 127.0.0.1:7001 ──► PvPGN bnetd (TCP :6112)
├─ wss://<host>/d2net/realm ──► Nginx :443 ──► websockify 127.0.0.1:7002 ──► PvPGN d2cs (TCP :6113)
└─ wss://<host>/d2net/game ──► Nginx :443 ──► websockify 127.0.0.1:7003 ──► D2GS 1.13c (TCP :4000)
```
#### 1) 安装 `websockify`
在 Linux 网关/宿主机(例如 Ubuntu/Debian)上安装 `websockify`:
```bash
sudo apt-get update && sudo apt-get install -y websockify nginx
```
#### 2) 配置 `systemd` 守护服务管理 3 个桥接端口
创建环境变量配置目录 `/etc/d2ws` 及三个端点的配置文件(若 D2GS 运行在另一台内网 Windows 机器上,将 `TARGET` 中的 `127.0.0.1` 替换为对应的内网 IP 即可):
```bash
sudo mkdir -p /etc/d2ws
# 1. BNCS (bnetd, TCP 6112)
sudo tee /etc/d2ws/bnet.env >/dev/null <<'EOF'
LISTEN=127.0.0.1:7001
TARGET=127.0.0.1:6112
EOF
# 2. MCP Realm (d2cs, TCP 6113)
sudo tee /etc/d2ws/realm.env >/dev/null <<'EOF'
LISTEN=127.0.0.1:7002
TARGET=127.0.0.1:6113
EOF
# 3. D2GS Game Server (D2GS.exe 1.13c, TCP 4000)
sudo tee /etc/d2ws/game.env >/dev/null <<'EOF'
LISTEN=127.0.0.1:7003
TARGET=127.0.0.1:4000
EOF
```
创建模板单元文件 `/etc/systemd/system/d2ws@.service` 并启动服务:
```bash
sudo tee /etc/systemd/system/d2ws@.service >/dev/null <<'EOF'
[Unit]
Description=Diablo II WebSocket-to-TCP Bridge (%i)
After=network.target
[Service]
Type=simple
EnvironmentFile=/etc/d2ws/%i.env
ExecStart=/usr/bin/websockify --heartbeat=30 ${LISTEN} ${TARGET}
Restart=always
RestartSec=2
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now d2ws@bnet d2ws@realm d2ws@game
```
#### 3) 配置 Nginx `/d2net/*` WebSocket 反向代理
在 Nginx 站点配置(如 `/etc/nginx/sites-enabled/default`)的 `server` 块中加入 `/d2net/` 路由规则:
```nginx
# BNCS (端口 6112)
location = /d2net/bnet {
proxy_pass http://127.0.0.1:7001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_buffering off;
}
# MCP Realm (端口 6113)
location = /d2net/realm {
proxy_pass http://127.0.0.1:7002;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_buffering off;
}
# D2GS Game Server (端口 4000)
location = /d2net/game {
proxy_pass http://127.0.0.1:7003;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_buffering off;
}
```
重载 Nginx 配置并验证服务状态:
```bash
sudo nginx -t && sudo systemctl reload nginx
systemctl is-active d2ws@bnet d2ws@realm d2ws@game
```
> **提示**:客户端的 `createWsBridgeResolver`(位于 [`src/netproto/transport/endpoint.ts`](./src/netproto/transport/endpoint.ts))会自动根据协议握手阶段返回的端口(`6112`、`6113`、`4000`)将连接重写至 `<wsBridgeUrl>/bnet`、`/realm` 与 `/game`,因此无需修改 D2GS 内部下发的 IP,只需确保 PvPGN 的 `realm.conf` / `address_translation.conf` 中配置的端口保持标准 `6113` 与 `4000` 即可。
---
### 3.2 第二步:如何烘焙离线美术资源(Offline Asset Baking)
Web 客户端运行所需的瓦片地图图集、角色/怪物 DCC 精灵、HUD 面板与动画速度表均通过 `src/baker` 从原版 1.13c MPQ 离线烘焙生成到 `public/` 目录。
#### 1) 安装项目依赖
```bash
npm install
```
#### 2) 放置原版 1.13c MPQ 文件
将原版 **Diablo II: Lord of Destruction v1.13c** 的 MPQ 文件放入(或软链接至)`samples/d2/` 目录:
```bash
mkdir -p samples/d2
# 确保以下 4 个核心 MPQ 存在(音频 MPQ 如 d2sfx.mpq / d2music.mpq / d2xmusic.mpq 可选):
ls -lh samples/d2/
# d2data.mpq d2char.mpq d2exp.mpq Patch_D2.mpq
```
#### 3) 执行全量烘焙与契约校验
```bash
# 一键烘焙全部离线资源到 public/act-packs/ 并生成 public/asset-manifest.json
npm run bake -- all
# 烘焙完成后执行严格完整性校验(验证 233+3 个 DT1 瓦片、实体图集与 0 缺失文件)
npm run bake:verify
```
如果需要单独重新烘焙某一部分资源,也可以使用细粒度命令:
- `npm run pack:tiles`:仅烘焙全部 5 幕瓦片图集与 `drlg-bundle.{json,bin}`
- `npm run pack:frontend`:仅烘焙战网登录、角色选择与大厅前置界面资源
- `npm run pack:ui`:仅烘焙游戏内 HUD 面板、技能图标与鼠标指针
- `npm run pack:animdata`:仅提取 `AnimData.d2` 动画帧率表
- `npm run pack:entities`:仅烘焙怪物、SuperUniques(含随从)与场景交互物图集
- `npm run pack:missiles` / `npm run pack:overlays`:仅烘焙投射物或光环/状态覆盖层特效
- `npm run pack:manifest`:重新扫描 `public/` 并更新 `public/asset-manifest.json`(触发客户端 Service Worker 增量更新)
---
### 3.3 第三步:如何启动服务与连接游戏
#### 方式 A:本地开发模式启动(Vite Dev Server)
```bash
npm run dev
```
- 启动后浏览器访问 **`http://127.0.0.1:5173/play.html`**(访问根路径 `/` 也会自动重写到 `/play.html`)。
- **默认代理配置**:[`vite.config.ts`](./vite.config.ts) 已内置 `/d2net` WebSocket 代理(默认转发至远端测试服 `wss://www.laiseek.xyz/d2net/*`),因此本地启动后可直接登录进入游戏。
- **自定义切换 D2GS 服务器地址**:
1. **URL 参数方式**:访问 `http://127.0.0.1:5173/play.html?wsBridge=wss://your-d2gs-domain.com/d2net`(或本地桥 `ws://127.0.0.1:8080/d2net`)。
2. **UI 设置面板方式**:点击画面顶部 32px 工具栏右侧的 **⚙ Settings** 按钮,修改 **WS Bridge URL** 并保存刷新。
#### 方式 B:生产环境构建与静态部署
```bash
# 1. 全仓类型检查(同时校验 common / netproto / server / client / baker 五套 tsconfig)
npm run typecheck
# 2. 构建生产静态产物(输出至 dist/,若需部署在 /diablo2/ 子路径下可执行 npm run build:game 输出至 dist-game/)
npm run build
# 3. 本地预览生产构建包
npm run preview
```
在生产环境 Nginx 中,只需将构建生成的 `dist/` 目录挂载为静态站点根目录(或子路径 `/diablo2/`),并与 **3.1 节** 中的 `/d2net/{bnet,realm,game}` WebSocket 反向代理配置在同一个域名下,即可通过浏览器直接访问完整的在线暗黑 II 体验。
#### 方式 C:使用命令行无头 Bot 快速验证服务连通性
无需打开浏览器,可在终端使用 [`tools/d2-bot.ts`](./tools/d2-bot.ts) 直接测试 TCP 桥与 D2GS 建房/进图流程:
```bash
npm run bot -- --bridge wss://your-d2gs-domain.com/d2net --user <账号> --pass <密码> --char <角色名>
```