fredangstadt-lang/LineWarsII-DOS
GitHub: fredangstadt-lang/LineWarsII-DOS
该项目将1994年DOS空战游戏LineWars II的引擎从原始二进制文件用C语言进行行为重建,并通过模拟器执行原始机器码的方式进行严格的自动化验证。
Stars: 2 | Forks: 0
# LineWars II,重构版
LineWars II 是 Patrick Aalto 于 1994 年制作的一款 DOS 空战游戏,由 Safari
Software 发行。这是它的引擎,从发布的二进制文件中用 C 语言重建。它构建为
单个 `game.exe`(107,200 字节),可以在 DOSBox-X 中玩全部十个任务:
座舱、HUD、雷达、扫描仪、声音、音乐、配置界面和吸引模式(attract-mode)
演示。


## 只想玩这款游戏
`dist/` 是一个预构建的、可直接运行的包:无需编译器,无需构建步骤。它包含
`GAME.EXE` 和 `GAME.DAT`,一个已内置该游戏的可启动 FreeDOS 软盘
(`LINEWARS2.img`),一个 `Play.bat` 启动器,以及一份 `README.txt`。安装
DOSBox-X(免费,dosbox-x.org),双击 `Play.bat`,它就会直接启动进入
游戏。你可以从解压的任何位置运行它。本文档的其余部分适用于
从源码构建。
## 出处与授权
LineWars II 版权所有 © 1994 Patrick Aalto。在发布任何内容之前,我联系了他,
他在 2026 年 7 月对该项目给予了祝福:该重建版可以以免费软件的形式发布;
任何商业用途都需要单独与他进行授权讨论。此仓库保持在该界限的免费软件范围内。
`NOTICE.md` 包含完整的条款。
## 这是什么,以及不是什么
这是对零售二进制文件的行为重建,而不是匹配的反编译。该 C 语言代码使用 Open
Watcom 构建,因此编译出的操作码有意地与原始可执行文件不匹配。其目标是使用
可移植的 C 语言来计算和绘制 1994 年游戏所计算和绘制的内容,并通过机械化
方式验证,而不是靠肉眼。
验证套件包含 41 个自动化关卡;其中 27 个在模拟环境下执行原始
机器码,并将其与此构建版本进行 diff 比较:包括在扫描输入域上的函数返回值
(仅 `lw2_atan_base` 就在所有 78,060 个黄金向量上进行了检查)、VGA 端口写入、
全部十个任务生成表(逐字节比较),以及一个跨越所有 153,600 个帧缓冲字节的
冻结场景比较。当 C 语言代码与原始版本产生偏差时,构建就会失败(变红)。
哪些是经过关卡验证的,哪些只是实现了功能:
| 领域 | 状态 |
| --- | --- |
| VGA 渲染、定点运算、游戏逻辑、所有 10 个任务生成表 | 经关卡验证,与原始执行字节一致 |
| 音频(通过 `SPEAKER.LW2` 驱动的 PC speaker;Sound Blaster DSP/DMA 及其音乐驱动)、键盘 | 已实现并经过游戏测试;未进行 oracle 关卡验证 |
| 串口/调制解调器多人模式(Duel/Wing/Group/StarWars) | 在提示屏幕后被存根(stubbed);原版需要串口对端 |
| 原版的其他声音设备(Covox 8-bit DAC、Gravis UltraSound、Roland LAPC-I、Roland SCC-1)和视频驱动(S3、VESA、ATI、MCGA) | 不在范围内;此构建版本仅支持 VGA 以及上述两条音频路径 |
如果你在经过验证的范围内发现与原版不一致的行为,
那是一个 bug。请提交它。
从原版重建的每个函数都带有其来源的偏移量。
`lw2_flight.c` 中的 `/* @0x24a4..0x24a7 */` 表示原始
映像中的这四个字节是该行重建的内容。这是故意的:这是将 2030 年发现的
bug 追溯到 1994 年代码的方式。(另一个独立的 SDL3 移植版,
,去除了所有这些内容,因为
移植版需要的是行为,而不是出处。)
## 它是如何制作的
LLM 智能体编写了它:由 Claude Opus 4.8 & Fable 5 主导,较小的模型作为
工作者,由一名操作员驱动。在 2026 年 7 月进行了 12 个日历日,其中 10 天处于
活跃状态,通过 12,310 次调用花费了 2,655.98 美元的 API token。这些数字是
从运行自身的记录中解析出来的。我不会花力气去证明这个时间线;信不信由你。
这个产物才是我会维护的部分。
它不是一帆风顺的。第一周准确地产生了文献中关于 LLM 反编译所预测
的结果:看似合理、自信但却是虚构的代码。有一次,
一个子智能体在一个启动时就崩溃的构建版本上报告了 MISSION WON。这被
老派的方法捕获了,即由人类来玩游戏。回应是机械化的:
每一条之前的声明都根据重新运行的反汇编进行了重新审核,并且从 7 月
3 日起,如果没有执行原始字节的关卡对变更进行投票裁决,任何改动都不会发布。
在这个过程中发现了两个比较丑陋的问题:一个静默的 DGROUP 耗尽导致
getenv() 失效并杀死了所有声音,以及一个优化器丢弃了 far 指针的段(segment)
部分。
## 开发在哪里进行
这个仓库是存档:反汇编注释、关卡,以及
DOS 构建版本,它们作为参考被完整保留,以备日后需要挖掘任何内容之用。
新功能不会合并到这里。未来的工作(TCP/IP 多人游戏、作为设置选项的敌方和
友方 AI 的导弹使用、可定制的第 10 个任务、进一步
的移植)在 SDL 仓库中进行:
。
## 包含内容
```
engine/ 23 C modules and their headers. 20 are linked into game.exe;
lw2_setup.c, lw2_object.c and lw2_image.c are earlier
reconstructions kept for reference and for the tests.
tests/ 41 gates. unit/ = golden-vector tests; oracle/ = the Unicorn
harness that executes the original bytes; stubs/ = link stubs.
tools/ build scripts, the codegen gate, the naming and style checkers,
the performance bench, generators/ for the extracted-data headers.
reference/ the original game and the dumps every gate diffs against.
docs/ the reverse-engineering log, per-module notes for 9 of the
modules under docs/engine/, and screenshots in docs/captures/.
data/ game.dat, the assets the engine reads at runtime.
```
## 前置条件
这里没有内置任何这些工具;全部都是免费的。
- **Open Watcom 2.0** 用于构建 DOS 二进制文件()。
将 `WATCOM` 指向你的安装路径,或者在 `tools/watcom/ow2` 中放入一个:
$env:WATCOM = "C:\WATCOM" # PowerShell
export WATCOM=/opt/watcom # bash
- **gcc** 和 **bash** 用于构建并驱动测试。
- **Python 3**,带有 **`unicorn`**(`pip install unicorn`)用于 oracle 关卡。
没有 unicorn,41 个关卡中有 27 个无法运行,并且套件会失败,而不是
报告剩下的 14 个关卡成功。如果完全没有 Python,测试套件将直接
失败。
## 构建
```
powershell -File tools\build_game.ps1
```
已通过 Windows PowerShell 5.1 和 Open Watcom 2.0 beta 测试。生成
`engine/game.exe`,107,200 字节。要运行它,请将 `game.exe` 和 `data/game.dat`
放在同一个目录下,并让 DOSBox-X 指向它。
构建被刻意拆分了。所有中断可触及的部分都使用 `-2` 编译,
因为 16 位 ISR 序言不会保留 32 位
寄存器的上半部分,而完整的 `-3` 构建会在菜单绘制时挂起。只有四个主循环
模块(`lw2_math3d`、`lw2_render`、`lw2_flight`、`lw2_gfx_vga`)使用 `-3`,这
带来了真正的 `imul`/`idiv`,而不是 Watcom 的 `__I4M`/`__I4D` 辅助调用。
`tools/build_game.ps1` 记录了每个标志及其存在的原因。
## 测试
```
bash tests/run_unit_tests.sh
```
预期输出 `UNIT TESTS: all passed`:12 个宿主测试,27 个 oracle 关卡,2 个源码关卡。
这需要几分钟时间,其中大部分时间都是 Unicorn 在单步执行 1994 年的代码。
oracle 关卡只需要 gcc 和 unicorn;object-hash 源码关卡还需要
Open Watcom,如果没有它,测试套件会指出缺失的编译器并失败,
而不是少报结果。`LW2_ALLOW_NO_WATCOM=1` 接受 40 个关卡的运行。
这两个源码关卡值得了解一下:
- `tools/prove_identical.py` 编译所有 20 个链接模块,并将每个模块
与记录的基线进行比较。重命名局部变量或重写注释不能
移动任何一条指令,因此如果哈希值发生变化,说明该编辑做了它
不应该做的事情。
为了使该基线在本机之外有意义,必须处理两件事,
而第二件是真正的 Open Watcom 怪癖。首先,`wcc` 将源码的
名称标记到 THEADR 中,并将每个依赖项的路径和 mtime 标记到
COMENT class 0xE9 记录中,因此如果你仅仅是解压到
其他地方或触碰了头文件,原始的 `.obj` 哈希值就会改变:同一个
`lw2_fmt.c` 在这里生成了一个 646 字节的目标文件,而从更长的路径
则生成 661 字节,代码完全相同,15 个字节的路径差。该关卡仅
对携带代码和符号的记录进行哈希处理,并跳过那两者。其次,
更奇怪的是:**Open Watcom 的代码生成取决于构建路径的长度。**
从一个 36 字符的目录和一个 39 字符的目录编译字节相同的
`lw2_gfx_vga.c`,你会得到两个不同的函数,具有不同的栈(stack)
槽和额外的重新加载。这是一个阈值,而不是滑动,并且该模块上的 `-oe=80`
正是暴露它的原因。所以该关卡不会在你解压的地方编译:它将
`engine/` 复制到一个较短的固定路径并在那里进行编译。因此,
`tools/object_hashes.json` 中的基线可以在任何地方重现,这一点已通过
在不同的目录名下从 112 字符的路径构建它进行了测试。
- `tools/check_names.py` 会对以逆向工程中碰巧落入的寄存器命名的变量报错。
`cl` 表示值所在的位置;
`shade` 表示它是什么。Delta(`dx`/`dy`/`dz`)、坐标系列
(`cx`/`cy`/`cz`、`sx`/`sy`)和名为 `ch` 的 `char` 是豁免的,
测试中的 `asm_ref`/`ref_*` 模型也是如此,它们的全部工作就是镜像
原版的寄存器。
`tools/check_knf.py` 检查 OpenBSD style(9)。它是一个独立的工具,而不是一个
关卡,它目前报告了大约 2,800 行长行:该树在注释中保留了 `@0x`
考古信息,而这些注释超过了 80 列。那是一个
选择,而不是疏忽。
## 性能
在 DOSBox-X 中设置 `cycles=32000` 时,`game.exe` 运行吸引模式(attract demo)的速度为 26.4 fps,
运行任务 1 的速度为 46.8 fps,这是使用 `tools/perf_bench.ps1` 针对
`tools/perf_baselines.json` 测量得出的。在优化过程中,每帧的工作量从 40.3 毫秒
下降到了 25.6 毫秒,减少了 36%(`docs/PROGRESS.md`)。原版仍然更快;
缩小剩余的差距是正在进行的工作。
决定这里每一项优化决策的因素:在 DOSBox-X 的正常核心中,一个
模拟周期就是一条指令,固定的,并且一次 VGA 字节写入消耗 1 个周期,
因为 `vmemdelay` 默认为 0。唯一按硬件速率计费的是
端口 I/O,一个字(word)的 `OUT` 为 16 个周期,一个字节(byte)为 25 个周期。因此,这里的衡量标准是
指令计数,而不是内存流量,这与 1994 年时代的
优化指南告诉你的做法大相径庭。`docs/DOSBOX_COST_MODEL.md` 中有相关的
测量数据。`docs/PROGRESS.md` 记录了那些结果变*慢*并被
还原的实验,包括一个停放索引方案(28.7 到 29.9 毫秒)和一个
RAM 平面渲染目标。
在 `tools/perf_report.py` 中有一个测量注意事项:任何改变帧
计时的构建也会改变自动驾驶仪的轨迹,因为输入是按 dt 缩放的,所以
除非你强制设定,否则两个构建版本测量的不是同一个场景。工作量计(work meter)
(`-dLW2_DIAG_NOBLIT`)的存在就是为了规避这个问题的。
## 原始游戏文件
`reference/original/lw2/` 是 1994 年发布的原版。`reference/original/`
还包含解包后的可执行文件和提取的驱动程序二进制文件。关卡
需要它们:一个不执行原版代码的关卡不能证明关于原版的任何信息。
LineWars 版权所有 1994 Safari Software 和 Patrick Aalto,并且是
Patrick Aalto 的商标(`reference/original/lw2/LW2.DOC`)。
`engine/` 中的重建版是一个从头开始的 C 语言重写,不包含任何原始的
机器码,但它衍生自该作品,并且仅仅是因为该作品而存在。两个头文件确实携带了原始*数据*而不是代码,这些数据从二进制文件中逐字提取,
并且可以通过 `tools/generators/` 中的脚本重新生成:
`lw2_setup_ui.h`(配置编辑器 UI 表)以及简报和 DS 文本表。
## 状态
功能完善并已通过游戏测试:所有十个任务、声音、音乐、座舱、HUD、
雷达、配置界面、F1-F4、暂停。
`docs/PROGRESS.md` 是工作日志,而不是一份经过润色的文档,未完成的项目都在那里。
诚实的总结:这在每个代码块(block)上是语义等效的,
并不与原始二进制文件字节完全一致。Open Watcom 的寄存器调度与 1994 年的编译器
不同,并且为了可移植性,早期就放弃了字节一致性。当某个关卡报告
“0 mismatch”时,这意味着编译后的 C 语言代码与执行的原版在测试的每个输入上都达成了一致,并且测试说明了那是多少个输入。
## 许可证
重建的 C 语言源代码版权归 2026 Fred Angstadt 所有,采用 PolyForm
Noncommercial License 1.0.0(`LICENSE.md`):出于
任何非商业目的,你可以使用它、修改它并分享它。原始游戏及其数据(`reference/`、
`data/`、`dist/`)仍归 1994 Safari Software 和 Patrick Aalto 版权所有,并
经 Patrick Aalto 于 2026 年 7 月 16 日的书面许可,在此作为免费软件重新分发。
对这里的任何内容进行商业用途使用,必须事先获得他的同意。
`docs/dbx_src/` 中的 DOSBox-X 源码摘录采用 GPL-2.0-or-later 许可,并带有
各自的许可证(`docs/dbx_src/COPYING`);该代码均未被编译进
游戏。详见 `NOTICE.md`。
标签:DOS, DOSBox-X, Open Watcom, 云资产清单, 客户端加密, 游戏, 逆向工具, 逆向工程