commit d9da37a98a05b77ee98b755ac0c1fff372067340
Author: Giygas <none>
Date: Fri, 3 Oct 2025 13:57:09 +0200
Initial commit
Diffstat:
| A | scrake.ini | | | 40 | ++++++++++++++++++++++++++++++++++++++++ |
| A | scrake.sh | | | 325 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
2 files changed, 365 insertions(+), 0 deletions(-)
diff --git a/scrake.ini b/scrake.ini
@@ -0,0 +1,40 @@
+[Scrake]
+Engine.AccessControl/AdminPassword=AdminPassword
+Engine.AccessControl/GamePassword=
+Engine.GameInfo/bVACSecured=True
+Engine.GameReplicationInfo/ServerName=My Server
+Engine.GameReplicationInfo/ShortName=
+Engine.GameReplicationInfo/AdminName=
+Engine.GameReplicationInfo/AdminEmail=
+Engine.GameReplicationInfo/MessageOfTheDay=
+IpDrv.MasterServerUplink/DoUplink=True
+IpDrv.MasterServerUplink/UplinkToGamespy=False
+IpDrv.MasterServerUplink/SendStats=True
+IpDrv.MasterServerUplink/ServerBehindNAT=False
+IpDrv.MasterServerUplink/DoLANBroadcast=False
+IpDrv.HTTPDownload/RedirectToURL=
+-Engine.GameEngine/ServerActors=IpDrv.MasterServerUplink
++Engine.GameEngine/ServerActors=CustomServerDetails.CSDMasterServerUplink
++Engine.GameEngine/ServerActors=MutKillMessage.MutKillMessage
++Engine.GameEngine/ServerActors=ReloadOptionsMut.ReloadOptionsMut
+
+[Scrake.Normal]
+URL/Port=7707
+IpDrv.UdpGamespyQuery/OldQueryPortNumber=7717
+UWeb.WebServer/bEnabled=True
+UWeb.WebServer/ListenPort=8075
+Engine.GameInfo/GameDifficulty=2
+
+[Scrake.Hard]
+URL/Port=7807
+IpDrv.UdpGamespyQuery/OldQueryPortNumber=7817
+UWeb.WebServer/bEnabled=True
+UWeb.WebServer/ListenPort=8175
+Engine.GameInfo/GameDifficulty=4
+
+[Scrake.Suicidal]
+URL/Port=7907
+IpDrv.UdpGamespyQuery/OldQueryPortNumber=7917
+UWeb.WebServer/bEnabled=True
+UWeb.WebServer/ListenPort=8275
+Engine.GameInfo/GameDifficulty=5
diff --git a/scrake.sh b/scrake.sh
@@ -0,0 +1,325 @@
+#!/bin/bash
+PROGNAME="$(basename "$0")"
+unset VERBOSE
+
+[ -z "$STEAM_APPID" ] && STEAM_APPID="215360"
+[ -z "$STEAM_ROOT" ] && STEAM_ROOT="$HOME/.steam"
+[ -z "$SCRAKE_BASE" ] && SCRAKE_BASE="$STEAM_ROOT/SteamApps/common/Killing Floor Dedicated Server - Linux"
+[ -z "$SCRAKE_ROOT" ] && SCRAKE_ROOT="$HOME/Scrake"
+[ -z "$SCRAKE_CFG" ] && SCRAKE_CFG="$SCRAKE_ROOT/scrake.ini"
+[ -z "$FIRSTRUN_FIFO" ] && FIRSTRUN_FIFO="$(mktemp -u)"
+
+notify() { echo "$PROGNAME: $@"; }
+warn() { echo "$PROGNAME: $@" >&2; }
+fatal() {
+ trap "exit 1" TERM
+ echo "$PROGNAME: $@" >&2
+ kill $$
+}
+
+clean() { if [ -e "$FIRSTRUN_FIFO" ]; then rm "$FIRSTRUN_FIFO" 2>&-; fi }
+
+trap "clean" TERM INT
+
+init_paths() {
+ if [ ! -d "$STEAM_ROOT" ]; then
+ fatal "could not find steam installation in '$STEAM_ROOT'. "\
+ "Change the STEAM_ROOT environment variable to the correct path." >&2
+ fi
+
+ if [ ! -d "$SCRAKE_BASE" ]; then
+ fatal "could not find base game in '$SCRAKE_BASE'. "\
+ "Change the SCRAKE_BASE environment variable to the correct path." >&2
+ fi
+
+ if [ ! -d "$SCRAKE_ROOT" ]; then
+ mkdir -p "$SCRAKE_ROOT" || exit 1
+ mkdir "$SCRAKE_ROOT/"{Servers,Mutators,Maps} || exit 1
+ fi
+}
+
+# Usage: init_base username [path]
+init_base() {
+ typeset STEAM_LOGIN PID
+ if [ $# -lt 1 ] || [ $# -gt 2 ]; then fatal "usage: init_base username [path]"; fi
+ if [ -e "$FIRSTRUN_FIFO" ]; then fatal "init_base: '$FIRSTRUN_FIFO' exists"; fi
+ STEAM_LOGIN="$1"
+ if [ ! -z "$2" ]; then
+ INSTALL_DIR="+force_install_dir $2"
+ notify "init_base: installing server base with steamcmd ($INSTALL_DIR)"
+ else
+ notify "init_base installing server base with steamcmd (default path)"
+ fi
+
+ steamcmd $INSTALL_DIR +login "$STEAM_LOGIN" +app_update "$STEAM_APPID" +quit || fatal "steamcmd error"
+ if [ ! -z "$INSTALL_DIR" ]; then
+ notify "init_base: custom install path specified; do not forget to set 'SCRAKE_BASE'" \
+ "to the server's root in your shell environment."
+ SCRAKE_BASE="$INSTALL_DIR"
+ fi
+
+ notify "init_base: allowing the server to run once to initialise the default configuration (this will take a second)"
+ mkfifo "$FIRSTRUN_FIFO"
+ run_server "$SCRAKE_BASE" server KF-Forgotten?game=KFmod.KFGameType -nohomedir >"$FIRSTRUN_FIFO" &
+ PID=$!
+ notify "init_base: first-run pid: $PID"
+ cat "$FIRSTRUN_FIFO" | while read -r LINE; do
+ echo "$LINE"
+ if echo "$LINE" | grep -q '^Webserver is not enabled.'; then
+ kill -9 "$PID"
+ break
+ fi
+ done
+ rm "$FIRSTRUN_FIFO"
+ notify "init_base: first run successful"
+}
+
+# Usage: skeleton_link -i src dst
+skeleton_link()
+{
+ typeset SRC DST INI_REPLACE DIR DIRNAME
+ if [ "$1" == "-i" ]; then INI_REPLACE=1; shift; fi
+ if [ $# -ne 2 ]; then fatal "usage: skeleton_link source destination"; fi
+ SRC="$(realpath "$1")"
+ if [ ! -d "$SRC" ]; then fatal "skeleton_link: no such directory '$SRC'"; fi
+ DST="$(realpath "$2")"
+ if [ ! -d "$DST" ]; then fatal "skeleton_link: no such directory '$SRC'"; fi
+
+ find "$SRC" -mindepth 1 -type d | while read DIR; do
+ DIRNAME="$(realpath --relative-to "$SRC" "$DIR")"
+ [ -d "$DST/$DIRNAME" ] || mkdir $VERBOSE "$DST/$DIRNAME"
+ find "$DIR" -type f -exec ln $VERBOSE -s {} "$DST/$DIRNAME" \;
+ done
+ if [ ! -z "$INI_REPLACE" ]; then
+ find "$DST" -type l -name '*.ini' -exec mv $VERBOSE {} {}.defaults \;
+ fi
+}
+
+# Usage: skeleton_unlink src dst
+skeleton_unlink()
+{
+ typeset SRC DST DIR
+ if [ $# -ne 2 ]; then fatal "usage: skeleton_unlink source destination"; fi
+ SRC="$(realpath "$1")"
+ DST="$(realpath "$2")"
+ if [ ! -d "$DST" ]; then fatal "skeleton_unlink: no such directory '$DST'"; fi
+
+ find "$DST" -mindepth 1 -type l -lname "$SRC/*" -exec rm $VERBOSE {} \;
+ find "$DST" -mindepth 1 -type d | while read DIR; do
+ [ -z "$(ls -A "$DIR")" ] && rmdir $VERBOSE "$DIR"
+ done
+}
+
+
+# Usage: new_instance name [name [...]]
+new_instance() {
+ typeset SRC DST
+ if [ $# -lt 1 ]; then fatal "usage: new_instance name [name [...]]" >&2; fi
+
+ while [ $# -ge 1 ]; do
+ notify "creating instance '$1'"
+ SRC="$(realpath "$SCRAKE_BASE")"
+ DST="$(realpath "$SCRAKE_ROOT/Servers/$1")"
+ if [ -d "$DST" ]; then notify "new_instance: instance '$1' already exists. Skipping..."; fi
+
+ mkdir $VERBOSE "$DST" && skeleton_link -i "$SRC" "$DST"
+
+ shift
+ done
+}
+
+usage_mod_instance() { echo "mod_instance instance [map|mutator] [add|remove] component"; }
+mod_instance() {
+ typeset TARGET WHAT ACTION SRC DST
+ if [ $# -lt 3 ]; then fatal "$(usage_mod_instance)"; exit 1; fi
+ TARGET="$1"
+ WHAT="$2"
+ ACTION="$3"; shift 3
+
+ case "$WHAT" in
+ map) SRC="$SCRAKE_ROOT/Maps";;
+ mutator) SRC="$SCRAKE_ROOT/Mutators";;
+ *) fatal "$(usage_mod_instance)"; exit 1;;
+ esac
+
+ DST="$SCRAKE_ROOT/Servers/$TARGET"
+ if [ ! -d "$DST" ]; then fatal "mod_mutator: fatal: no such instance '$TARGET'"; fi
+
+ case "$ACTION" in
+ add)
+ while [ $# -ge 1 ]; do
+ if [ ! -d "$SRC/$1" ]; then
+ notify "mod_mutator: no such $WHAT '$SRC/$1'. Skipping..." >&2
+ shift
+ continue;
+ fi
+ notify "mod_mutator: installing $WHAT '$1' to $TARGET"
+ skeleton_link -i "$SRC/$1" "$DST"
+ shift
+ done;;
+ remove)
+ while [ $# -ge 1 ]; do
+ notify "mod_mutator: removing $WHAT '$1' from $TARGET"
+ skeleton_unlink "$SRC/$1" "$DST"
+ shift
+ done;;
+ *)
+ fatal "$(usage_mod_instance)";;
+ esac
+}
+
+inimerge() {
+ if [ "$#" -ne 3 ]; then fatal "usage: iniawk overrides instance ini"; fi
+ cat "$3" | tr -d '\r' | awk \
+ -v overrides=<(sed -n '/^ *\[Scrake\] *$/,/^ *$/p; /^ *\[Scrake\.'$2'\] *$/,/^ *$/p' "$1") \
+ -f <(sed -n '/^## begin inimerge.awk/,$p' "$0")
+}
+
+# usage: sync_cfg inifile instance_name ..."
+sync_cfg() {
+ typeset DST INIFILE
+ if [ "$#" -lt 2 ]; then fatal "usage: sync_cfg inifile instance_name ..."; fi
+ INIFILE="$1"; shift
+
+ while [ "$#" -ge 1 ]; do
+ DST="$(realpath "$SCRAKE_ROOT/Servers/$1")"
+ if [ ! -d "$DST" ]; then
+ notify "sync_cfg: no such instance '$DST'. Skipping..." >&2
+ shift
+ continue;
+ fi
+ find "$DST" -name '*.ini.defaults' | while read FILE; do
+ BASENAME="$(echo "$FILE" | sed 's/\.defaults$//')"
+ notify "sync_cfg: applying overrides from '$1' to $BASENAME"
+ inimerge "$INIFILE" "$1" "$FILE" >"$BASENAME"
+ done
+
+ shift
+ done
+}
+
+run_server() {
+ ROOT="$1"; shift
+ if [ ! -e "$ROOT/System/ucc-bin" ]; then fatal "run_server: no server found under '$ROOT'"; fi
+ cd "$ROOT/System"
+ export LD_LIBRARY_PATH="$STEAM_ROOT/steamcmd/linux32"
+ exec ./ucc-bin $* # ucc-bin-real?
+}
+
+# usage: run_instance instance_name map
+run_instance() {
+ if [ "$#" -ne 2 ]; then fatal "usage: run_instance instance_name map"; fi
+ DST="$(realpath "$SCRAKE_ROOT/Servers/$1")"
+ if [ ! -d "$DST" ]; then fatal "run_server: no such instance '$DST'"; fi
+ if [ ! -e "$DST/Maps/$2" ]; then fatal "run_server: map not found '$DST/Maps/$2'"; fi
+
+ run_server "$DST" server "$2?game=KFmod.KFGameType" -nohomedir
+}
+
+usage_scrake() {
+ cat <<EOF
+usage:
+ $PROGNAME [global-options] create instance_name
+ $PROGNAME [global-options] mod instance_name mutator add|remove mutname ...
+ $PROGNAME [global-options] mod instance_name map add|remove mapname ...
+ $PROGNAME [global-options] sync [-i inifile] instance_name ...
+global options:
+ -h show this help message
+ -s alternative steam install directory (overrides STEAM_ROOT)
+ -b alternative server base directory (overrides SCRAKE_BASE)
+ -r alternative scrake root directory (overrides SCRAKE_ROOT)
+ -v verbose flag
+sync options:
+ -i alternative configuration override file (overrides SCRAKE_CFG)
+EOF
+}
+while getopts "b:hr:s:v" OPT; do
+ case "$OPT" in
+ b) SCRAKE_BASE="${OPTARG}";;
+ h) usage_scrake; exit;;
+ r) SCRAKE_ROOT="${OPTARG}";;
+ s) STEAM_ROOT="${OPTARG}";;
+ v) VERBOSE="-v";;
+ *) fatal "$(usage_scrake)";;
+ esac
+done
+shift $((OPTIND-1))
+
+init_base "STEAMUSERNAME"
+init_paths
+new_instance "Normal" "Hard" "Suicidal"
+mod_instance "Normal" mutator add "CustomServerDetails" "ReloadOptions" "MutKillMessage"
+mod_instance "Hard" mutator add "CustomServerDetails" "ReloadOptions" "MutKillMessage"
+mod_instance "Suicidal" mutator add "CustomServerDetails" "ReloadOptions" "MutKillMessage"
+sync_cfg "$HOME/scrake.ini" "Normal" "Hard" "Suicidal"
+run_instance "$1" "KF-Forgotten.rom"
+
+bullshit_debug() {
+ new_server T
+ mod_server "T" mutator add "CustomServerDetails" "ReloadOptions" "MutKillMessage"
+ mod_server "T" map add KF-Affairs KF-Awe KF-Headquarters
+ cd "$SCRAKE_ROOT/Servers/T"
+ echo "Inspect and ctrl+d when done.."
+ bash
+ mod_server "T" mutator remove "CustomServerDetails" "ReloadOptions" "MutKillMessage"
+ mod_server "T" map remove KF-Affairs KF-Awe KF-Headquarters
+ echo "Inspect and ctrl+d when done.."
+ bash
+ rm -r "$SCRAKE_ROOT/Servers/T"
+ echo "all done.."
+}
+
+clean
+exit
+## begin inimerge.awk
+# apply settings from the file described by the 'overrides' variable (supplied through -v) to the
+# ini data read from stdin, and write to stdout.
+BEGIN {
+ n=0
+ FS=IFS=OFS="="
+ while (getline <overrides) {
+ split($1, parts, "/");
+ value=substr($0, length($1)+2)
+
+ switch(parts[1]) {
+ case /^\+/: add[substr(parts[1],2)][parts[2]][n++]=value; break
+ case /^\-/: remove[substr(parts[1],2)][parts[2]][n++]=value; break
+ default: change[parts[1]][parts[2]][n++]=value; break
+ }
+ }
+}
+
+/^ *\[[^\[\]]+\] *$/ {
+ print
+ gsub(/[\[\]]/,"")
+ SECTION=$0
+
+ if (SECTION in add) {
+ for (key in add[SECTION]) {
+ for (i in add[SECTION][key]) {
+ print key"="add[SECTION][key][i]
+ }
+ }
+ }
+ next
+}
+
+(SECTION in remove) && ($1 in remove[SECTION]) {
+ key=$1
+ value=substr($0, length($1)+2);
+ for (i in remove[SECTION][key]) {
+ if (remove[SECTION][key][i]==value) {
+ delete remove[SECTION][key][i]
+ next
+ }
+ }
+}
+
+(SECTION in change) && ($1 in change[SECTION]) {
+ for (i in change[SECTION][$1])
+ print $1"="change[SECTION][$1][i]
+
+ next
+}
+
+{ print }