内容
# 无客户端
一款无客户端的暗黑破坏神 II **1.14d** 战网客户端,使用 Zig 编写。它直接与服务器通信,使用
**BNCS**(登录)、**MCP**(游戏服务器/角色)、**BNFTP**(文件传输)和 **D2GS** 游戏协议
—— **无需游戏二进制文件,无需 Wine,也无需图形支持**。一个小型静态二进制文件即可连接、
认证、选择游戏服务器、创建/选择角色、加入游戏并接收实时的游戏数据流。
## 为什么存在
这是 **[jaenster/d2-dedicated-server](https://github.com/jaenster/d2-dedicated-server)**
的**测试客户端** —— 一个使用 Zig 重写的暗黑破坏神 II 封闭游戏服务器的干净室实现(`realmd`
三元组:bnetd + d2cs + d2dbs,加上一个无头游戏服务器)。
要验证服务器是否真正工作,需要一个能与服务器进行**客户端**通信的东西。真正的游戏客户端
是一个封闭源代码的 Windows 二进制文件,需要图形支持、Wine 和专有数据文件 —— 对于自动化测试
来说毫无用处。`clientless` 就是答案:它从脚本驱动**整个**客户端→服务器握手过程,以便在 **CI
和手动**测试中,在每次提交时都能验证服务器的认证、游戏服务器、角色和加入游戏的路径。
它也是一个忠实且可读的参考,展示了 1.14d 封闭战网协议的工作原理,并且可以对抗真正的战网
进行验证(它已经针对实时网关进行了端到端验证)。
## 功能
- **版本检查** —— 计算现代 base64 CheckRevision 响应(`checkrev_core`,1.14d `CheckRevision.mpq`
算法)和 `SID_AUTH_CHECK` 的 CD-key 认证块。
- **账户** —— 登录或创建账户,支持 OLS(Blizzard 的损坏 SHA-1 密码哈希)。
- **游戏服务器** —— 列出游戏服务器、登录到游戏服务器,然后通过 **MCP**:列出/创建/选择角色,
读取排行榜。
- **聊天** —— 进入聊天、加入频道、发送消息、`/kick` 和监听事件。
- **游戏** —— 创建或加入游戏并**进入游戏服务器**:`GAMELOGON (0x68)` → 等待 GS
`0x02` LoadSuccess → `JOINGAME (0x6b)`,然后读取 S→C 游戏数据流(opcode 帧;`0xAE`
霍夫曼块被解压缩)—— 角色分配、关卡加载、单位生成、状态更新、任务。
- **BNFTP** —— 发现和下载服务器文件(`bnftp` 子命令)。
- 支持对抗**您自己的游戏服务器**(realmd/pvpgn)和**真正的战网**;处理现代 base64 挑战和旧版
`A=1 B=1 …` 公式;在宽容的游戏服务器上**无密钥**运行。
## 不能(和不会)做的事情
- **它不是一个机器人。** 它执行加入握手、解析传入的数据包流并打印它看到的内容 —— 但它**不**
建模游戏状态(无玩家/单位/位置跟踪)并且**不**在世界中行动(无移动、无技能、无物品拾取)。
它是一个协议测试工具,而不是自动化框架。
- **它不会留在游戏中。** 加入后,它读取数据流窗口并退出;没有长期会话/保持活动循环。
- **没有本地 Windows 二进制文件。** 套接字层是 POSIX;在 Windows 上通过容器运行(Docker Desktop
/ WSL2)。Winsock 端口是可能的,但尚未实现。
- **经典挑战是一个占位符。** 对于旧版 `A=1 B=1 …` 公式,它发送一个占位符哈希,只有宽容的游戏
服务器接受 —— 它不计算真正的经典 CheckRevision(需要游戏文件)。
- **没有反作弊工作。** 它不运行、伪造或绕过锁定/额外工作(类似 Warden 的)模块。游戏进入
不需要它们,所以它根本不参与它们;它不会被扩展到在实时服务上击败它们。
- **一次会话,每个运行,单线程。** 没有连接池,没有并发。
## 安装
一行命令(Linux / macOS / FreeBSD)—— 检测您的操作系统/架构,获取最新的发布二进制文件:
curl -fsSL https://raw.githubusercontent.com/jaenster/d2-clientless/main/install.sh | sh
或者,从 [Releases](https://github.com/jaenster/d2-clientless/releases) 页面自行获取 tarball
文件 —— **Linux**(x86_64, aarch64, armv7, riscv64, 静态 musl),**macOS**(x86_64,
aarch64)和 **FreeBSD**(x86_64, aarch64)。或者运行 [容器](#容器)。
## 构建
需要 **Zig 0.16**。生成单个二进制文件:
zig build # -> zig-out/bin/clientless
zig build test # 加密单元测试(CheckRevision / CD-key / xSHA-1)
## 用法
clientless <主机> [产品] [版本] [选项] # BNCS / MCP / 聊天 / 游戏
clientless bnftp [选项] <主机> [产品] [文件] # BNFTP 文件客户端
运行 `clientless` 时不带参数即可查看完整的分段帮助。常见标志:
| 标志 | 含义 |
|-|-|
| `--keys K1[,K2]` | 26 个字符的 CD-key(s);宽容的游戏服务器上省略 |
| `--login acct:pass` / `--create acct:pass` | 登录 / 注册然后登录 |
| `--game <名称>` (`--gs-port <n>`) | 创建 + 加入游戏并进入 GS |
| `--channel/--say/--kick/--listen <秒>` | 聊天 |
| `--verbyte <n>` | GAMELOGON 版本字节(默认 14 = 1.14d) |
| `--force-checkrev` | 即使版本检查 MPQ 不是 `CheckRevision.mpq` 也回答 |
| `--sig0` / `--delay <毫秒>` / `--verbose` | sigOk=0 / 每个步骤的间隔 / 所有流量十六进制转 dump |
### 示例
# 仅版本检查 - 无密钥,无帐户
clientless useast.battle.net
# 您自己的游戏服务器上的完整会话:创建角色,聊天,读取排行榜
clientless realm.example.com D2XP 1.14.3.71 --login me:pw --listen 20
# 创建帐户,然后创建 + 进入游戏
clientless realm.example.com D2XP 1.14.3.71 --create me:pw --game MyGame
# 两个客户端聊天;操作员踢出另一个客户端
clientless realm.example.com --login op:pw --channel ops --kick rude
# 真实的战网与真实的 CD-keys(您的帐户,您的责任)
clientless useast.battle.net D2XP 1.14.3.71 --keys K1,K2 --login acct:pw --game MyGame
# BNFTP:文件的大小,然后下载它
clientless bnftp --head useast.battle.net D2XP CheckRevision.mpq
clientless bnftp --out-dir . useast.battle.net D2XP CheckRevision.mpq
## 游戏加入方式
`GAMELOGON (0x68)` 携带 JOINGAME 令牌 + 游戏哈希 + 角色名称(确切的 `D2GSPacketClt0x68`
字段布局)。客户端首先**等待 GS 的 `0xAF`** 连接包(类似于真正的客户端连接循环),然后发送
`0x68`,读取 S→C 数据流**由 opcode + 引擎大小表帧组成**(非长度前缀),并仅在服务器的
`0x02` LoadSuccess 响应后发送 `JOINGAME (0x6b)`。压缩的 `0xAE` 块被霍夫曼解压缩
(`src/huffman.zig`)并解析其内部数据包。
## 三个哈希
容易混淆;它们是不同的:
1. **CheckRevision** —— 标准 SHA-1。`checkrev_core` 特定实现 1.14d `CheckRevision.mpq`
算法;对于其他版本检查 MPQ,它拒绝回答(因此它永远不会发送错误响应),除非
`--force-checkrev`。
2. **CD-key 块** —— 标准 SHA-1 哈希 `clientToken ++ serverToken ++ product ++ public ++ value`。
3. **帐户密码** —— Blizzard 的**损坏** SHA-1(`src/xsha1.zig`):单比特旋转消息调度,小端
I/O,零填充。
## 容器
docker run --rm ghcr.io/jaenster/d2-clientless <主机> D2XP 1.14.3.71 --login acct:pass
docker run --rm ghcr.io/jaenster/d2-clientless bnftp --head <主机> D2XP CheckRevision.mpq
多架构(linux/amd64 + arm64),静态 musl 在 `scratch` 上。这也是在 Windows 或 macOS
上运行它的方法(Docker Desktop / WSL2),因为目前还没有这些平台的本地二进制文件。
## CI
`zig build` + `zig build test` 在每次推送时运行。`e2e` 作业检出
[d2-dedicated-server](https://github.com/jaenster/d2-dedicated-server),构建 `realmd`,并
驱动与其的完整会话(认证 → 创建 → 登录 → MCP → 角色 → 排行榜 → 聊天)——
参见 `ci/e2e.sh`。游戏进入需要一个真正的游戏服务器(专有的 `Game.exe` 在 Wine 下),所以
`ci/e2e-game.sh` 只在具有 D2 文件的自托管运行器上运行。
## 注释和致谢
- 与实时战网通信会发送您的 CD-keys 和凭证 —— 速率限制和服务条款由您负责。在您自己的游戏
服务器上,它只是一个测试客户端。
- 没有提交密钥或凭证;它们是参数。单元测试使用综合向量。
- `src/huffman.zig` 中的 D2GS 霍夫曼表移植自
[jaenster/D2PacketBased](https://github.com/jaenster/D2PacketBased)。
- 套接字通过 libc (`std.net` 在 Zig 0.16 中被删除);本地主机目标。