2 Commits
23 changed files with 400 additions and 268 deletions
+72 -40
View File
@@ -6,14 +6,11 @@ variables:
DOCKER_HOST: tcp://docker:2375
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: ""
# Where to push the dev image. Leave empty to not push at all.
# Examples: "192.168.1.11:9090/<group>/openra3" or "docker.io/<user>/openra3"
IMAGE_NAME: ""
DOCKER_BUILDKIT: "1"
# apt mirror used inside both images (override per pipeline if needed)
APT_MIRROR: "http://mirrors.tuna.tsinghua.edu.cn/ubuntu"
image_build:
stage: image
image: docker:latest
.dind: &dind
services:
- name: docker:dind
# Pull docker.io images through the intranet Harbor pull-through cache;
@@ -21,44 +18,79 @@ image_build:
command:
- --registry-mirror=http://192.168.1.11:9090/dockerhub
- --insecure-registry=192.168.1.11:9090
script:
- docker build --target dev -t openra3-dev:local --build-arg APT_MIRROR=$APT_MIRROR .
- docker save openra3-dev:local -o dev-image.tar
- |
if [ -n "$IMAGE_NAME" ]; then
# add `docker login` here if IMAGE_NAME points to a private registry
docker tag openra3-dev:local $IMAGE_NAME:$CI_COMMIT_SHORT_SHA
docker push $IMAGE_NAME:$CI_COMMIT_SHORT_SHA
fi
artifacts:
name: "dev-image-$CI_COMMIT_SHORT_SHA"
paths:
- dev-image.tar
expire_in: 1 day
# --- build the two isolated toolchain images ---------------------------------
linux_image:
stage: image
image: docker:latest
services: *dind
tags:
- docker
script:
- docker build --target dev -t openra3-linux:local --build-arg APT_MIRROR=$APT_MIRROR .
- docker save openra3-linux:local -o linux-image.tar
artifacts:
name: "linux-image-$CI_COMMIT_SHORT_SHA"
paths:
- linux-image.tar
expire_in: 1 day
build_test_package:
windows_image:
stage: image
image: docker:latest
services: *dind
tags:
- docker
script:
- docker build -f Dockerfile.win --target dev -t openra3-windows:local --build-arg APT_MIRROR=$APT_MIRROR .
- docker save openra3-windows:local -o windows-image.tar
artifacts:
name: "windows-image-$CI_COMMIT_SHORT_SHA"
paths:
- windows-image.tar
expire_in: 1 day
# --- build and test each target ----------------------------------------------
build_linux:
stage: build
image: docker:latest
services:
- name: docker:dind
command:
- --registry-mirror=http://192.168.1.11:9090/dockerhub
- --insecure-registry=192.168.1.11:9090
needs:
- image_build
before_script:
- docker load -i dev-image.tar
script:
- 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
- docker run --rm -v "$(pwd):/work" -w /work openra3-dev:local bash -c "mkdir -p artifacts_bin && cp build/bin/openra3 artifacts_bin/"
artifacts:
name: "$CI_PROJECT_NAME-executables-$CI_COMMIT_SHORT_SHA"
paths:
- artifacts_bin/
expire_in: 7 days
services: *dind
tags:
- docker
needs:
- linux_image
before_script:
- docker load -i linux-image.tar
script:
- 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 run --rm -v "$(pwd):/work" -w /work openra3-linux:local bash -c "mkdir -p artifacts/linux && cp build/linux/bin/openra3 artifacts/linux/"
artifacts:
name: "$CI_PROJECT_NAME-linux-$CI_COMMIT_SHORT_SHA"
paths:
- artifacts/linux/
expire_in: 7 days
build_windows:
stage: build
image: docker:latest
services: *dind
tags:
- docker
needs:
- windows_image
before_script:
- docker load -i windows-image.tar
script:
- 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
- docker run --rm -v "$(pwd):/work" -w /work openra3-windows:local bash -c "mkdir -p artifacts/windows && cp build/windows/bin/openra3.exe build/windows/bin/SDL3.dll artifacts/windows/"
artifacts:
name: "$CI_PROJECT_NAME-windows-$CI_COMMIT_SHORT_SHA"
paths:
- artifacts/windows/
expire_in: 7 days
+86 -67
View File
@@ -1,10 +1,31 @@
cmake_minimum_required(VERSION 3.28)
cmake_minimum_required(VERSION 3.30)
project(OpenRA3 VERSION 0.0.1 LANGUAGES CXX)
# --- C++26 modules + `import std;` (clang + libc++) -------------------------
# CMake gates `import std` behind an experimental value documented in
# Help/dev/experimental.rst for the CMake version in use.
set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "d0edc3af-4c50-42ea-a356-e2862fe7a444")
# Path to the standard-library module description. A cross toolchain overrides
# this before the compiler is probed; this default targets the Linux LLVM
# package.
if(NOT CMAKE_CXX_STDLIB_MODULES_JSON)
set(CMAKE_CXX_STDLIB_MODULES_JSON "/usr/lib/llvm-21/lib/libc++.modules.json")
endif()
# libc++ must be selected before the compiler is probed: CMake builds its
# internal standard-library module target during `project()`, and that target
# only sees `CMAKE_CXX_FLAGS` (not later `add_compile_options`). The standard
# and extension settings must also be in place before `project()` so the
# internal `std.pcm` is built with the same configuration as our modules.
set(CMAKE_CXX_FLAGS_INIT "-stdlib=libc++")
set(CMAKE_EXE_LINKER_FLAGS_INIT "-stdlib=libc++")
set(CMAKE_SHARED_LINKER_FLAGS_INIT "-stdlib=libc++")
set(CMAKE_CXX_STANDARD 26)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_SCAN_FOR_MODULES ON)
project(OpenRA3 VERSION 0.1.0 LANGUAGES CXX)
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
@@ -14,122 +35,120 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
option(OPENRA3_WERROR "Treat warnings as errors" OFF)
# Windows: statically link the C++ runtime (libc++, libunwind) so the .exe only
# needs SDL3.dll next to it at runtime.
if(WIN32)
add_link_options(-static)
endif()
function(openra3_target_defaults target)
target_compile_options(${target} PRIVATE -Wall -Wextra -Wpedantic)
target_compile_options(${target} PRIVATE -Wall -Wextra)
set_property(TARGET ${target} PROPERTY CXX_MODULE_STD ON)
if(OPENRA3_WERROR)
target_compile_options(${target} PRIVATE -Werror)
endif()
endfunction()
# --- engine core: fundamental types, math, strings, random, message stream ---
# --- SDL3: pkg-config on Linux, or an explicit MinGW root for Windows --------
add_library(openra3_sdl3 INTERFACE)
set(OPENRA3_HAS_SDL3 OFF)
if(OPENRA3_SDL3_ROOT)
target_include_directories(openra3_sdl3 INTERFACE "${OPENRA3_SDL3_ROOT}/x86_64-w64-mingw32/include")
target_link_libraries(openra3_sdl3 INTERFACE "${OPENRA3_SDL3_ROOT}/x86_64-w64-mingw32/lib/libSDL3.dll.a")
set(OPENRA3_HAS_SDL3 ON)
else()
find_package(PkgConfig QUIET)
if(PkgConfig_FOUND)
pkg_check_modules(SDL3 QUIET IMPORTED_TARGET sdl3)
endif()
if(SDL3_FOUND)
target_link_libraries(openra3_sdl3 INTERFACE PkgConfig::SDL3)
set(OPENRA3_HAS_SDL3 ON)
endif()
endif()
if(OPENRA3_HAS_SDL3)
message(STATUS "OpenRA3: SDL3 found - windowed viewer enabled")
else()
message(STATUS "OpenRA3: SDL3 not found - offscreen BMP output only")
endif()
# --- engine core -------------------------------------------------------------
add_library(ra3_core STATIC)
target_sources(ra3_core
PUBLIC FILE_SET CXX_MODULES FILES
src/core/ra3.core.cppm
)
target_sources(ra3_core PUBLIC FILE_SET CXX_MODULES FILES src/core/ra3.core.cppm)
openra3_target_defaults(ra3_core)
# --- game logic: objects, players, teams, spatial partition, game loop ---
# --- game logic --------------------------------------------------------------
add_library(ra3_logic STATIC)
target_sources(ra3_logic
PUBLIC FILE_SET CXX_MODULES FILES
src/logic/ra3.logic.cppm
)
target_sources(ra3_logic PUBLIC FILE_SET CXX_MODULES FILES src/logic/ra3.logic.cppm)
target_link_libraries(ra3_logic PUBLIC ra3_core)
openra3_target_defaults(ra3_logic)
# --- client: headless display/input abstraction and the client facade ---
# --- client ------------------------------------------------------------------
add_library(ra3_client STATIC)
target_sources(ra3_client
PUBLIC FILE_SET CXX_MODULES FILES
src/client/ra3.client.cppm
)
target_sources(ra3_client PUBLIC FILE_SET CXX_MODULES FILES src/client/ra3.client.cppm)
target_link_libraries(ra3_client PUBLIC ra3_core ra3_logic)
openra3_target_defaults(ra3_client)
# --- RA3 game definitions: factions, player templates, science, special powers ---
# --- RA3 game definitions ----------------------------------------------------
add_library(ra3_game STATIC)
target_sources(ra3_game
PUBLIC FILE_SET CXX_MODULES FILES
src/game/ra3.game.cppm
)
target_sources(ra3_game PUBLIC FILE_SET CXX_MODULES FILES src/game/ra3.game.cppm)
target_link_libraries(ra3_game PUBLIC ra3_core ra3_logic)
openra3_target_defaults(ra3_game)
# --- filesystem: BIG4 archives, RefPack codec, local install locator ---
# --- filesystem: BIG4 + RefPack ---------------------------------------------
add_library(ra3_fs STATIC)
target_sources(ra3_fs
PUBLIC FILE_SET CXX_MODULES FILES
src/fs/ra3.fs.cppm
)
target_sources(ra3_fs PUBLIC FILE_SET CXX_MODULES FILES src/fs/ra3.fs.cppm)
target_link_libraries(ra3_fs PUBLIC ra3_core)
openra3_target_defaults(ra3_fs)
# --- map discovery/loading (real .big data, user's local install) ---
# --- map discovery/loading ---------------------------------------------------
add_library(ra3_map STATIC)
target_sources(ra3_map
PUBLIC FILE_SET CXX_MODULES FILES
src/map/ra3.map.cppm
)
target_sources(ra3_map PUBLIC FILE_SET CXX_MODULES FILES src/map/ra3.map.cppm)
target_link_libraries(ra3_map PUBLIC ra3_core ra3_fs)
openra3_target_defaults(ra3_map)
# --- minimal headless skirmish simulation ---
# --- minimal skirmish simulation --------------------------------------------
add_library(ra3_skirmish STATIC)
target_sources(ra3_skirmish
PUBLIC FILE_SET CXX_MODULES FILES
src/skirmish/ra3.skirmish.cppm
)
target_sources(ra3_skirmish PUBLIC FILE_SET CXX_MODULES FILES src/skirmish/ra3.skirmish.cppm)
target_link_libraries(ra3_skirmish PUBLIC ra3_core ra3_logic ra3_game ra3_map)
openra3_target_defaults(ra3_skirmish)
# --- software renderer: framebuffer, TGA decode, BMP encode, map compositing ---
# --- software renderer -------------------------------------------------------
add_library(ra3_render STATIC)
target_sources(ra3_render
PUBLIC FILE_SET CXX_MODULES FILES
src/render/ra3.render.cppm
)
target_sources(ra3_render PUBLIC FILE_SET CXX_MODULES FILES src/render/ra3.render.cppm)
target_link_libraries(ra3_render PUBLIC ra3_core)
openra3_target_defaults(ra3_render)
# --- windowed viewer: SDL3 when available, otherwise a null backend ---
find_package(PkgConfig QUIET)
if(PkgConfig_FOUND)
pkg_check_modules(SDL3 QUIET IMPORTED_TARGET sdl3)
endif()
# --- windowed viewer (SDL3 or null) -----------------------------------------
add_library(ra3_ui STATIC)
if(SDL3_FOUND)
target_sources(ra3_ui
PUBLIC FILE_SET CXX_MODULES FILES
src/ui/ra3.ui.sdl.cppm
)
target_link_libraries(ra3_ui PUBLIC ra3_core ra3_render PkgConfig::SDL3)
message(STATUS "OpenRA3: SDL3 ${SDL3_VERSION} found - windowed viewer enabled")
if(OPENRA3_HAS_SDL3)
target_sources(ra3_ui PUBLIC FILE_SET CXX_MODULES FILES src/ui/ra3.ui.sdl.cppm)
target_link_libraries(ra3_ui PUBLIC ra3_core ra3_render openra3_sdl3)
else()
target_sources(ra3_ui
PUBLIC FILE_SET CXX_MODULES FILES
src/ui/ra3.ui.null.cppm
)
target_sources(ra3_ui PUBLIC FILE_SET CXX_MODULES FILES src/ui/ra3.ui.null.cppm)
target_link_libraries(ra3_ui PUBLIC ra3_core ra3_render)
message(STATUS "OpenRA3: SDL3 not found - offscreen BMP output only")
endif()
openra3_target_defaults(ra3_ui)
# --- umbrella module re-exporting the whole SDK ---
# --- umbrella -----------------------------------------------------------------
add_library(ra3 STATIC)
target_sources(ra3
PUBLIC FILE_SET CXX_MODULES FILES
src/ra3.cppm
)
target_sources(ra3 PUBLIC FILE_SET CXX_MODULES FILES src/ra3.cppm)
target_link_libraries(ra3 PUBLIC ra3_core ra3_logic ra3_client ra3_game ra3_fs ra3_map ra3_skirmish ra3_render ra3_ui)
openra3_target_defaults(ra3)
# --- the headless game executable ---
# --- executable ---------------------------------------------------------------
add_executable(openra3 apps/openra3/main.cpp)
target_link_libraries(openra3 PRIVATE ra3)
openra3_target_defaults(openra3)
# --- unit tests ---
if(WIN32 AND OPENRA3_SDL3_ROOT)
add_custom_command(TARGET openra3 POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${OPENRA3_SDL3_ROOT}/x86_64-w64-mingw32/bin/SDL3.dll"
"$<TARGET_FILE_DIR:openra3>")
endif()
# --- tests --------------------------------------------------------------------
enable_testing()
add_executable(ra3_tests tests/ra3_tests.cpp)
target_link_libraries(ra3_tests PRIVATE ra3)
+19 -17
View File
@@ -1,7 +1,9 @@
# syntax=docker/dockerfile:1
# dev target: full toolchain + debug tools for development and debugging.
# Base image and compiler are the latest stable (ubuntu:26.04 ships gcc/g++ 16).
# Linux build environment. Isolated from the Windows cross image (Dockerfile.win)
# so the two toolchains and their standard libraries never interfere.
#
# dev target: clang + libc++ (for `import std;`) + SDL3 + debug tools.
FROM ubuntu:26.04 AS dev
ENV DEBIAN_FRONTEND=noninteractive
@@ -23,44 +25,44 @@ RUN --mount=type=cache,target=/var/lib/apt,sharing=locked \
sleep 5; \
done \
&& apt-get -o Acquire::Retries=5 install -y --no-install-recommends \
build-essential \
ca-certificates \
clang-21 \
clang-tools-21 \
cmake \
g++-16 \
gdb \
git \
libc++-21-dev \
libc++abi-21-dev \
libsdl3-dev \
ninja-build \
pkg-config \
&& rm -rf /var/lib/apt/lists/*
# Pin the toolchain to GCC 16 (the distro default is still GCC 15).
ENV CC=gcc-16
ENV CXX=g++-16
RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-16 100 \
&& update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-16 100 \
&& update-alternatives --install /usr/bin/c++ c++ /usr/bin/g++-16 100
ENV CC=clang-21
ENV CXX=clang++-21
WORKDIR /work
COPY . .
# C++26 modules need CMake >= 3.28, the Ninja generator and a module-aware gcc.
RUN cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release \
&& cmake --build build -j \
&& ctest --test-dir build --output-on-failure
RUN cmake -S . -B build/linux -G Ninja -DCMAKE_BUILD_TYPE=Release \
&& cmake --build build/linux -j \
&& ctest --test-dir build/linux --output-on-failure
# deploy target: runtime dependencies + final binary, minimal and fast.
FROM ubuntu:26.04 AS deploy
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get -o Acquire::Retries=5 update \
RUN --mount=type=cache,target=/var/lib/apt,sharing=locked \
--mount=type=cache,target=/var/cache/apt,sharing=locked \
apt-get -o Acquire::Retries=5 update \
&& apt-get -o Acquire::Retries=5 install -y --no-install-recommends \
libc++1 \
libc++abi1 \
libsdl3-0 \
libstdc++6 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=dev /work/build/bin/openra3 /app/openra3
COPY --from=dev /work/build/linux/bin/openra3 /app/openra3
ENTRYPOINT ["/app/openra3"]
+63
View File
@@ -0,0 +1,63 @@
# syntax=docker/dockerfile:1
# Windows cross-build environment (x86_64), fully isolated from the Linux image
# in Dockerfile so the two toolchains and standard libraries never interfere.
#
# Toolchain: llvm-mingw (clang + libc++ + the libc++ `std` module, MSVCRT
# runtime) with the official SDL3 MinGW development package.
FROM ubuntu:26.04 AS dev
ENV DEBIAN_FRONTEND=noninteractive
ARG APT_MIRROR=""
RUN if [ -n "$APT_MIRROR" ]; then \
sed -i -e "s|http://archive.ubuntu.com/ubuntu|${APT_MIRROR}|g" \
-e "s|http://security.ubuntu.com/ubuntu|${APT_MIRROR}|g" \
-e "s|http://ports.ubuntu.com/ubuntu-ports|${APT_MIRROR}|g" \
/etc/apt/sources.list /etc/apt/sources.list.d/*.sources 2>/dev/null || true; \
fi
RUN --mount=type=cache,target=/var/lib/apt,sharing=locked \
--mount=type=cache,target=/var/cache/apt,sharing=locked \
for attempt in 1 2 3 4 5; do \
apt-get -o Acquire::Retries=5 update && break; \
echo "apt-get update failed (attempt $attempt), retrying"; \
sleep 5; \
done \
&& apt-get -o Acquire::Retries=5 install -y --no-install-recommends \
ca-certificates \
cmake \
curl \
ninja-build \
xz-utils \
&& rm -rf /var/lib/apt/lists/*
# Pinned toolchain + SDL3 versions (override with --build-arg to bump).
ARG LLVM_MINGW_VERSION=20260908
ARG LLVM_MINGW_FLAVOR=msvcrt
ARG SDL3_VERSION=3.4.16
RUN mkdir -p /opt/llvm-mingw /opt/sdl3-mingw \
&& curl -fL "https://github.com/mstorsjo/llvm-mingw/releases/download/${LLVM_MINGW_VERSION}/llvm-mingw-${LLVM_MINGW_VERSION}-${LLVM_MINGW_FLAVOR}-ubuntu-22.04-x86_64.tar.xz" \
| tar -xJ -C /opt/llvm-mingw --strip-components=1 \
&& curl -fL "https://github.com/libsdl-org/SDL/releases/download/release-${SDL3_VERSION}/SDL3-devel-${SDL3_VERSION}-mingw.tar.gz" \
| tar -xz -C /opt/sdl3-mingw --strip-components=1
ENV OPENRA3_LLVM_MINGW_ROOT=/opt/llvm-mingw
ENV OPENRA3_SDL3_ROOT=/opt/sdl3-mingw
WORKDIR /work
COPY . .
RUN cmake -S . -B build/windows -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/llvm-mingw-x86_64.cmake \
-DCMAKE_BUILD_TYPE=Release \
&& cmake --build build/windows -j
# package target: the .exe plus the SDL3 runtime DLL.
FROM ubuntu:26.04 AS package
WORKDIR /app
COPY --from=dev /work/build/windows/bin/openra3.exe /app/
COPY --from=dev /work/build/windows/bin/SDL3.dll /app/
+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`.
+1 -1
View File
@@ -1 +1 @@
0.1.0
0.2.1
+1 -10
View File
@@ -1,13 +1,4 @@
#include <algorithm>
#include <cstdio>
#include <exception>
#include <filesystem>
#include <fstream>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
import std;
import ra3;
namespace {
+43
View File
@@ -0,0 +1,43 @@
# Cross-compile for Windows x86_64 with llvm-mingw: clang + libc++ + the
# libc++ `std` module, targeting the MSVCRT MinGW runtime (which is what the
# official SDL3 MinGW package links against).
# The `import std` gate must be set before the compiler is probed; a toolchain
# file is processed earlier than the project's own top-level settings.
set(CMAKE_EXPERIMENTAL_CXX_IMPORT_STD "d0edc3af-4c50-42ea-a356-e2862fe7a444")
set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_SYSTEM_PROCESSOR x86_64)
set(OPENRA3_LLVM_MINGW_ROOT "/opt/llvm-mingw" CACHE PATH "llvm-mingw installation root")
if(NOT EXISTS "${OPENRA3_LLVM_MINGW_ROOT}/bin/clang++")
message(FATAL_ERROR "llvm-mingw not found at ${OPENRA3_LLVM_MINGW_ROOT} (expected bin/clang++)")
endif()
set(CMAKE_C_COMPILER "${OPENRA3_LLVM_MINGW_ROOT}/bin/clang")
set(CMAKE_CXX_COMPILER "${OPENRA3_LLVM_MINGW_ROOT}/bin/clang++")
set(CMAKE_C_COMPILER_TARGET x86_64-w64-mingw32)
set(CMAKE_CXX_COMPILER_TARGET x86_64-w64-mingw32)
# CMake's standard-library detection does not pass COMPILER_TARGET, so without
# this it probes the host standard library. Putting the target in ARG1 makes
# the probe see libc++ (the MinGW target's library).
set(CMAKE_C_COMPILER_ARG1 "--target=x86_64-w64-mingw32")
set(CMAKE_CXX_COMPILER_ARG1 "--target=x86_64-w64-mingw32")
# libc++ standard-library module description for the MinGW target. Must be set
# before the compiler is probed so CMake can build `import std;`.
set(CMAKE_CXX_STDLIB_MODULES_JSON "${OPENRA3_LLVM_MINGW_ROOT}/x86_64-w64-mingw32/lib/libc++.modules.json")
# Select libc++ explicitly so CMake's `import std` detection recognises the
# standard library (llvm-mingw defaults to it, but CMake keys off -stdlib=).
set(CMAKE_CXX_FLAGS_INIT "-stdlib=libc++")
set(CMAKE_EXE_LINKER_FLAGS_INIT "-stdlib=libc++")
set(CMAKE_SHARED_LINKER_FLAGS_INIT "-stdlib=libc++")
# Official SDL3 MinGW development package (headers + import library + DLL).
set(OPENRA3_SDL3_ROOT "/opt/sdl3-mingw" CACHE PATH "SDL3 MinGW development package root")
set(CMAKE_FIND_ROOT_PATH "${OPENRA3_LLVM_MINGW_ROOT}/x86_64-w64-mingw32")
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+4 -2
View File
@@ -1,7 +1,9 @@
# Architecture
OpenRA3 is organised as a stack of C++ modules. Each layer may import the ones
below it, never the ones above.
OpenRA3 is organised as a stack of C++26 modules. Each layer may import the ones
below it, never the ones above. Every module uses `import std;` for the standard
library; the engine is built with Clang + libc++ on Linux and cross-compiled
with llvm-mingw for Windows.
```
┌───────────────────────────────────────┐
+4
View File
@@ -14,6 +14,10 @@ that each one produces something that builds and runs.
- [x] **v0.1.0 — map renderer + window.** Software ARGB framebuffer, TGA
decoder for the map art, BMP output, map/grid/unit compositing, and an
SDL3 window viewer (pan/zoom).
- [x] **v0.2.0 — dual toolchain.** Switched to Clang + libc++ with
`import std;` in every module; added an isolated Windows cross-build
(llvm-mingw + SDL3 MinGW) producing `openra3.exe` + `SDL3.dll`, alongside
the Linux build. Both images are separate to keep the toolchains apart.
## Next
+14
View File
@@ -0,0 +1,14 @@
#!/usr/bin/env bash
# Build the Linux target inside its own isolated image (Dockerfile).
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
IMAGE="${OPENRA3_LINUX_IMAGE:-openra3-linux:local}"
BUILD_DIR="${1:-build/linux}"
docker build --target dev -t "$IMAGE" "$ROOT"
docker run --rm -v "$ROOT:/work" -w /work "$IMAGE" cmake -S . -B "$BUILD_DIR" -G Ninja -DCMAKE_BUILD_TYPE=Release
docker run --rm -v "$ROOT:/work" -w /work "$IMAGE" cmake --build "$BUILD_DIR" -j
docker run --rm -v "$ROOT:/work" -w /work "$IMAGE" ctest --test-dir "$BUILD_DIR" --output-on-failure
echo "Linux build: $ROOT/$BUILD_DIR/bin/openra3"
+16
View File
@@ -0,0 +1,16 @@
#!/usr/bin/env bash
# Build the Windows x86_64 target inside its own isolated image (Dockerfile.win).
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
IMAGE="${OPENRA3_WINDOWS_IMAGE:-openra3-windows:local}"
BUILD_DIR="${1:-build/windows}"
docker build --target dev -f "$ROOT/Dockerfile.win" -t "$IMAGE" "$ROOT"
docker run --rm -v "$ROOT:/work" -w /work "$IMAGE" \
cmake -S . -B "$BUILD_DIR" -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/llvm-mingw-x86_64.cmake \
-DCMAKE_BUILD_TYPE=Release
docker run --rm -v "$ROOT:/work" -w /work "$IMAGE" cmake --build "$BUILD_DIR" -j
echo "Windows build: $ROOT/$BUILD_DIR/bin/openra3.exe (+ SDL3.dll)"
+2 -6
View File
@@ -1,11 +1,7 @@
module;
#include <cstdint>
#include <memory>
#include <string_view>
export module ra3.client;
import std;
import ra3.core;
import ra3.logic;
+4 -17
View File
@@ -1,20 +1,7 @@
module;
#include <cmath>
#include <compare>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <optional>
#include <ostream>
#include <span>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
export module ra3.core;
import std;
/**
* Fundamental engine types shared by every OpenRA3 module.
*
@@ -24,8 +11,8 @@ export module ra3.core;
*/
export namespace ra3::core {
inline constexpr int version_major = 0;
inline constexpr int version_minor = 1;
inline constexpr int version_patch = 0;
inline constexpr int version_minor = 2;
inline constexpr int version_patch = 1;
using real = float;
using int32 = std::int32_t;
+2 -18
View File
@@ -1,23 +1,7 @@
module;
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <optional>
#include <span>
#include <stdexcept>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
export module ra3.fs;
import std;
export import ra3.core;
/**
+2 -7
View File
@@ -1,12 +1,7 @@
module;
#include <cstdint>
#include <string>
#include <string_view>
#include <vector>
export module ra3.game;
import std;
import ra3.core;
import ra3.logic;
+2 -12
View File
@@ -1,17 +1,7 @@
module;
#include <cstddef>
#include <cstdint>
#include <memory>
#include <span>
#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <vector>
export module ra3.logic;
import std;
import ra3.core;
/**
+2 -13
View File
@@ -1,18 +1,7 @@
module;
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstring>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
export module ra3.map;
import std;
export import ra3.core;
export import ra3.fs;
+3 -14
View File
@@ -1,18 +1,7 @@
module;
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <span>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
export module ra3.render;
import std;
export import ra3.core;
/**
@@ -223,7 +212,7 @@ export namespace ra3::render {
out[at + 2U] = static_cast<uint8>(value >> 16U);
out[at + 3U] = static_cast<uint8>(value >> 24U);
};
auto put16 = [&out](usize at, uint16_t value) {
auto put16 = [&out](usize at, std::uint16_t value) {
out[at] = static_cast<uint8>(value);
out[at + 1U] = static_cast<uint8>(value >> 8U);
};
+2 -11
View File
@@ -1,16 +1,7 @@
module;
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <span>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
export module ra3.skirmish;
import std;
export import ra3.core;
export import ra3.map;
export import ra3.game;
+1 -4
View File
@@ -1,9 +1,6 @@
module;
#include <string>
export module ra3.ui;
import std;
export import ra3.core;
import ra3.render;
+1 -3
View File
@@ -2,11 +2,9 @@ module;
#include <SDL3/SDL.h>
#include <algorithm>
#include <string>
export module ra3.ui;
import std;
export import ra3.core;
import ra3.render;
+1 -9
View File
@@ -1,12 +1,4 @@
#include <cstdint>
#include <cstdio>
#include <filesystem>
#include <fstream>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
import std;
import ra3;
namespace {