Add a language option to the VR menu, and fix a crash switching to it
build / build (push) Successful in 2m22s

- New "Language" row on the Main Menu tab (English/French/German),
  backed by a new "general" group in questshock.config.json
- Fixes a device-reproduced crash: switching language used to call
  language_dealfunc(), whose render_run()/opanel_redraw() repaint
  assumes the game's own in-game options panel is on screen - crashes
  (SIGSEGV) when triggered from our independent VR overlay instead.
  Now calls language_change() directly, which is all that's actually
  needed to reload resources and refresh the inventory/MFD/side-icon UI
- Fixes a second crash: this data set ships frnstrng.res/gerstrng.res
  but not mfdfrn.res/mfdger.res, and language_change() closes the
  current resource files before discovering the new ones don't exist -
  leaving no valid resource file open and crashing on the next lookup.
  Now checks both files exist before ever switching, refusing the
  change (with an in-headset popup) instead of corrupting resource state
- extract_assets.sh can now also pull mfdfrn.res/mfdger.res out of a
  System Shock: Classic installer (which includes them, unlike Enhanced
  Edition) if one is present in res/assets/ - entirely optional
- Documented all of the above in README.md/CONFIG.md
This commit is contained in:
ml
2026-09-05 08:09:12 +02:00
parent 09280dd410
commit c0619a55ab
9 changed files with 422 additions and 48 deletions
+64 -6
View File
@@ -13,10 +13,23 @@
# 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).
# filenames present in both: intro.res, objprop.dat), EXCEPT
# mfdfrn.res/mfdger.res (French/German MFD art - see CONFIG.md/README.md),
# which that installer doesn't include at all - see "Optional: French/
# German MFD art" below for where those instead come from. 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).
#
# Optional: French/German MFD art
# The Enhanced Edition installer's data set doesn't include
# mfdfrn.res/mfdger.res, so French/German are otherwise missing their
# MFD (Multi-Function Display) art and the game refuses to switch to
# them (see CONFIG.md). System Shock: Classic (also gog.com) does
# include both files. If you also own it, drop its offline installer
# (e.g. setup_system_shock_classic_2.0.0.3.exe) in this directory too -
# entirely optional, this script still runs fine without it - and this
# script pulls just those two files out of it into ss_ee/data/ as well.
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")"
@@ -27,6 +40,7 @@ fail() { echo "PREFLIGHT FAIL: $*" >&2; exit 1; }
shopt -s nullglob
installers=(setup_system_shock_enhanced_edition*.exe)
classic_installers=(setup_system_shock_classic*.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" ;;
@@ -35,6 +49,15 @@ case "${#installers[@]}" in
esac
INSTALLER="${installers[0]}"
# Optional - see "Optional: French/German MFD art" above. Empty (not
# missing/failed) when absent; only a genuine ambiguity (more than one
# candidate file) is a hard failure.
case "${#classic_installers[@]}" in
0) CLASSIC_INSTALLER="" ;;
1) CLASSIC_INSTALLER="${classic_installers[0]}" ;;
*) fail "multiple setup_system_shock_classic*.exe files found in $ASSETS_DIR - keep only one: ${classic_installers[*]}" ;;
esac
# 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
@@ -69,9 +92,38 @@ run_extraction() {
cp -a "$work/kpf/res/pc/hd/sound/." "$out_dir/sound/"
}
# Extracts mfdfrn.res/mfdger.res from $1 (a System Shock: Classic installer
# .exe) into $2 (an existing ss_ee output directory's data/ subfolder) -
# see "Optional: French/German MFD art" above. Unlike the Enhanced Edition
# installer, Classic's data files sit directly in the Inno Setup package
# (no intermediate zip), so this is just a targeted innoextract --include,
# no unzip step needed. Same declare -f-into-docker structure as
# run_extraction() above.
run_classic_mfd_extraction() {
set -euo pipefail
local installer="$1" out_dir="$2"
work="$(mktemp -d)"
trap 'rm -rf "${work:-}"' EXIT
echo "== Extracting mfdfrn.res/mfdger.res from classic installer =="
innoextract --silent \
--include 'app/SSHOCK/DATA/MFDFRN.RES' --include 'app/SSHOCK/DATA/MFDGER.RES' \
-d "$work/classic" "$installer"
# Original DOS-era filenames are uppercase; Shockolate's resource
# loader expects the lowercase names already used for every other
# res/data/*.res file (case-sensitive on Linux, unlike the DOS/Windows
# filesystems these came from).
cp "$work/classic/app/SSHOCK/DATA/MFDFRN.RES" "$out_dir/data/mfdfrn.res"
cp "$work/classic/app/SSHOCK/DATA/MFDGER.RES" "$out_dir/data/mfdger.res"
}
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"
if [ -n "$CLASSIC_INSTALLER" ]; then
run_classic_mfd_extraction "$ASSETS_DIR/$CLASSIC_INSTALLER" "$OUT_DIR"
fi
else
echo "== innoextract/unzip not found locally - using a throwaway docker container =="
command -v docker >/dev/null 2>&1 \
@@ -86,12 +138,18 @@ else
apt-get update -qq >/dev/null
apt-get install -y -qq innoextract unzip >/dev/null
$(declare -f run_extraction)
$(declare -f run_classic_mfd_extraction)
run_extraction '/assets/$INSTALLER' '/assets/ss_ee'
$([ -n "$CLASSIC_INSTALLER" ] && echo "run_classic_mfd_extraction '/assets/$CLASSIC_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."
if [ -n "$CLASSIC_INSTALLER" ]; then
echo "Also extracted mfdfrn.res/mfdger.res (French/German MFD art) from $CLASSIC_INSTALLER."
else
echo "Note: mfdfrn.res/mfdger.res (French/German MFD art) are not present - only needed for"
echo "French/German (see CONFIG.md); drop a System Shock: Classic installer here too to get them."
fi