Vendor the Shockolate engine and build it via a Docker image with every
build / build (push) Failing after 5s

dependency (SDL2, SDL2_mixer, fluidsynth-lite, a MIDI soundfont)
prebuilt, so compiling the engine needs no network access - just the
image and the engine source. Add res/assets/extract_assets.sh to pull
the game's data files out of a purchased GOG installer, and a Makefile
that assembles a runnable dist/ from the two.

Also add `make package`, which builds a redistributable tarball that
omits the proprietary game assets (shipping res/GET_ASSETS.txt instead)
plus license information for both the MIT tooling and the GPLv3 engine,
and a Gitea Actions workflow that builds and publishes it to
dl.ladkau.de on every push.
This commit is contained in:
ml
2026-07-19 15:43:38 +02:00
parent 3ed6400466
commit a5ec8db5f7
826 changed files with 214554 additions and 0 deletions
+16
View File
@@ -0,0 +1,16 @@
This package does not include System Shock's game data - it's
copyrighted, proprietary content that can't be redistributed. To play,
you need a copy of System Shock: Enhanced Edition (e.g. from gog.com).
From your Enhanced Edition install, you need its classic-game data and
sound files - the res/pc/hd/data and res/pc/cdrom/data trees merged
together (res/pc/hd's copies win the couple of filenames present in
both: intro.res, objprop.dat), and the res/pc/hd/sound tree, packed
inside the install's sshock.kpf (a zip file).
Copy that merged data into place, alongside this file:
res/data/ <- res/pc/hd/data + res/pc/cdrom/data, merged
res/sound/ <- res/pc/hd/sound
Once res/data/ and res/sound/ exist next to this file, run ./run.sh from
the root of this package to play.
+97
View File
@@ -0,0 +1,97 @@
#!/usr/bin/env bash
# Extracts the game data Shockolate needs out of a purchased System Shock:
# Enhanced Edition installer from gog.com, into ./ss_ee/{data,sound}.
#
# Usage:
# 1. Buy System Shock: Enhanced Edition on gog.com and download the
# offline installer (a Windows .exe, e.g.
# setup_system_shock_enhanced_edition_1.2.16_(64bit)_(44378).exe).
# 2. Drop it in this directory (res/assets/).
# 3. Run this script: ./extract_assets.sh
#
# The installer is an Inno Setup package; the actual game data lives
# inside it in sshock.kpf, which is just a zip file. Its res/pc/hd/ and
# res/pc/cdrom/ trees together hold every classic-game data/sound file
# Shockolate's res/ folder expects (res/pc/hd wins the couple of
# filenames present in both: intro.res, objprop.dat). Needs innoextract
# and unzip; if they're not installed locally, this script does the
# extraction inside a throwaway Ubuntu container instead (needs docker in
# that case, nothing else).
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")"
ASSETS_DIR="$(pwd)"
OUT_DIR="$ASSETS_DIR/ss_ee"
fail() { echo "PREFLIGHT FAIL: $*" >&2; exit 1; }
shopt -s nullglob
installers=(setup_system_shock_enhanced_edition*.exe)
shopt -u nullglob
case "${#installers[@]}" in
0) fail "no setup_system_shock_enhanced_edition*.exe found in $ASSETS_DIR - buy System Shock: Enhanced Edition on gog.com, download the offline installer, and drop it here" ;;
1) ;;
*) fail "multiple installer .exe files found in $ASSETS_DIR - keep only one: ${installers[*]}" ;;
esac
INSTALLER="${installers[0]}"
# Extracts sshock.kpf from $1 (an installer .exe) and assembles $2 (an
# ss_ee output directory) from its res/pc/{hd,cdrom} trees. Defined as a
# function so the exact same logic can run either natively or, via
# `declare -f`, inside a throwaway container.
run_extraction() {
set -euo pipefail
local installer="$1" out_dir="$2"
# Deliberately not `local`: an EXIT trap set here fires when the whole
# shell process exits, which is after this function - and this
# function's own scope - has already returned, so a `local` var would
# already be unset by then (unbound-variable error under `set -u`).
work="$(mktemp -d)"
trap 'rm -rf "${work:-}"' EXIT
echo "== Extracting sshock.kpf from installer =="
innoextract --silent --include sshock.kpf -d "$work/installer" "$installer"
echo "== Unpacking sshock.kpf (data/sound trees only) =="
mkdir -p "$work/kpf"
unzip -q "$work/installer/sshock.kpf" \
'res/pc/hd/data/*' 'res/pc/hd/sound/*' 'res/pc/cdrom/data/*' \
-d "$work/kpf"
echo "== Assembling $out_dir =="
rm -rf "$out_dir"
mkdir -p "$out_dir/data" "$out_dir/sound"
# cdrom/data first (cutscenes, audio logs, and shared tables only it
# has), then hd/data on top (wins the 2 overlapping filenames:
# intro.res, objprop.dat - the hd install's own copies).
cp -a "$work/kpf/res/pc/cdrom/data/." "$out_dir/data/"
cp -a "$work/kpf/res/pc/hd/data/." "$out_dir/data/"
cp -a "$work/kpf/res/pc/hd/sound/." "$out_dir/sound/"
}
if command -v innoextract >/dev/null 2>&1 && command -v unzip >/dev/null 2>&1; then
echo "== Using local innoextract/unzip =="
run_extraction "$ASSETS_DIR/$INSTALLER" "$OUT_DIR"
else
echo "== innoextract/unzip not found locally - using a throwaway docker container =="
command -v docker >/dev/null 2>&1 \
|| fail "docker not found in PATH (needed since innoextract/unzip aren't installed locally)"
docker run --rm \
-v "$ASSETS_DIR:/assets" \
-e HOST_UID="$(id -u)" \
-e HOST_GID="$(id -g)" \
ubuntu:22.04 \
bash -c "
set -euo pipefail
apt-get update -qq >/dev/null
apt-get install -y -qq innoextract unzip >/dev/null
$(declare -f run_extraction)
run_extraction '/assets/$INSTALLER' '/assets/ss_ee'
chown -R \"\$HOST_UID:\$HOST_GID\" /assets/ss_ee
"
fi
echo "== Done =="
echo "Assets extracted to $OUT_DIR"
echo "Note: mfdfrn.res/mfdger.res (French/German MFD art) are not present in this"
echo "installer and are only needed for those localizations - not for English play."
Executable
+7
View File
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
# Launches System Shock. Copied next to the systemshock binary in dist/
# by the root Makefile.
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")"
export LD_LIBRARY_PATH="$(pwd)/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
exec ./systemshock "$@"