Files
questshock/res/assets/extract_assets.sh
ml a5ec8db5f7
build / build (push) Failing after 5s
Vendor the Shockolate engine and build it via a Docker image with every
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.
2026-07-19 15:43:38 +02:00

98 lines
4.1 KiB
Bash
Executable File

#!/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."