# 01｜先认清这份红警源码：版本、目录与构建边界

这套文档研究的是 EA 发布的 **《Command & Conquer: Red Alert》第一代源码**。研究基线固定为提交 `0dc09bb1d6188d1ec79ab4765b22a48b75ffee32`，提交日期为 2025-02-27。这里的 2025 是公开仓库提交时间，不能当成游戏代码的创作时间。它也不是《红色警戒 2》、OpenRA 的重新实现，或 Remastered Collection 的现代宿主工程。以下解释以这份实际文件树及其编译条件为边界。

最先需要建立的认识是：**源代码可读，不等于仓库可直接编译，更不等于已经包含一套可运行游戏。** 官方 README 明确说明目前不能完整编译，恢复需要工作；重建说明限于 Win32，列出的历史工具是 Watcom C/C++ 10.6 与 TASM 4.0。本文进行了源码和构建清单审计，没有声称已编译、启动或通关游戏。[依据：README 的构建说明](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/README.md#L7-L24)

如果自行取得同一阅读基线，可以使用下面的 Git 命令；它们用于下载与固定版本，不是编译命令。后续做实验时宜从这个基线另开分支，保持原始代码可随时对照。所有源码链接也固定到此提交，避免未来分支位置变化导致正文与行号不一致。

```bash
git clone https://github.com/electronicarts/CnC_Red_Alert.git
git -C CnC_Red_Alert checkout --detach 0dc09bb1d6188d1ec79ab4765b22a48b75ffee32
```

## 1.1 这不是 1996 年原版代码的单一时间切片

读老项目时，文件头的初始日期、注释里的版本名称、当前启用的代码可能来自不同年代。例如 `DEFINES.H` 保留 1993 年初始日期和 CounterStrike 路径，但当前启用了 `FIXIT_ANTS`、`FIXIT_CSII`、`FIXIT_CARRIER`、`FIXIT_PHASETRANSPORT` 等修改。更有价值的是 1998-09-28 的说明：Aftermath 的修改同时包含修复与游戏规则变化，后来为 DVD 与 WOLAPI 的更新，决定把 RA、Counterstrike、Aftermath 及 DVD 版本统一到同一个可执行程序，让部分扩展包差异转为运行期判断。[依据：扩展包合并说明与开关](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/DEFINES.H#L75-L108)

当前还定义了 `FIXIT_VERSION_3` 和 `DVD`，后者进一步打开 `MPEGMOVIE`；Win32 编译清单打开 `WOLAPI_INTEGRATION` 与 `WINSOCK_IPX`。因此阅读单位、视频和网络逻辑时，应把它理解为**含后期补丁、扩展包统一与 Windows 在线服务接入的第一代红警代码**。[版本及 DVD 开关](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/DEFINES.H#L117-L137)；[Win32 编译宏](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/MAKEFILE#L85-L109)

版本常量也不能只取一个名字就下结论。`RAWOLAPI.H` 定义 `GAME_VERSION = 0x00030003`，而 `VERSION.CPP` 特意说明旧 `Version_Number()` 已不再使用，旧版本界限存在过时值，现用 `GAME_VERSION`。这为“后期 3.03 相关代码”提供证据，但仅凭常量不能证明这份树能一字节不差地生成某张发行光盘上的 EXE。[当前在线版本常量](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/RAWOLAPI.H#L319-L336)；[旧版本函数的弃用说明](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/VERSION.CPP#L147-L176)

另一个阅读陷阱是“文件存在”与“功能启用”不同。当前定义的是 `RELEASE_VERSION`；场景编辑器要求 `INTERNAL_VERSION`，完整作弊键要求内部版或测试版。源码保留编辑器实现，不代表默认发行配置会向玩家开放。[发行配置](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/DEFINES.H#L48-L58)；[编辑器和作弊键条件](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/DEFINES.H#L143-L175)

## 1.2 整个仓库是游戏、底层库和生产工具的集合

以下数量为固定提交本地文件树统计，包含头文件、汇编、构建文件、备份和少量二进制；不能相加后宣称“有这么多独立源码模块”。根目录 README 与 LICENSE 不计入表内。

| 目录 | 文件数 | 在制作流程中的角色 | 阅读时的边界 |
|---|---:|---|---|
| `CODE` | 589 | 游戏规则、对象、地图、界面、AI、事件、网络与通用技术模块 | 第一阅读入口；277 个 `.cpp`、242 个 `.h`，并非只有玩法代码 |
| `WWFLAT32` | 400 | DOS 时代 32 位底层库 | 包含内存、定时器、VGA/VESA、文件、音频等历史实现 |
| `WIN32LIB` | 568 | Windows 底层库 | DirectDraw、DirectSound、输入、计时及软件绘图；有历史副本 |
| `VQ` | 183 | DOS 路径下的 VQA 播放与配套工具库 | 不能直接等同于视频制作源素材 |
| `WINVQ` | 216 | Windows 路径下的 VQA 播放与配套工具库 | 包含播放器、查看器、历史版本与少量库文件 |
| `IPX` | 52 | 早期 Windows 32/16 位 IPX 桥接 | 同时有 `OK`、`OLD` 和备份，不等于全部进入当前构建 |
| `LAUNCH` | 2 | DOS 启动外壳 | 16 位汇编准备运行环境后启动 `GAME.DAT` |
| `LAUNCHER` | 47 | Windows 启动及补丁应用外壳 | 独立 `.dsp/.dsw` 工程，不是游戏主循环 |
| `TOOLS` | 25 | 资源转换、压缩、打包工具 | 只有部分工具提供源码，其余主要是历史 EXE |

构建清单验证了这几层的关系：主游戏按平台选择 `win32lib.lib` 或 `wwflat32.lib`，另有 `vqa32wp.lib` 与 `vqm32wp.lib`；主目录内部还把通用算法归入 `tech.lib`，把依赖图形库的 `ROTBMP`、`SPRITE` 归入 `jshell.lib`。目录位置和逻辑职责并不完全重合。[主程序的库分组](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/MAKEFILE#L340-L444)

## 1.3 从一个 CPP 到游戏 EXE：构建过程究竟做什么

可以把构建分为编译、归档和链接三步：编译把每个实现文件转为目标文件，归档把一组目标文件装进静态库，链接再选择需要的符号并组织成可执行文件。红警的 `CODE/MAKEFILE` 使用 Watcom `wmake` 语法，包括 `!ifdef`、`.AUTODEPEND`、`%create` 等；不是拿现代 GNU `make` 原样执行就能完成的通用脚本。

以 **Win32 英文版**为例，根据清单推演一条真实依赖路径：

1. 未指定德语或法语时，`LANGUAGE=ENGLISH`。定义 `WIN32` 后，库根变为 `..\win32lib`，目标文件放到 `obj\win32\ENGLISH`，链接响应文件选为 `win95.lnk`。
2. 例如 `UNIT.CPP` 由 `..\watcom\binnt\wpp386` 编译到相应 `.obj`。所有源文件共享平台宏、语言宏和头文件搜索路径。
3. `LCW`、`MIXFILE`、`RAWFILE`、加密与校验等对象归入 `tech.lib`；`SPRITE` 与 `ROTBMP` 归入 `jshell.lib`。
4. `wmake` 根据主清单**生成** `win95.lnk`，把目标文件、内部库、图形音频库和系统导入库逐条写入。
5. `linker\nwlink` 根据响应文件输出 `..\run\ra95.exe`。

以上是静态依赖推演，不是已执行成功的命令日志。[平台路径与语言选择](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/MAKEFILE#L45-L79)；[编译与汇编规则](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/MAKEFILE#L142-L152)；[内部静态库生成](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/MAKEFILE#L468-L484)；[最终链接及响应文件生成](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/MAKEFILE#L509-L570)

```mermaid
flowchart TD
    A[游戏 CPP 与 ASM] --> B[平台及语言对应 OBJ]
    C[通用技术模块] --> D["tech.lib 与 jshell.lib"]
    E[平台库及 VQA 库] --> F[生成链接响应文件]
    B --> F
    D --> F
    F --> G["ra95.exe 或 DOS game.dat"]
```

这里有一个容易误报的“缺文件”：仓库中没有现成 `WIN95.LNK`、`CONQUER.LNK`，但主 Makefile 明确会生成它们，所以不能仅凭缺失就判为构建损坏。相反，IPX 的独立脚本引用 `thipx32.lnk`、`thipx16.lnk`，在当前树中既未找到文件，也未见这些脚本生成它们，这才是需要补齐的另一类输入。[主响应文件规则](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/MAKEFILE#L521-L570)；[IPX 的依赖](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/IPX/MAKEFILE#L19-L28)

DOS 分支默认目标叫 `game.dat`，但它是程序，不是资源数据库。其规则先链接 `dos.exe`，再经过 `wstrip` 与 DOS/4GW Professional 绑定得到 `game.dat`。`LAUNCH.ASM` 的 `GameName` 则直接写着 `GAME.DAT`。[DOS 绑定过程](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/MAKEFILE#L495-L507)；[启动目标字符串](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/LAUNCH/LAUNCH.ASM#L550-L559)

平台条件还会更换实现文件：Win32 编译 `2KEYFBUF`、`2KEYFRAM`，DOS 编译 `KEYFBUFF`、`KEYFRAME`。研究形状帧缓存时若读错分支，会把不同实现混为一谈。[平台对象清单](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/MAKEFILE#L401-L425)

## 1.4 编译选项也是游戏格式的一部分

主清单为 Windows 使用 `/5r`，DOS 使用 `/5s`，对应不同调用约定；公用选项包含 `/zp1` 一字节结构体对齐、`/j` 有符号 `char`、`/ri` 对小整型返回值的约定，以及严格警告设置。这些选项会影响 C++ 与汇编互调、对象布局和二进制读写。移植时单纯让编译器“不报错”，无法证明游戏状态或资源格式仍然正确。[完整核心编译参数](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/MAKEFILE#L85-L129)

一个具体例子是 `FUNCTION.H`：当年的编译器没有原生 `bool`，代码自行声明 `false/true` 并 `typedef int bool`。把它替换成现代一字节 `bool`，有可能改变包含布尔字段的对象大小。这个问题是否影响某条存档路径，要结合该对象的序列化方式检查；不能笼统认定“改成现代写法总是安全”。[历史布尔类型兼容层](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/FUNCTION.H#L140-L151)

因此，在源码分析中，“字段类型”“对象内存”“文件格式”“网络格式”要分别看待。现代工程可以用明确位宽的格式结构、字段级序列化和适配层减少耦合；但那属于移植设计，不是原项目已经具有的保证。

## 1.5 目前缺少什么，哪些只是生成产物

官方首先列出四组需自行寻找、替换或移除使用代码的依赖：DirectX 5 SDK、DirectX Media 5.1 SDK、Greenleaf Communications Library、HMI SOS。这里不是“所有第三方痕迹都不存在”：树中确实能找到部分 SOS 头文件及 `.lib`，但不能据此认定版本、ABI、完整性和授权分发条件全部满足。[官方依赖表](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/README.md#L7-L14)

| 审计项目 | 固定提交中的观察 | 对重建的含义 |
|---|---|---|
| `watcom`、`dxsdk`、`dxmedia`、`gcl510` | 未包含对应完整目录 | 主清单硬编码了相邻路径，需要外部环境或替代实现 |
| `CODE/linker/nwlink`、`CODE/utils/tasm` | 未找到指定工具 | 原命令路径不能直接使用 |
| `CODE/mpgdll.lib`、`uuid.lib` | 未找到链接清单指定文件 | 需审计视频导入符号和系统导入库的来源 |
| `CODE/ipx/wwipx32.lib` | 主清单引用此处；IPX 源码实际在顶层 | 必须明确子项目输出与拷贝步骤，而不是只补搜索路径 |
| `WIN32LIB/LIB`、目标 OBJ、`tech.lib` | 无完整预构建产物 | 很多是待生成结果，缺失本身不是缺源码的证明 |
| `WINVQ` 所需两个播放器库 | 有源代码与 Makefile，未发现目标 `vqa32wp.lib/vqm32wp.lib` | 需要单独构建并校核调用约定 |
| `RULES.INI`、`.MIX/.SHP/.AUD/.VQA` 游戏资源 | 本树未发现这些扩展名的成套游戏资源 | 编译后仍需要合法获得且匹配版本的游戏数据 |

链接清单可逐项验证 DirectDraw、DirectSound、DirectX Media 与 MPEG 库依赖，资源初始化则实际尝试打开 `REDALERT.MIX`、`LOCAL.MIX` 等文件。这里的“未发现”只描述本次固定提交，不推断所有历史发行包或其他仓库。[Win32 导入库清单](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/MAKEFILE#L555-L570)；[游戏资源包入口](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/INIT.CPP#L2981-L2988)

资源制作也另有流水线。`RULES.MAK` 设置光盘、音频、电影、美术和临时 MIX 路径，大量指向当年的盘符与共享目录；它不是运行时 `RULES.INI`。代码构建、资源转换和光盘打包曾经属于同一工作室的生产系统，而公开仓库只保存了其中一部分。[资源制作环境](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/RULES.MAK#L19-L33)；[资源类型路径](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/RULES.MAK#L79-L100)

## 1.6 从工作室的生产方式理解这些构建文件

这个工程还有一个很有教学价值的特点：构建文件不仅描述程序，还携带当时工作室的工作方式。`JOEMAKE.BAT` 会配置本地与共享盘路径、复制头文件和源文件到网络目录，启动 `netexec` 并运行 `wmake`，最后取回目标文件。它的注释明确称其为借助网络从机的编译脚本。这说明开发者已在处理编译耗时和多人协作问题，但采用的是当时环境中的盘符映射、共享目录和外部工具；我们没有这些共享盘，也不能只凭脚本推断当年的分布式编译性能。[网络构建准备](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/JOEMAKE.BAT#L3-L45)；[启动与取回产物](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/JOEMAKE.BAT#L57-L70)

主 Makefile 还把源码、美术、音频、运行文件复制到网络及光盘目录，调用打包工具并生成带日期的测试版本目录。今天常把编译、资源制作、发布分成不同任务，这里则直接写进一个大型 Makefile。它能帮助读者理解：制作游戏不仅是编写单位 AI，还包括把正确资源与正确语言、补丁、程序组合到同一发行介质上。[资源及源码同步](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/MAKEFILE#L573-L585)；[光盘与测试版本准备](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/MAKEFILE#L587-L605)

语言构建也能展示“中间产物隔离”和“最终产物隔离”的区别。`MAKE_ALL.BAT` 依次重建英文、德文、法文；主清单把各自 OBJ 放进语言子目录，但链接命令仍输出相同的 `..\run\ra95.exe`。所以从当前脚本可推断，若这些命令依次成功且没有外部复制步骤，后一次会覆盖前一次 EXE。恢复工程应为各语言保留独立最终目录，不能以“有三个 OBJ 目录”证明“三套发行产物已妥善保存”。[三语言脚本](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/MAKE_ALL.BAT#L1-L4)；[公共 EXE 输出路径](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/CODE/MAKEFILE#L509-L518)

底层库同样是多阶段产物。`WIN32LIB/MAKEFILE` 逐个进入子目录执行 `wmake`，子目录先生成自己的 `.lib`，再由总目录的 `libs.lbc` 将各库组成 `win32lib.lib`。因此游戏链接失败时，应沿依赖向下定位：是某个 CPP 尚未编译、子库未生成、总库未合并，还是最终缺了系统导入符号。直接把所有报错归因于“旧代码不兼容新系统”，反而丢失了可行动的信息。[子库递归构建与总库归档](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/WIN32LIB/MAKEFILE#L195-L217)

## 1.7 正确的研究顺序与恢复目标

如果目的是理解游戏，先沿主程序、对象模型和事件链阅读，暂时不必恢复每一套历史工具。如果目的是运行修改版，先选一个明确目标，例如“Win32 英文、单机、固定资源版本”，再建立依赖清单与实验门槛。不要同时尝试 DOS、DVD、三种语言、拨号、WOL 和全部工具：它们的构建环境并不统一，连 `AUDIOMAK` 都使用 Borland `bcc/tlink` 与大内存模型。[音频工具的独立工具链](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/TOOLS/AUDIOMAK/MAKEFILE#L19-L54)

仓库 README 将本次发布定位为保存用途并支持游戏 Workshop 相关工作，说明仓库不接受贡献、没有支持。它指向 GPL v3 及附加条款，并要求使用编译产物时拥有游戏。附加条款明确涉及商标、来源标识和随附声明等事项；这不意味着游戏美术、配乐与商标随源码自动成为可任意重发的素材。分发前应直接阅读完整 LICENSE，本文只记录仓库自己的说明。[发布及支持定位](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/README.md#L2-L39)；[附加条款](https://github.com/electronicarts/CnC_Red_Alert/blob/0dc09bb1d6188d1ec79ab4765b22a48b75ffee32/LICENSE.md#L600-L617)

### 源码阅读路线

`README.md` → `CODE/MAKEFILE` 的平台分支和目标 → `DEFINES.H` 的版本开关 → `RAWOLAPI.H` 的版本常量 → `WIN32LIB/MAKEFILE` 与 `WINVQ` 子项目 → `RULES.MAK`。完成这一圈后，再读后续游戏架构章节，能持续判断“这段逻辑属于哪个构建、依赖哪一层”。底层实现与恢复实验详见 [第 15 章](15-platform-libraries-tools-and-porting.md)。

### 尚未确认的边界

本次没有恢复 Watcom/TASM 历史环境，没有链接或运行游戏，没有逐个验证随附 EXE 的功能，没有证明公开树与某个商业发行二进制完全一致。当前编译宏可从清单确认；某个历史发行版本究竟启用了哪些宏、带了哪些资源，仍需相应发行产物作对照。
