v0.2.0: clang + import std; isolated Windows cross-build (llvm-mingw + SDL3)

This commit is contained in:
EnderTheCoder
2026-09-12 02:04:25 +08:00
parent b35372da52
commit 1d12e4e099
23 changed files with 393 additions and 267 deletions
+55 -17
View File
@@ -1,7 +1,8 @@
# OpenRA3
A from-scratch, portable re-implementation of **Command & Conquer: Red Alert 3**
in **C++26** using **C++ modules**, built with **GCC 16**.
in **C++26** using **C++ modules** and `import std;`, built with **Clang** for
both **Linux** and **Windows**.
Red Alert 3 runs SAGE 2.0. EA never released that engine's C++ source, but it
did open-source the closely related SAGE 1.0 engine as
@@ -17,11 +18,13 @@ Ghidra.
## Status
OpenRA3 is at **v0.0.1**. The engine compiles and runs headless, and a **minimal
OpenRA3 is at **v0.2.0**. The engine compiles and runs headless, and a **minimal
skirmish** is playable: it reads a real multiplayer map out of your install,
recovers the player start waypoints, and simulates two sides earning credits,
training units and fighting until one base falls. The map and match state can be
**rendered** to a window (SDL3) or to an image.
**rendered** to a window (SDL3) or to an image. The whole tree builds for
**Linux** (clang + libc++) and cross-compiles to **Windows** (`openra3.exe` +
`SDL3.dll`) with llvm-mingw — both using C++26 modules and `import std;`.
```text
$ openra3 skirmish --game-dir "/game" --map map_mp_2_feasel4
@@ -73,19 +76,48 @@ built-in test map so the project still builds and runs in CI.
| `tests/ra3_tests.cpp` | smoke tests (run via `ctest`) |
| `tools/` | reference fetch + Ghidra-driven reconstruction helpers |
| `docs/` | architecture, reverse-engineering notes, roadmap |
| `Dockerfile` | `dev` (toolchain) and `deploy` (runtime) targets |
| `.gitlab-ci.yml` | build → test → package pipeline |
| `Dockerfile` | Linux build image (`dev` toolchain + `deploy` runtime) |
| `Dockerfile.win` | isolated Windows cross-build image (llvm-mingw + SDL3 MinGW) |
| `cmake/toolchains/` | `llvm-mingw-x86_64.cmake` cross toolchain |
| `scripts/` | `build-linux.sh` / `build-windows.sh` one-shot builders |
| `.gitlab-ci.yml` | build both targets → test → package pipeline |
## Why Clang + `import std;`
The engine never `#include`s the standard library: every module does
`import std;`. CMake's support for that (`CXX_MODULE_STD`) works today with
Clang + libc++. Linux uses the distro clang (LLVM 21); Windows cross-compiles
with [llvm-mingw](https://github.com/mstorsjo/llvm-mingw) (clang 23 + libc++ +
the libc++ `std` module). The two toolchains live in **separate images** so
their compilers and standard libraries never interfere.
## Build
The host needs no toolchain: build inside the container.
The host needs no toolchain: each target builds inside its own image.
```bash
docker build --target dev -t openra3-dev:local .
# Linux -> build/linux/bin/openra3
scripts/build-linux.sh
docker run --rm -v "$PWD:/work" -w /work openra3-dev:local cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
docker run --rm -v "$PWD:/work" -w /work openra3-dev:local cmake --build build -j
docker run --rm -v "$PWD:/work" -w /work openra3-dev:local ctest --test-dir build --output-on-failure
# Windows -> build/windows/bin/openra3.exe (+ SDL3.dll)
scripts/build-windows.sh
```
Or drive Docker directly:
```bash
docker build --target dev -t openra3-linux:local .
docker run --rm -v "$PWD:/work" -w /work openra3-linux:local \
cmake -S . -B build/linux -G Ninja -DCMAKE_BUILD_TYPE=Release
docker run --rm -v "$PWD:/work" -w /work openra3-linux:local cmake --build build/linux -j
docker run --rm -v "$PWD:/work" -w /work openra3-linux:local ctest --test-dir build/linux --output-on-failure
docker build -f Dockerfile.win --target dev -t openra3-windows:local .
docker run --rm -v "$PWD:/work" -w /work openra3-windows:local \
cmake -S . -B build/windows -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/llvm-mingw-x86_64.cmake \
-DCMAKE_BUILD_TYPE=Release
docker run --rm -v "$PWD:/work" -w /work openra3-windows:local cmake --build build/windows -j
```
Behind a slow or blocked mirror, pass `--build-arg APT_MIRROR=<url>`.
@@ -94,14 +126,17 @@ Behind a slow or blocked mirror, pass `--build-arg APT_MIRROR=<url>`.
```bash
# list the maps in your install
docker run --rm -v "/path/to/Red Alert 3:/game:ro" openra3-dev:local \
./build/bin/openra3 maps --game-dir /game
docker run --rm -v "/path/to/Red Alert 3:/game:ro" openra3-linux:local \
/work/build/linux/bin/openra3 maps --game-dir /game
# play a headless skirmish on a real map
docker run --rm -v "/path/to/Red Alert 3:/game:ro" openra3-dev:local \
./build/bin/openra3 skirmish --game-dir /game --map map_mp_2_feasel4 --seed 7
docker run --rm -v "/path/to/Red Alert 3:/game:ro" openra3-linux:local \
/work/build/linux/bin/openra3 skirmish --game-dir /game --map map_mp_2_feasel4 --seed 7
```
The Windows build is a native `openra3.exe` — copy it next to `SDL3.dll` and run
it from `cmd`/PowerShell, e.g. `openra3.exe skirmish --game-dir "C:\Red Alert 3"`.
`--frames N` caps the simulation length (default 15 minutes of game time at
30 Hz). The result is deterministic for a given map and seed.
@@ -113,10 +148,13 @@ The renderer draws the map's own overview art, a world grid and the match state
**Offscreen image** (works anywhere, no display needed):
```bash
docker run --rm -v "/path/to/Red Alert 3:/game:ro" -v "$PWD/out:/out" openra3-dev:local \
/work/build/bin/openra3 render --game-dir /game --map map_mp_2_feasel4 --out /out/map.bmp
docker run --rm -v "/path/to/Red Alert 3:/game:ro" -v "$PWD/out:/out" openra3-linux:local \
/work/build/linux/bin/openra3 render --game-dir /game --map map_mp_2_feasel4 --out /out/map.bmp
```
On Windows the same command runs natively: `openra3.exe render --game-dir
"C:\Red Alert 3" --map map_mp_2_feasel4` opens an SDL3 window.
**Interactive window** (SDL3; drag to pan, wheel to zoom, Esc to quit). In a
container you need an X server on the host — on Windows run
[VcXsrv](https://sourceforge.net/projects/vcxsrv/) and launch it with "Disable
@@ -124,7 +162,7 @@ access control", then:
```bash
docker run --rm -v "/path/to/Red Alert 3:/game:ro" -e DISPLAY=host.docker.internal:0.0 \
openra3-dev:local /work/build/bin/openra3 render --game-dir /game --map map_mp_2_feasel4
openra3-linux:local /work/build/linux/bin/openra3 render --game-dir /game --map map_mp_2_feasel4
```
If no display is available the viewer falls back to writing `openra3_view.bmp`.