git: 2ac9e4a8713d - main - games/CWR-CE: New port
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Thu, 02 Jul 2026 00:54:05 UTC
The branch main has been updated by olivier:
URL: https://cgit.FreeBSD.org/ports/commit/?id=2ac9e4a8713de9ed3443db974b6e410c21f90cbf
commit 2ac9e4a8713de9ed3443db974b6e410c21f90cbf
Author: Olivier Cochard <olivier@FreeBSD.org>
AuthorDate: 2026-07-02 00:50:48 +0000
Commit: Olivier Cochard <olivier@FreeBSD.org>
CommitDate: 2026-07-02 00:53:20 +0000
games/CWR-CE: New port
Game engine of Arma: Cold War Assault - Remastered
---
games/CWR-CE/Makefile | 79 +++++++
games/CWR-CE/distinfo | 7 +
games/CWR-CE/files/cwr-ce.sh | 41 ++++
games/CWR-CE/files/install-cwr-data.sh | 392 +++++++++++++++++++++++++++++++++
games/CWR-CE/files/pkg-message.in | 31 +++
games/CWR-CE/pkg-descr | 15 ++
games/Makefile | 1 +
7 files changed, 566 insertions(+)
diff --git a/games/CWR-CE/Makefile b/games/CWR-CE/Makefile
new file mode 100644
index 000000000000..614406cd3e5c
--- /dev/null
+++ b/games/CWR-CE/Makefile
@@ -0,0 +1,79 @@
+PORTNAME= CWR-CE
+DISTVERSIONPREFIX= v
+DISTVERSION= 3.01
+CATEGORIES= games
+
+PATCH_SITES= https://github.com/ocochard/CWR-CE/compare/
+PATCHFILES= 9abbdf2769f3008d76c47ac0af219388ff0323dc..b6c9690f03cf0abb6af577db7294b00c30f4172c.patch:-p1 \
+ b6c9690f03cf0abb6af577db7294b00c30f4172c..85dd554fc9e21312ee52d7f9eced6b171a6a892b.patch:-p1
+
+MAINTAINER= olivier@FreeBSD.org
+COMMENT= Arma: Cold War Assault - Remastered Community Edition
+WWW= https://github.com/ofpisnotdead-com/CWR-CE
+
+LICENSE= GPLv3+
+LICENSE_FILE= ${WRKSRC}/LICENSE
+
+BUILD_DEPENDS= stb>0:devel/stb \
+ ${LOCALBASE}/include/CLI/CLI.hpp:devel/cli11 \
+ ${LOCALBASE}/include/vulkan/vulkan.h:graphics/vulkan-headers \
+ ${LOCALBASE}/lib/cmake/Catch2/Catch2Config.cmake:devel/catch2 \
+ ${LOCALBASE}/lib/cmake/glslang/glslang-config.cmake:graphics/glslang
+LIB_DEPENDS= libcjson.so:devel/libcjson \
+ libcurl.so:ftp/curl \
+ libenkiTS.so:devel/enkits \
+ libfmt.so:devel/libfmt \
+ libfreetype.so:print/freetype2 \
+ libimgui.so:x11-toolkits/imgui \
+ libmimalloc.so:devel/mimalloc \
+ libogg.so:audio/libogg \
+ libopenal.so:audio/openal-soft \
+ libopus.so:audio/opus \
+ libSDL3.so:devel/sdl3 \
+ libvulkan.so:graphics/vulkan-loader \
+ libspdlog.so:devel/spdlog \
+ libvorbis.so:audio/libvorbis \
+ libzstd.so:archivers/zstd
+
+USES= cmake compiler:c++20-lang dos2unix gl localbase pkgconfig
+
+DOS2UNIX_REGEX= .*\.(c|h|cpp|hpp)
+USE_GITHUB= yes
+GH_ACCOUNT= ofpisnotdead-com
+GH_PROJECT= CWR-CE
+GH_TAGNAME= 9abbdf2769f3008d76c47ac0af219388ff0323dc
+USE_GL= gl
+
+SUB_FILES= pkg-message
+
+# Disable vcpkg toolchain wiring — find_package(... CONFIG REQUIRED) picks up
+# system libraries from ${LOCALBASE}/lib/cmake instead.
+CMAKE_ARGS= -DVCPKG_MANIFEST_MODE=OFF \
+ -DCMAKE_DISABLE_FIND_PACKAGE_VCPKG=ON
+
+CMAKE_OFF= POSEIDON_DISABLE_PCH POSEIDON_BUILD_FUZZERS
+
+PLIST_FILES= bin/PoseidonGame \
+ bin/PoseidonGameDemo \
+ bin/PoseidonServer \
+ bin/PoseidonStudio \
+ bin/PoseidonTetris \
+ bin/PoseidonTools \
+ bin/cwr-ce \
+ bin/install-cwr-data
+
+do-install:
+ ${INSTALL_PROGRAM} \
+ ${BUILD_WRKSRC}/apps/cwr/Game/PoseidonGame \
+ ${BUILD_WRKSRC}/apps/cwr/GameDemo/PoseidonGameDemo \
+ ${BUILD_WRKSRC}/apps/cwr/Server/PoseidonServer \
+ ${BUILD_WRKSRC}/apps/tools/Studio/PoseidonStudio \
+ ${BUILD_WRKSRC}/apps/tetris/Tetris/PoseidonTetris \
+ ${BUILD_WRKSRC}/apps/tools/Tools/PoseidonTools \
+ ${STAGEDIR}${PREFIX}/bin
+ ${INSTALL_SCRIPT} ${FILESDIR}/install-cwr-data.sh \
+ ${STAGEDIR}${PREFIX}/bin/install-cwr-data
+ ${INSTALL_SCRIPT} ${FILESDIR}/cwr-ce.sh \
+ ${STAGEDIR}${PREFIX}/bin/cwr-ce
+
+.include <bsd.port.mk>
diff --git a/games/CWR-CE/distinfo b/games/CWR-CE/distinfo
new file mode 100644
index 000000000000..670262e54b4a
--- /dev/null
+++ b/games/CWR-CE/distinfo
@@ -0,0 +1,7 @@
+TIMESTAMP = 1782950316
+SHA256 (ofpisnotdead-com-CWR-CE-v3.01-9abbdf2769f3008d76c47ac0af219388ff0323dc_GH0.tar.gz) = 87df6297ed74993d56d3ceec9d90fb9287cebc58f47030a285a2eafb61c3ce22
+SIZE (ofpisnotdead-com-CWR-CE-v3.01-9abbdf2769f3008d76c47ac0af219388ff0323dc_GH0.tar.gz) = 4944610
+SHA256 (9abbdf2769f3008d76c47ac0af219388ff0323dc..b6c9690f03cf0abb6af577db7294b00c30f4172c.patch) = 8add6877aae7534967c2f609026cd4c4aa861a41f27d6cd68ec89c052e56d31d
+SIZE (9abbdf2769f3008d76c47ac0af219388ff0323dc..b6c9690f03cf0abb6af577db7294b00c30f4172c.patch) = 23346
+SHA256 (b6c9690f03cf0abb6af577db7294b00c30f4172c..85dd554fc9e21312ee52d7f9eced6b171a6a892b.patch) = d87ab832d44d46ae761c8a920463a70b5e8369477a0eda327b4be6995b139e7f
+SIZE (b6c9690f03cf0abb6af577db7294b00c30f4172c..85dd554fc9e21312ee52d7f9eced6b171a6a892b.patch) = 18184
diff --git a/games/CWR-CE/files/cwr-ce.sh b/games/CWR-CE/files/cwr-ce.sh
new file mode 100644
index 000000000000..bb4b232a4a99
--- /dev/null
+++ b/games/CWR-CE/files/cwr-ce.sh
@@ -0,0 +1,41 @@
+#!/bin/sh
+# cwr-ce — launcher for PoseidonGame (Arma: Cold War Assault Remastered CE).
+#
+# Resolves the data root (from $CWR_DATA or the default per-user path),
+# verifies a minimal sentinel file exists, then execs PoseidonGame with
+# -C <data-root> and forwards any remaining arguments.
+
+set -u
+
+DATA_ROOT="${CWR_DATA:-${HOME}/.local/share/CWR/base}"
+# The Steam Demo ships bin/CONFIG.BIN (uppercase); an overlay-installed layout
+# may lowercase it. Accept either — the engine itself is case-insensitive.
+if [ -f "${DATA_ROOT}/bin/config.bin" ] || [ -f "${DATA_ROOT}/bin/CONFIG.BIN" ]; then
+ :
+else
+ cat >&2 <<EOF
+cwr-ce: CWR-CE data set not found under: ${DATA_ROOT}
+ (missing: bin/config.bin or bin/CONFIG.BIN)
+
+The engine binaries alone are not enough — you must install a data root
+first. Obtain the Steam "Arma: Cold War Assault Remastered Demo" (free)
+and optionally the retail 2001 game from GOG:
+
+ https://store.steampowered.com/app/4819000/
+ https://www.gog.com/game/arma_cold_war_assault
+
+Then populate the data root with the install helper shipped by this port:
+
+ install-cwr-data --help
+
+By default the helper installs into \$HOME/.local/share/CWR/base. To use a
+different location, set \$CWR_DATA before launching:
+
+ export CWR_DATA=/path/to/data-root
+ cwr-ce
+
+EOF
+ exit 1
+fi
+
+exec PoseidonGame -C "${DATA_ROOT}" "$@"
diff --git a/games/CWR-CE/files/install-cwr-data.sh b/games/CWR-CE/files/install-cwr-data.sh
new file mode 100644
index 000000000000..a20b64bc4b7c
--- /dev/null
+++ b/games/CWR-CE/files/install-cwr-data.sh
@@ -0,0 +1,392 @@
+#!/bin/sh
+# install-cwr-data.sh — populate a CWR-CE data root from:
+# the Steam Demo (default):
+# https://store.steampowered.com/app/4819000/Arma_Cold_War_Assault_Remastered_Demo/
+# and optionally overlay 2001 retail (GOG) content on top:
+# https://www.gog.com/game/arma_cold_war_assault
+#
+# The Poseidon engine reads its data root from $CWD or --work-dir and expects
+# (case-sensitive on POSIX — FindBank/unixPath translates path separators only):
+# <root>/dta/ (lowercase)
+# <root>/addons/ (lowercase)
+# <root>/bin/ (lowercase, holds config.bin/resource.bin/stringtable.csv)
+# <root>/Campaigns/ (PascalCase)
+# <root>/Worlds/ <root>/Missions/ <root>/MPMissions/
+# <root>/Templates/ <root>/SPTemplates/
+# <root>/fonts/ (cwr_*.ttf — only ships with the Remaster Demo)
+#
+# Per CWR-CE upstream: the binaries are not drop-in compatible with the original
+# 2001 retail folder. The supported layout is "Remaster Demo as base + retail
+# AddOns/DTA/Campaigns/Missions overlaid on top".
+#
+# Config merge (bin/CONFIG.BIN):
+# The Steam Demo's bin/CONFIG.BIN is the *sole* engine config authority — no
+# .pbo under dta/ contains a config.bin, they are pure asset archives. The
+# demo config carries the CWR-CE remaster additions (CfgLandscapeSky, sky
+# models, fonts, CfgLanguages) that a plain retail BIN/config.bin would
+# regress, which is why the demo BIN is kept as base and the retail BIN is
+# NOT overlaid wholesale.
+#
+# However, the demo config is a strict subset of the retail one:
+# - CfgVehicles: retail defines ~110 classes the demo omits — Cobra/Mi24/
+# Mi17 (helicopters), A10 (jet), M1Abrams/T80 (tanks), Skoda/Cessna
+# (civilians), the SoldierG* resistance roster, reammo boxes, targets,
+# props, wrecks — everything retail campaigns and missions reference.
+# - CfgVoice: retail defines Dan/Greg/John/Peter (the classic ARMA:CWA
+# voice actors); the demo only ships Adam. CfgHQIdentities entries in
+# the demo config still reference speaker="Dan"/"Greg", so any radio
+# message crashes on class lookup without the merge.
+# - CfgGroups: retail group compositions that place the missing vehicles.
+#
+# With --gog, this script performs a class-level *additive* merge via
+# 'PoseidonTools config merge': base = demo CONFIG.BIN (kept verbatim),
+# overlay = retail BIN/CONFIG.BIN (adds missing subclasses only, never
+# overwrites the remaster-specific tweaks). Without this step the engine
+# crashes the first time a retail mission references an unknown class.
+
+set -eu
+
+usage()
+{
+ cat >&2 <<EOF
+Usage: $0 [options] <demo.zip>
+
+Installs the Steam "Arma: Cold War Assault Remastered Demo" zip into a data
+root the engine can run from. The retail (GOG) game can additionally be
+overlaid on top to enable the full islands and campaign.
+
+Arguments:
+ <demo.zip> Path to the zipped Steam Demo install folder.
+ Optional if --gog is provided AND --dest already
+ holds a previous demo install.
+
+Options:
+ -g, --gog <file> Path to setup_arma_cold_war_assault_*.exe (GOG retail
+ installer). Its AddOns/DTA/Campaigns/Missions/... are
+ overlaid on top of the demo base. Requires innoextract.
+ --dest <dir> Destination data root (default: \$HOME/.local/share/CWR/base).
+ --force Overwrite a non-empty destination.
+ --keep-temp Leave the unpacked staging directory in place.
+ -h, --help Show this help.
+
+Examples:
+ $0 ~/Arma\\ Cold\\ War\\ Assault\\ Demo.zip
+ $0 ~/demo.zip --gog ~/setup_arma_cold_war_assault_1.0_36421.exe
+ $0 --gog ~/setup.exe --force # overlay GOG onto an existing demo install
+
+Required tools: unzip (always), innoextract (only when --gog is used).
+
+How to produce <demo.zip> from a Windows machine running Steam:
+ 1. In Steam, install "Arma: Cold War Assault Remastered Demo" (free,
+ app 4819000).
+ 2. Right-click the title in your Library -> Manage -> Browse local files.
+ That opens the install folder (e.g.
+ C:\\Program Files (x86)\\Steam\\steamapps\\common\\Arma Cold War Assault Demo).
+ 3. In Explorer, right-click that folder ->
+ "Send to" -> "Compressed (zipped) folder",
+ or "Compress to" -> "ZIP file" (Windows 11).
+ 4. Copy the resulting .zip to this machine (scp, USB stick, ...) and pass
+ its path to this script.
+EOF
+ exit 1
+}
+
+DEST="${HOME}/.local/share/CWR/base"
+FORCE=0
+KEEP_TEMP=0
+DEMO_ZIP=
+GOG_EXE=
+
+while [ $# -gt 0 ]; do
+ case "$1" in
+ -g|--gog) [ $# -ge 2 ] || usage; GOG_EXE="$2"; shift 2 ;;
+ --dest) [ $# -ge 2 ] || usage; DEST="$2"; shift 2 ;;
+ --force) FORCE=1; shift ;;
+ --keep-temp) KEEP_TEMP=1; shift ;;
+ -h|--help) usage ;;
+ --) shift; break ;;
+ -*) echo "unknown option: $1" >&2; usage ;;
+ *) [ -z "$DEMO_ZIP" ] || usage; DEMO_ZIP="$1"; shift ;;
+ esac
+done
+
+# Either the demo zip or a pre-existing dest+gog combination must be present.
+if [ -z "$DEMO_ZIP" ] && [ -z "$GOG_EXE" ]; then
+ usage
+fi
+if [ -n "$DEMO_ZIP" ] && [ ! -f "$DEMO_ZIP" ]; then
+ echo "demo zip not found: $DEMO_ZIP" >&2; exit 1
+fi
+if [ -n "$GOG_EXE" ] && [ ! -f "$GOG_EXE" ]; then
+ echo "GOG installer not found: $GOG_EXE" >&2; exit 1
+fi
+
+# Detect the host package manager once and remember the install command.
+# pkg(8) on FreeBSD is checked first because that is the primary platform for
+# this script; the others are best-effort for Linux distros that happen to
+# ship the same tools.
+detect_pkg_install()
+{
+ if command -v pkg >/dev/null 2>&1; then
+ echo "sudo pkg install -y"; return
+ fi
+ if command -v apt-get >/dev/null 2>&1; then
+ echo "sudo apt-get install -y"; return
+ fi
+ if command -v dnf >/dev/null 2>&1; then
+ echo "sudo dnf install -y"; return
+ fi
+ if command -v pacman >/dev/null 2>&1; then
+ echo "sudo pacman -S --noconfirm"; return
+ fi
+ return 1
+}
+
+# Ensure $1 is on PATH. If missing, offer to install it via the host package
+# manager (same package name, which holds for unzip/innoextract on all targets
+# above). Aborts the script if the user declines or the install fails.
+require_tool()
+{
+ name="$1"
+ command -v "$name" >/dev/null 2>&1 && return 0
+
+ install_cmd="$(detect_pkg_install || true)"
+ if [ -z "$install_cmd" ]; then
+ echo "'$name' is required but not found, and no supported package manager" >&2
+ echo "was detected. Install '$name' manually and rerun." >&2
+ exit 1
+ fi
+
+ printf "'%s' is required but missing. Install it with '%s %s'? [Y/n] " \
+ "$name" "$install_cmd" "$name"
+ # Read from the controlling terminal so piped invocations still prompt.
+ if [ -r /dev/tty ]; then
+ read -r reply </dev/tty || reply=n
+ else
+ read -r reply || reply=n
+ fi
+ case "$reply" in
+ ""|y|Y|yes|YES) ;;
+ *) echo "aborted." >&2; exit 1 ;;
+ esac
+
+ # shellcheck disable=SC2086
+ $install_cmd "$name" || {
+ echo "package install failed; aborting." >&2; exit 1; }
+ command -v "$name" >/dev/null 2>&1 || {
+ echo "'$name' still not on PATH after install; aborting." >&2; exit 1; }
+}
+
+# Tool checks scoped to what we actually need this invocation.
+if [ -n "$DEMO_ZIP" ]; then
+ require_tool unzip
+fi
+if [ -n "$GOG_EXE" ]; then
+ require_tool innoextract
+fi
+
+# Non-empty dest is only acceptable when we're explicitly overlaying GOG onto
+# an existing demo, or the caller passed --force.
+if [ "$FORCE" -ne 1 ] && [ -d "$DEST" ] && [ -n "$(ls -A "$DEST" 2>/dev/null || true)" ]; then
+ if [ -n "$DEMO_ZIP" ]; then
+ echo "destination is not empty: $DEST (use --force to overwrite)" >&2
+ exit 1
+ fi
+ # GOG-only run with existing dest: treat as an overlay; require the demo
+ # base to be present, otherwise the engine would still fail on fonts/.
+ if [ ! -d "$DEST/fonts" ] || [ ! -d "$DEST/bin" ]; then
+ echo "destination exists but does not look like a demo install: $DEST" >&2
+ echo "(missing fonts/ or bin/ — install the demo first, or pass <demo.zip>)" >&2
+ exit 1
+ fi
+fi
+mkdir -p "$DEST"
+
+TMP="$(mktemp -d -t cwr-data.XXXXXX)"
+cleanup()
+{
+ if [ "$KEEP_TEMP" -ne 1 ]; then
+ rm -rf "$TMP"
+ else
+ echo "staging left at: $TMP"
+ fi
+}
+trap cleanup EXIT INT TERM
+
+# Locate a directory under $1 case-insensitively (handles both GOG's "DTA"/
+# "AddOns" and the demo's "DTA"/"AddOns" without coupling to either).
+locate_dir()
+{
+ base="$1"; needle="$2"
+ [ -d "$base" ] || return 1
+ lc_needle="$(printf '%s' "$needle" | tr '[:upper:]' '[:lower:]')"
+ for entry in "$base"/*; do
+ [ -d "$entry" ] || continue
+ lc_name="$(printf '%s' "$(basename "$entry")" | tr '[:upper:]' '[:lower:]')"
+ if [ "$lc_name" = "$lc_needle" ]; then
+ printf '%s\n' "$entry"
+ return 0
+ fi
+ done
+ return 1
+}
+
+# Copy a source tree on top of $DEST. Destination name fixed by the caller so
+# we can lowercase DTA/AddOns/BIN regardless of source casing.
+install_dir()
+{
+ src="$1"; dest_name="$2"
+ [ -d "$src" ] || return 0
+ echo " -> $dest_name/"
+ mkdir -p "$DEST/$dest_name"
+ cp -R "$src/." "$DEST/$dest_name/"
+}
+
+# ---------------------------------------------------------------------------
+# Phase 1: Steam Demo (base)
+# ---------------------------------------------------------------------------
+if [ -n "$DEMO_ZIP" ]; then
+ echo "==> unpacking demo: $(basename "$DEMO_ZIP")"
+ DEMO_STAGE="$TMP/demo"
+ mkdir -p "$DEMO_STAGE"
+ unzip -q "$DEMO_ZIP" -d "$DEMO_STAGE"
+
+ # Steam-shipped zips usually wrap everything in a single top-level folder
+ # ("Arma Cold War Assault Demo/"). If there is exactly one such directory,
+ # descend into it. Otherwise assume the zip was made from inside the dir.
+ DEMO_SRC="$DEMO_STAGE"
+ entries=0
+ only_dir=
+ for e in "$DEMO_STAGE"/*; do
+ entries=$((entries + 1))
+ [ -d "$e" ] && only_dir="$e"
+ done
+ if [ "$entries" -eq 1 ] && [ -n "$only_dir" ]; then
+ DEMO_SRC="$only_dir"
+ fi
+
+ # Lowercase the three engine-literal dirs.
+ for pair in "DTA dta" "AddOns addons" "BIN bin"; do
+ src_name="${pair% *}"; dst_name="${pair#* }"
+ src="$(locate_dir "$DEMO_SRC" "$src_name" || true)"
+ if [ -n "$src" ]; then
+ install_dir "$src" "$dst_name"
+ fi
+ done
+
+ # Strip Windows-only payload from bin/ if present.
+ find "$DEST/bin" -type f \( -iname '*.dll' -o -iname '*.exe' \) -delete 2>/dev/null || true
+
+ # Preserve every other top-level directory verbatim (engine reads each by
+ # exact name). Covers Campaigns/, Worlds/, Missions/, MPMissions/,
+ # Templates/, SPTemplates/, fonts/, demo/, dtaExt/, Music/, …
+ for entry in "$DEMO_SRC"/*; do
+ [ -d "$entry" ] || continue
+ name="$(basename "$entry")"
+ case "$(printf '%s' "$name" | tr '[:upper:]' '[:lower:]')" in
+ dta|addons|bin) continue ;;
+ esac
+ install_dir "$entry" "$name"
+ done
+
+ # Loose top-level data files (config.cfg-style metadata). Drop Windows
+ # binaries and license blurbs.
+ for f in "$DEMO_SRC"/*; do
+ [ -f "$f" ] || continue
+ name="$(basename "$f")"
+ case "$name" in
+ *.exe|*.dll|*.url|*.ico|*.pdf|*.txt) continue ;;
+ PoseidonGameDemo|PoseidonGameDemo.*) continue ;;
+ esac
+ echo " -> $name"
+ cp "$f" "$DEST/$name"
+ done
+fi
+
+# ---------------------------------------------------------------------------
+# Phase 2: GOG retail overlay (optional)
+# ---------------------------------------------------------------------------
+# Per CWR-CE issue #29 the supported merge is: demo as base + retail's
+# AddOns/DTA/Campaigns/Missions/Worlds/Templates overlaid. The retail BIN/ is
+# NOT copied wholesale — it would replace CWR-CE's remaster-aware config
+# (fonts, sky models, CfgLandscapeSky) — but its class definitions ARE merged
+# class-by-class into the demo config, see the header comment and Phase 3.
+if [ -n "$GOG_EXE" ]; then
+ echo "==> extracting GOG installer: $(basename "$GOG_EXE")"
+ GOG_STAGE="$TMP/gog"
+ mkdir -p "$GOG_STAGE"
+ innoextract --silent --gog -d "$GOG_STAGE" "$GOG_EXE"
+
+ # Most GOG bundles drop payload at the extraction root; some create app/
+ # holding only GOG metadata. Pick whichever actually has DTA/AddOns/BIN.
+ GOG_SRC=
+ for candidate in "$GOG_STAGE" "$GOG_STAGE/app"; do
+ for d in DTA dta AddOns addons; do
+ if [ -d "$candidate/$d" ]; then
+ GOG_SRC="$candidate"; break 2
+ fi
+ done
+ done
+ [ -n "$GOG_SRC" ] || { echo "no game data found under $GOG_STAGE" >&2; exit 2; }
+
+ echo "==> overlaying retail content onto $DEST"
+ # Lowercased dirs (overlay merges file-by-file via cp -R).
+ for pair in "DTA dta" "AddOns addons"; do
+ src_name="${pair% *}"; dst_name="${pair#* }"
+ src="$(locate_dir "$GOG_SRC" "$src_name" || true)"
+ if [ -n "$src" ]; then
+ install_dir "$src" "$dst_name"
+ fi
+ done
+
+ # PascalCase content dirs — engine reads literal names. NOT BIN/ — that
+ # dir is handled by the config-merge phase below, not a plain cp.
+ for d in Campaigns Worlds Missions MPMissions Templates SPTemplates; do
+ src="$(locate_dir "$GOG_SRC" "$d" || true)"
+ [ -n "$src" ] || continue
+ name="$(basename "$src")"
+ install_dir "$src" "$name"
+ done
+
+ # -----------------------------------------------------------------------
+ # Phase 3: merge retail CfgVehicles/CfgVoice/CfgGroups into demo CONFIG.BIN
+ # -----------------------------------------------------------------------
+ # 'PoseidonTools config merge base overlay -o out' walks the overlay
+ # class tree and *adds* any subclass the base lacks, without touching
+ # entries the base already defines. That gives us:
+ # - all retail vehicles, weapons, ammo the campaigns/missions need,
+ # - the Dan/Greg/John/Peter CfgVoice classes referenced by
+ # CfgHQIdentities (radio messages crash without them),
+ # while preserving CWR-CE's remaster-only additions (CfgLandscapeSky,
+ # CfgLanguages, remaster sky/font entries) that a wholesale copy of the
+ # retail config would silently drop.
+ GOG_BIN_SRC="$(locate_dir "$GOG_SRC" "BIN" || true)"
+ if [ -n "$GOG_BIN_SRC" ] && [ -f "$GOG_BIN_SRC/CONFIG.BIN" ] \
+ && [ -f "$DEST/bin/CONFIG.BIN" ] \
+ && command -v PoseidonTools >/dev/null 2>&1; then
+ echo "==> merging retail class definitions into demo CONFIG.BIN"
+ cp "$DEST/bin/CONFIG.BIN" "$DEST/bin/CONFIG.BIN.demo-orig"
+ PoseidonTools config merge \
+ "$DEST/bin/CONFIG.BIN.demo-orig" \
+ "$GOG_BIN_SRC/CONFIG.BIN" \
+ -o "$DEST/bin/CONFIG.BIN"
+ elif [ -n "$GOG_BIN_SRC" ] && [ -f "$GOG_BIN_SRC/CONFIG.BIN" ]; then
+ echo "WARNING: PoseidonTools not on PATH; skipping config merge." >&2
+ echo " Retail missions will crash on missing vehicle/voice classes" >&2
+ echo " until you run:" >&2
+ echo " PoseidonTools config merge $DEST/bin/CONFIG.BIN \\" >&2
+ echo " $GOG_BIN_SRC/CONFIG.BIN -o $DEST/bin/CONFIG.BIN.new" >&2
+ fi
+fi
+
+cat <<EOF
+
+Done. Data root: $DEST
+
+Run the game from that directory:
+ cd "$DEST" && PoseidonGame
+or pass it via --work-dir:
+ PoseidonGame --work-dir "$DEST"
+ PoseidonServer --work-dir "$DEST"
+
+EOF
diff --git a/games/CWR-CE/files/pkg-message.in b/games/CWR-CE/files/pkg-message.in
new file mode 100644
index 000000000000..0d8e83c92184
--- /dev/null
+++ b/games/CWR-CE/files/pkg-message.in
@@ -0,0 +1,31 @@
+[
+{ type: install
+ message: <<EOM
+CWR-CE installs the engine binaries (PoseidonGame, PoseidonServer,
+PoseidonStudio, PoseidonTools, PoseidonTetris, PoseidonGameDemo) but
+no game data. The original "Arma: Cold War Assault" assets are required
+to run anything beyond Tetris and are NOT redistributable.
+
+If you own the game (e.g. via GOG), install the data with:
+
+ pkg install innoextract
+ install-cwr-data /path/to/setup_arma_cold_war_assault_*.exe
+
+This unpacks DTA/, AddOns/, BIN/, Campaigns/, Worlds/, ... into
+~/.local/share/CWR/base/ in the layout the engine expects. Pass
+--dest <dir> to install elsewhere, or --help for the full usage.
+
+Then launch the game with:
+
+ cwr-ce
+
+The launcher resolves the data root and passes it to PoseidonGame.
+If you installed the data elsewhere, set $CWR_DATA before launching:
+
+ CWR_DATA=/path/to/data-root cwr-ce
+
+Any extra arguments are forwarded to PoseidonGame (see --help).
+
+EOM
+}
+]
diff --git a/games/CWR-CE/pkg-descr b/games/CWR-CE/pkg-descr
new file mode 100644
index 000000000000..21da95fb12b0
--- /dev/null
+++ b/games/CWR-CE/pkg-descr
@@ -0,0 +1,15 @@
+Arma: Cold War Assault - Remastered (codename Poseidon) is the engine
+and game source code first released in 2001 as Operation Flashpoint:
+Cold War Crisis. It is the codebase that launched Bohemia Interactive
+and began the technology lineage later grown into Real Virtuality, Arma,
+and Enfusion. The code has been modernized to C++20, built with CMake
+and Clang.
+
+Game assets (models, textures, sounds, missions, voices) are NOT
+included with this port and are NOT GPL. They ship separately under the
+Arma Public License Share Alike (APL-SA).
+A free Demo is available on Steam:
+https://store.steampowered.com/app/4819000
+And this game was tested with the full GOG version game assets too.
+
+Without game data the binaries do nothing useful at runtime.
diff --git a/games/Makefile b/games/Makefile
index 4e6bf3dd3931..79d0c15f6a12 100644
--- a/games/Makefile
+++ b/games/Makefile
@@ -11,6 +11,7 @@
SUBDIR += 90secondportraits
SUBDIR += Bugdom
SUBDIR += Bugdom2
+ SUBDIR += CWR-CE
SUBDIR += CaribbeanStud
SUBDIR += DDNet
SUBDIR += Doom64EX