소스 검색

feat(csgoserver): support the standalone CS:GO client (App ID 4465480) with configurable clientappid and the NoLobbyReservation mod (#4963)

* feat(csgoserver): support the standalone CS:GO client (App ID 4465480) with configurable runtimeappid and the NoLobbyReservation mod

* feat(csgoserver): rename runtimeappid to clientappid, pin NoLobbyReservation, prefer spcomp64

- Rename runtimeappid to clientappid and reword the comments: appid is
  the SteamCMD download ID (740), clientappid is the App ID the server
  advertises and players' clients connect with (4465480 or 730).
- Pin NoLobbyReservation to commit 003ab19f. It has no releases and is
  compiled from source on install, so do not follow master.
- Prefer spcomp64 and fall back to spcomp, so compiling does not rely
  on i386 libraries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Daniel Gibbs <me@danielgibbs.co.uk>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Attila 1 주 전
부모
커밋
22f84b3d0c

+ 6 - 1
lgsm/config-default/config-lgsm/csgoserver/_default.cfg

@@ -43,6 +43,7 @@ tickrate="64"
 
 ## Game Server Login Token (GSLT): Required
 # GSLT is required for running a public server.
+# The GSLT must be created using the App ID set in clientappid (default 4465480).
 # More info: https://docs.linuxgsm.com/steamcmd/gslt
 gslt=""
 
@@ -168,8 +169,12 @@ ansi="on"
 sleeptime="0.5"
 
 ## SteamCMD Settings | https://docs.linuxgsm.com/steamcmd
-# Server appid
+# App ID SteamCMD downloads the dedicated server files from. Do not change.
 appid="740"
+# App ID the server advertises to players. Only clients of this App ID can connect, and the GSLT must be created for it.
+# 4465480: standalone CS:GO | 730: CS2 csgo_legacy beta branch | "": leave steam_appid.txt and steam.inf unmanaged
+# https://docs.linuxgsm.com/game-servers/counter-strike-global-offensive#client-app-id
+clientappid="4465480"
 steamcmdforcewindows="no"
 # SteamCMD Branch | https://docs.linuxgsm.com/steamcmd/branch
 branch=""

+ 9 - 0
lgsm/modules/command_mods_install.sh

@@ -127,11 +127,20 @@ if [ "${modcommand}" == "amxmodxcs" ] \
 	fn_mod_exist "amxmodx"
 fi
 
+# If nolobbyreservation check if sourcemod exists first as it is compiled using the SourceMod compiler
+if [ "${modcommand}" == "nolobbyreservation" ]; then
+	fn_mod_exist "sourcemod"
+fi
+
 fn_create_mods_dir
 fn_mods_clear_tmp_dir
 fn_mods_create_tmp_dir
 fn_mod_install_files
 fn_mod_lowercase
+# NoLobbyReservation is only distributed as source
+if [ "${modcommand}" == "nolobbyreservation" ]; then
+	fn_mod_compile_sourcemod_plugin "nolobbyreservation"
+fi
 fn_mod_create_filelist
 fn_mod_copy_destination
 fn_mod_add_list

+ 4 - 0
lgsm/modules/command_mods_update.sh

@@ -88,6 +88,10 @@ while [ "${installedmodsline}" -le "${installedmodscount}" ]; do
 			fn_mod_install_files
 			fn_mod_lowercase
 			fn_remove_cfg_files
+			# NoLobbyReservation is only distributed as source
+			if [ "${modcommand}" == "nolobbyreservation" ]; then
+				fn_mod_compile_sourcemod_plugin "nolobbyreservation"
+			fi
 			fn_mod_create_filelist
 			fn_mod_copy_destination
 			fn_mod_add_list

+ 22 - 3
lgsm/modules/fix_csgo.sh

@@ -7,11 +7,30 @@
 
 moduleselfname="$(basename "$(readlink -f "${BASH_SOURCE[0]}")")"
 
+# Server files are downloaded using App ID 740 (appid), but the App ID written to steam_appid.txt and steam.inf
+# (clientappid) decides which client can connect: 4465480 for standalone CS:GO, 730 for the CS2 csgo_legacy branch.
+# SteamCMD update/validate can revert these files, so this runs before every start.
+
 # Fixes: server not always creating steam_appid.txt file.
-if [ ! -f "${serverfiles}/steam_appid.txt" ]; then
-	fixname="730 steam_appid.txt"
+# The server writes steam_appid.txt with a trailing null byte, which is removed before comparing.
+if [ -n "${clientappid}" ] && { [ ! -f "${serverfiles}/steam_appid.txt" ] || [ "$(tr -d '\0' < "${serverfiles}/steam_appid.txt")" != "${clientappid}" ]; }; then
+	fixname="${clientappid} steam_appid.txt"
+	fn_fix_msg_start
+	if echo -n "${clientappid}" > "${serverfiles}/steam_appid.txt"; then
+		exitcode=0
+	else
+		exitcode=1
+	fi
+	fn_fix_msg_end
+fi
+
+# Fixes: steam.inf not using the client App ID.
+# steam.inf uses CRLF line endings, the whole value is replaced while keeping the line ending.
+if [ -n "${clientappid}" ] && [ -f "${systemdir}/steam.inf" ] && grep -q "^appID=" "${systemdir}/steam.inf" && ! grep -qE "^appID=${clientappid}[[:cntrl:]]?$" "${systemdir}/steam.inf"; then
+	fixname="${clientappid} steam.inf"
 	fn_fix_msg_start
-	echo -n "730" >> "${serverfiles}/steam_appid.txt"
+	sed -i "s/^appID=[^[:cntrl:]]*/appID=${clientappid}/" "${systemdir}/steam.inf"
+	exitcode=$?
 	fn_fix_msg_end
 fi
 

+ 4 - 0
lgsm/modules/install_gslt.sh

@@ -17,6 +17,10 @@ else
 	echo -e "GSLT is an optional feature for ${gamename} server."
 	fn_script_log_info "GSLT is an optional feature for ${gamename} server"
 fi
+if [ "${shortname}" == "csgo" ] && [ -n "${clientappid}" ]; then
+	echo -e "The GSLT must be created using the App ID set in clientappid (${clientappid})."
+	fn_script_log_info "The GSLT must be created using the App ID set in clientappid (${clientappid})"
+fi
 
 echo -e ""
 echo -e "More info: ${italic}https://docs.linuxgsm.com/steamcmd/gslt${default}"

+ 43 - 3
lgsm/modules/mods_core.sh

@@ -29,17 +29,54 @@ fn_mod_install_files() {
 		mkdir -p "${extractdest}"
 	fi
 	fn_dl_extract "${modstmpdir}" "${modfilename}" "${extractdest}"
-	# If modsubdirs names a specific subfolder, use its contents as the install root.
+	# If modsubdirs names a specific subfolder (or path to one), use its contents as the install root.
 	if [ "${modsubdirs}" != "0" ] && [ -d "${extractdest}/${modsubdirs}" ]; then
 		local tmpsubdir
 		tmpsubdir=$(mktemp -d)
 		mv "${extractdest}/${modsubdirs}" "${tmpsubdir}/"
 		rm -rf "${extractdest}"
-		mv "${tmpsubdir}/${modsubdirs}" "${extractdest}"
+		mv "${tmpsubdir}/$(basename "${modsubdirs}")" "${extractdest}"
 		rm -rf "${tmpsubdir}"
 	fi
 }
 
+# Compile a SourceMod plugin that is only distributed as source, using the installed SourceMod compiler.
+# The compiled plugin is added to the extracted mod files so it is installed, updated and removed with the mod.
+fn_mod_compile_sourcemod_plugin() {
+	local pluginname="${1}"
+	local spcompdir="${modinstalldir}/addons/sourcemod/scripting"
+	# Prefer the 64-bit compiler; the 32-bit spcomp needs i386 libraries.
+	local spcomp="${spcompdir}/spcomp64"
+	if [ ! -x "${spcomp}" ]; then
+		spcomp="${spcompdir}/spcomp"
+	fi
+	echo -en "compiling ${pluginname}.sp..."
+	fn_sleep_time
+	if [ ! -x "${spcomp}" ]; then
+		fn_print_fail_eol_nl
+		fn_script_log_fail "Compiling ${pluginname}.sp: SourceMod compiler not found in ${spcompdir}"
+		echo -e "* SourceMod compiler not found in ${spcompdir}"
+		exitcode=1
+		core_exit.sh
+	fi
+	mkdir -p "${extractdest}/addons/sourcemod/plugins"
+	spcompoutput=$("${spcomp}" -i"${spcompdir}/include" "${extractdest}/addons/sourcemod/scripting/${pluginname}.sp" -o"${extractdest}/addons/sourcemod/plugins/${pluginname}.smx" 2>&1)
+	exitcode=$?
+	if [ "${exitcode}" -ne 0 ] || [ ! -s "${extractdest}/addons/sourcemod/plugins/${pluginname}.smx" ]; then
+		fn_print_fail_eol_nl
+		fn_script_log_fail "Compiling ${pluginname}.sp"
+		if [ -f "${lgsmlog}" ]; then
+			echo -e "${spcompoutput}" >> "${lgsmlog}"
+		fi
+		echo -e "${spcompoutput}"
+		exitcode=1
+		core_exit.sh
+	else
+		fn_print_ok_eol_nl
+		fn_script_log_pass "Compiling ${pluginname}.sp"
+	fi
+}
+
 # Convert mod files to lowercase if needed.
 fn_mod_lowercase() {
 	# Checking lowercase settings from mods array definition
@@ -164,7 +201,7 @@ fn_mod_tidy_files_list() {
 	fi
 
 	# Remove common paths from deletion list (Add your sourcemod mod here)
-	if [ "${modcommand}" == "gokz" ] || [ "${modcommand}" == "ttt" ] || [ "${modcommand}" == "steamworks" ] || [ "${modcommand}" == "get5" ]; then
+	if [ "${modcommand}" == "gokz" ] || [ "${modcommand}" == "ttt" ] || [ "${modcommand}" == "steamworks" ] || [ "${modcommand}" == "get5" ] || [ "${modcommand}" == "nolobbyreservation" ]; then
 		sed -i "/^addons\/sourcemod$/d" "${modsdir}/${modcommand}-files.txt"
 		sed -i "/^addons\/sourcemod\/configs$/d" "${modsdir}/${modcommand}-files.txt"
 		sed -i "/^addons\/sourcemod\/extensions$/d" "${modsdir}/${modcommand}-files.txt"
@@ -202,6 +239,9 @@ fn_mod_tidy_files_list() {
 		sed -i "/^addons\/sourcemod\/scripting\/include\/dhooks.inc$/d" "${modsdir}/${modcommand}-files.txt"
 		sed -i "/^addons\/sourcemod\/scripting\/include\/updater.inc$/d" "${modsdir}/${modcommand}-files.txt"
 	fi
+	if [ "${modcommand}" == "nolobbyreservation" ]; then
+		sed -i "/^addons\/sourcemod\/gamedata$/d" "${modsdir}/${modcommand}-files.txt"
+	fi
 }
 
 ## Information Gathering.

+ 11 - 3
lgsm/modules/mods_list.sh

@@ -67,7 +67,12 @@ metamodsource2latestfile=$(wget "${metamodsource2scrapeurl}" -q -O -)
 metamodsource2downloadurl="https://www.metamodsource.net/latest.php?os=linux&version=${metamodsource2version}"
 metamodsource2url="${metamodsource2downloadurl}"
 # Sourcemod
-sourcemodversion="1.13"
+# Use the compatible stable SourceMod branch for Counter-Strike: Global Offensive.
+if [ "${shortname}" == "csgo" ]; then
+	sourcemodversion="1.12"
+else
+	sourcemodversion="1.13"
+fi
 sourcemodscrapeurl="https://sm.alliedmods.net/smdrop/${sourcemodversion}/sourcemod-latest-linux"
 sourcemodlatestfile=$(wget "${sourcemodscrapeurl}" -q -O -)
 sourcemoddownloadurl="https://www.sourcemod.net/latest.php?os=linux&version=${sourcemodversion}"
@@ -134,7 +139,7 @@ modseparator="MOD"
 # [2] 	| "Pretty Name": the common name people use to call the mod that will be displayed to the user
 # [3] 	| "URL": link to the mod archive file; can be a variable previously defined while scraping a URL
 # [4] 	| "filename": the output filename
-# [5]	| "modsubdirs": name of a subfolder within the archive to extract as the install root (use "0" to extract all contents)
+# [5]	| "modsubdirs": name of (or path to) a subfolder within the archive to extract as the install root (use "0" to extract all contents)
 # [6]	| "LowercaseOn/Off": LowercaseOff or LowercaseOn: enable/disable converting extracted files and directories to lowercase (some games require it)
 # [7] 	| "modinstalldir": the directory in which to install the mode (use LGSM dir variables such as ${systemdir})
 # [8]	| "/files/to/keep;", files & directories that should not be overwritten upon update, separated and ended with a semicolon; you can also use "OVERWRITE" value to ignore the value or "NOUPDATE" to disallow updating; for files to keep upon uninstall, see fn_mod_tidy_files_list from mods_core.sh
@@ -174,6 +179,9 @@ mod_info_metamodsource2=(MOD "metamodsource2" "Metamod:Source 2.0" "${metamodsou
 mod_info_counterstrikesharp=(MOD "counterstrikesharp" "CounterStrikeSharp" "${counterstrikesharplatestlink}" "${counterstrikesharplatestfile}" "0" "LowercaseOff" "${systemdir}" "OVERWRITE" "ENGINES" "Counter-Strike 2;" "NOTGAMES" "https://docs.cssharp.dev/" "Write server plugins in C# (requires Metamod:Source 2.0)")
 
 # CS:GO Mods
+# NoLobbyReservation is pinned to a reviewed commit: it has no releases and is compiled from source on install.
+nolobbyreservationcommit="003ab19fcf5e6eb67d21794218a4dc387d2923ca"
+mod_info_nolobbyreservation=(MOD "nolobbyreservation" "NoLobbyReservation" "https://github.com/eldoradoel/NoLobbyReservation/archive/${nolobbyreservationcommit}.zip" "NoLobbyReservation-${nolobbyreservationcommit}.zip" "NoLobbyReservation-${nolobbyreservationcommit}/csgo" "LowercaseOff" "${systemdir}" "OVERWRITE" "ENGINES" "Counter-Strike: Global Offensive;" "NOTGAMES" "https://github.com/eldoradoel/NoLobbyReservation" "Allows players to connect without a Steam lobby reservation - Required for standalone CS:GO clients to connect (requires SourceMod and MetaMod)")
 mod_info_gokz=(MOD "gokz" "GOKZ" "${gokzlatestlink}" "${gokzlatestfile}" "0" "LowercaseOff" "${systemdir}" "cfg;addons/sourcemod/configs;" "ENGINES" "Counter-Strike: Global Offensive;" "NOTGAMES" "https://github.com/KZGlobalTeam/gokz" "GOKZ ${gokzlatestversion} - Implements the KZ game mode (requires SourceMod and MetaMod)")
 mod_info_ttt=(MOD "ttt" "Trouble in Terrorist Town" "https://csgottt.com/downloads/ttt-latest-dev-${sourcemodversion}.zip" "ttt-latest.zip" "0" "LowercaseOff" "${systemdir}" "cfg;addons/sourcemod/configs;" "ENGINES" "Counter-Strike: Global Offensive;" "NOTGAMES" "https://github.com/Bara/TroubleinTerroristTown" "Implements the TTT game mode (requires SourceMod and MetaMod)")
 mod_info_get5=(MOD "get5" "Get 5" "${get5latestfilelink}" "${get5latestfile}" "0" "LowercaseOff" "${systemdir}" "cfg;addons/sourcemod/configs;" "ENGINES" "Counter-Strike: Global Offensive;" "NOTGAMES" "https://github.com/splewis/get5" "Plugin for competitive matches/scrims (requires SourceMod and MetaMod)")
@@ -227,4 +235,4 @@ mod_info_valheimplus=(MOD "valheimplus" "Valheim PLUS" "${valheimpluslatestlink}
 mod_info_bepinexvh=(MOD "bepinexvh" "BepInEx Valheim" "${bepinexvhlatestlink}" "denikson-BepInExPack_Valheim.zip" "BepInExPack_Valheim" "LowercaseOff" "${systemdir}" "OVERWRITE" "ENGINES" "Valheim;" "NOTGAMES" "https://valheim.thunderstore.io/package/denikson/BepInExPack_Valheim/" "Unity / XNA game patcher and plugin framework")
 
 # REQUIRED: Set all mods info into the global array
-mods_global_array=("${mod_info_metamod[@]}" "${mod_info_base_amxx[@]}" "${mod_info_cs_amxx[@]}" "${mod_info_dod_amxx[@]}" "${mod_info_tfc_amxx[@]}" "${mod_info_ns_amxx[@]}" "${mod_info_ts_amxx[@]}" "${mod_info_metamodsource[@]}" "${mod_info_metamodsource2[@]}" "${mod_info_counterstrikesharp[@]}" "${mod_info_sourcemod[@]}" "${mod_info_steamworks[@]}" "${mod_info_gokz[@]}" "${mod_info_ttt[@]}" "${mod_info_get5[@]}" "${mod_info_prac[@]}" "${mod_info_pug[@]}" "${mod_info_dhook[@]}" "${mod_info_movement[@]}" "${mod_info_cleaner[@]}" "${mod_info_ulib[@]}" "${mod_info_ulx[@]}" "${mod_info_utime[@]}" "${mod_info_uclip[@]}" "${mod_info_acf[@]}" "${mod_info_acf_missiles[@]}" "${mod_info_acf_sweps[@]}" "${mod_info_advdupe2[@]}" "${mod_info_pac3[@]}" "${mod_info_wiremod[@]}" "${mod_info_wiremodextras[@]}" "${mod_info_darkrp[@]}" "${mod_info_darkrpmodification[@]}" "${mod_info_rustcarbon[@]}" "${mod_info_rustoxide[@]}" "${mod_info_hwoxide[@]}" "${mod_info_sdtdoxide[@]}" "${mod_info_advduplicator[@]}" "${mod_info_trackassemblytool[@]}" "${mod_info_physpropertiesadv[@]}" "${mod_info_controlsystemse2[@]}" "${mod_info_e2pistontiming[@]}" "${mod_info_propcannontool[@]}" "${mod_info_gearassemblytool[@]}" "${mod_info_spinnertool[@]}" "${mod_info_surfacefrictiontool[@]}" "${mod_info_magneticdipole[@]}" "${mod_info_environmentorganizer[@]}" "${mod_info_precision_alignment[@]}" "${mod_info_improved_stacker[@]}" "${mod_info_improved_weight[@]}" "${mod_info_improved_antinoclip[@]}" "${mod_info_laserstool[@]}" "${mod_info_valheimplus[@]}" "${mod_info_bepinexvh[@]}")
+mods_global_array=("${mod_info_metamod[@]}" "${mod_info_base_amxx[@]}" "${mod_info_cs_amxx[@]}" "${mod_info_dod_amxx[@]}" "${mod_info_tfc_amxx[@]}" "${mod_info_ns_amxx[@]}" "${mod_info_ts_amxx[@]}" "${mod_info_metamodsource[@]}" "${mod_info_metamodsource2[@]}" "${mod_info_counterstrikesharp[@]}" "${mod_info_sourcemod[@]}" "${mod_info_nolobbyreservation[@]}" "${mod_info_steamworks[@]}" "${mod_info_gokz[@]}" "${mod_info_ttt[@]}" "${mod_info_get5[@]}" "${mod_info_prac[@]}" "${mod_info_pug[@]}" "${mod_info_dhook[@]}" "${mod_info_movement[@]}" "${mod_info_cleaner[@]}" "${mod_info_ulib[@]}" "${mod_info_ulx[@]}" "${mod_info_utime[@]}" "${mod_info_uclip[@]}" "${mod_info_acf[@]}" "${mod_info_acf_missiles[@]}" "${mod_info_acf_sweps[@]}" "${mod_info_advdupe2[@]}" "${mod_info_pac3[@]}" "${mod_info_wiremod[@]}" "${mod_info_wiremodextras[@]}" "${mod_info_darkrp[@]}" "${mod_info_darkrpmodification[@]}" "${mod_info_rustcarbon[@]}" "${mod_info_rustoxide[@]}" "${mod_info_hwoxide[@]}" "${mod_info_sdtdoxide[@]}" "${mod_info_advduplicator[@]}" "${mod_info_trackassemblytool[@]}" "${mod_info_physpropertiesadv[@]}" "${mod_info_controlsystemse2[@]}" "${mod_info_e2pistontiming[@]}" "${mod_info_propcannontool[@]}" "${mod_info_gearassemblytool[@]}" "${mod_info_spinnertool[@]}" "${mod_info_surfacefrictiontool[@]}" "${mod_info_magneticdipole[@]}" "${mod_info_environmentorganizer[@]}" "${mod_info_precision_alignment[@]}" "${mod_info_improved_stacker[@]}" "${mod_info_improved_weight[@]}" "${mod_info_improved_antinoclip[@]}" "${mod_info_laserstool[@]}" "${mod_info_valheimplus[@]}" "${mod_info_bepinexvh[@]}")