From 03450d1f07bbf5c505ba1fcec4f454cd0a5df91d Mon Sep 17 00:00:00 2001 From: Kamil Czop Date: Wed, 26 Feb 2025 03:01:57 +0100 Subject: [PATCH] Build dedicated server script and use SteamGameServerHTTP when defined --- game/tc2/gameinfo_server.txt | 89 +++++++++++++++++++++++++++++ src/builddedicatedserver | 87 ++++++++++++++++++++++++++++ src/game/server/tf/tf_gc_server.cpp | 37 +++++++----- 3 files changed, 198 insertions(+), 15 deletions(-) create mode 100644 game/tc2/gameinfo_server.txt create mode 100755 src/builddedicatedserver diff --git a/game/tc2/gameinfo_server.txt b/game/tc2/gameinfo_server.txt new file mode 100644 index 00000000000..302180db433 --- /dev/null +++ b/game/tc2/gameinfo_server.txt @@ -0,0 +1,89 @@ +"GameInfo" +{ + game "Team Comtress 2" + type multiplayer_only + nomodels 1 + nohimodel 1 + nocrosshair 0 + hidden_maps + { + "test_speakers" 1 + "test_hardware" 1 + } + nodegraph 0 + GameData "tf.fgd" + AdditionalLocalization "tf" + InstancePath "maps/instances/" + advcrosshair 1 + supportsvr 0 + ReplayRequired 1 + + DependsOnAppID 440 + + FileSystem + { + SteamAppId 243750 + + SearchPaths + { + // First, mount all user customizations. This will search for VPKs and subfolders + // and mount them in alphabetical order. The easiest way to distribute a mod is to + // pack up the custom content into a VPK. To "install" a mod, just drop it in this + // folder. + // + // Note that this folder is scanned only when the game is booted. + game+mod+custom_mod tc2/custom/* + // Enable this if you want to load your TF custom content. + //game+mod |appid_440|tf/custom/* + + // Now search loose files. We'll set the directory containing the gameinfo.txt file + // as the first "mod" search path (after any user customizations). This is also the one + // that's used when writing to the "mod" path. + mod+mod_write |all_source_engine_paths|tc2 + game+game_write |all_source_engine_paths|tc2 + default_write_path |all_source_engine_paths|tc2 + gamebin |all_source_engine_paths|tc2/bin + + // We search VPK files before ordinary folders, because most files will be found in + // VPK and we can avoid making thousands of file system calls to attempt to open files + // in folders where they don't exist. (Searching a VPK is much faster than making an operating + // system call.) + game_lv |all_source_engine_paths|tf/tf2_lv.vpk + game+mod |all_source_engine_paths|tf/tf2_textures.vpk + game+mod |all_source_engine_paths|tf/tf2_sound_vo_english.vpk + game+mod |all_source_engine_paths|tf/tf2_sound_misc.vpk + game+mod+vgui |all_source_engine_paths|tf/tf2_misc.vpk + game |all_source_engine_paths|hl2/hl2_textures.vpk + game |all_source_engine_paths|hl2/hl2_sound_vo_english.vpk + game |all_source_engine_paths|hl2/hl2_sound_misc.vpk + game+vgui |all_source_engine_paths|hl2/hl2_misc.vpk + platform+vgui |all_source_engine_paths|platform/platform_misc.vpk + + // Last, mount in shared HL2 and TF2 loose files + // game |all_source_engine_paths|tf + // game |all_source_engine_paths|hl2 + // platform |all_source_engine_paths|platform + + + // Random files downloaded from gameservers go into a seperate directory, so + // that it's easy to keep those files segregated from the official game files + // or customizations intentially installed by the user. + // + // This directory is searched LAST. If you visit a server and download + // a custom model, etc, we don't want that file to override the default + // game file indefinitely (after you have left the server). Servers CAN have + // custom content that overrides the default game files, it just needs to be + // packed up in the .bsp file so that it will be mounted as a map search pack. + // The map search pack is mounted at the top of the search path list, + // but only while you are connected that server and on that map. + game+download tc2/download + } + } + ToolsEnvironment + { + "Engine" "Source" + "UseVPLATFORM" "1" + "PythonVersion" "2.7" + "PythonHomeDisable" "1" + } +} diff --git a/src/builddedicatedserver b/src/builddedicatedserver new file mode 100755 index 00000000000..c453b47fe36 --- /dev/null +++ b/src/builddedicatedserver @@ -0,0 +1,87 @@ +#!/usr/bin/env bash + +set -euo pipefail + +script=$(readlink -f -- "$0") +pushd "$(dirname -- "$script")" > /dev/null + +source sdk_container +run_in_sniper "$@" +export CCACHE_SLOPPINESS="pch_defines,time_macros" +export VPC_ENABLE_CCACHE="1" +ccache -z + +if [ $# -eq 0 ]; then + export VPC_NINJA_BUILD_MODE="release" +else + if [[ "$1" == "debug" ]]; then + export VPC_NINJA_BUILD_MODE="debug" + elif [[ "$1" == "release" ]]; then + export VPC_NINJA_BUILD_MODE="release" + else + echo "Usage: $0 [debug|release]" + exit 1 + fi +fi + +solution_out="_vpc_/ninja/sdk_dedicated_$VPC_NINJA_BUILD_MODE" + +# Dir of interest in /my_mod/src +DIR="./lib/public/linux64" + +# Check if the serversided libraries are provided in catalog files check +SERVER_LIBS=("libtier0_srv.so" "libvstdlib_srv.so") + +for file in "${SERVER_LIBS[@]}"; do + if [[ ! -f "$DIR/$file" ]]; then + echo "Error: Required file $DIR/$file is missing!" + echo "Please, provide them from your dedicated server install." + exit 1 + fi +done + +# Directories for symlinking server libraries in +DIRS=("./lib/public/linux64" "./lib/common/linux64") + +# Copy all the *.a -> *_srv.a files from lib catalog before compiling +for dir in "${DIRS[@]}"; do + for file in "$dir"/*.a; do + [[ -e "$file" ]] || continue + base_name=$(basename -- "$file" .a) + copy="$dir/${base_name}_srv.a" + + if [[ -f "$copy" ]]; then + echo "File already exists, skipping: $copy" + continue + fi + + cp "$file" "$copy" + echo "Copied: $file -> $copy" + done +done + + +if [[ ! -e "$solution_out.ninja" ]]; then + devtools/bin/vpc /tf /linux64 /ninja /define:SOURCESDK +dedicated /dedicated /mksln "$solution_out" + + # Generate compile commands. + ninja -f "$solution_out.ninja" -t compdb > compile_commands.json + # Remove some unsupported clang commands. + sed -i 's/-fpredictive-commoning//g; s/-fvar-tracking-assignments//g' compile_commands.json + sed -i 's|/my_mod/src|.|g' compile_commands.json +fi + +ninja -f "$solution_out.ninja" -j$(nproc) +ccache -s + +# Removing copied files +for dir in "${DIRS[@]}"; do + for copy in "$dir"/*_srv.a; do + if [[ -f "$copy" ]]; then + echo "Deleting copied file: $copy" + rm "$copy" + fi + done +done + +popd diff --git a/src/game/server/tf/tf_gc_server.cpp b/src/game/server/tf/tf_gc_server.cpp index 6ccca97d206..0fba1877bfb 100644 --- a/src/game/server/tf/tf_gc_server.cpp +++ b/src/game/server/tf/tf_gc_server.cpp @@ -4068,6 +4068,13 @@ void CTFGCServerSystem::WebapiEquipmentThink() WebapiEquipmentThinkRequest( m_mapEquipmentRequests.Key( i ), m_mapEquipmentRequests.Element( i ) ); } } + +#ifdef DEDICATED + #define STEAM_HTTP_INTERFACE SteamGameServerHTTP +#else + #define STEAM_HTTP_INTERFACE SteamHTTP +#endif + void CTFGCServerSystem::WebapiEquipmentThinkRequest( CSteamID steamID, WebapiEquipmentState_t* pState ) { Assert( pState ); @@ -4093,7 +4100,7 @@ void CTFGCServerSystem::WebapiEquipmentThinkRequest( CSteamID steamID, WebapiEqu if ( state.m_hEquipmentRequest != INVALID_HTTPREQUEST_HANDLE ) { - SteamHTTP()->ReleaseHTTPRequest( state.m_hEquipmentRequest ); + STEAM_HTTP_INTERFACE()->ReleaseHTTPRequest( state.m_hEquipmentRequest ); state.m_hEquipmentRequest = INVALID_HTTPREQUEST_HANDLE; } @@ -4117,14 +4124,14 @@ void CTFGCServerSystem::WebapiEquipmentThinkRequest( CSteamID steamID, WebapiEqu Assert( state.m_pKVCurrentRequest != nullptr ); KeyValues* pKV = state.m_pKVCurrentRequest; - if ( !SteamHTTP() ) + if ( !STEAM_HTTP_INTERFACE() ) return; // Request inventory from teamfortress.com webapi CFmtStr strUrl( "%swebapi/ISDK/GetEquipment/v0001", GetWebBaseUrl() ); state.m_EquipmentRequestCompleted.Cancel(); - state.m_hEquipmentRequest = SteamHTTP()->CreateHTTPRequest( k_EHTTPMethodGET, strUrl.Get() ); + state.m_hEquipmentRequest = STEAM_HTTP_INTERFACE()->CreateHTTPRequest( k_EHTTPMethodGET, strUrl.Get() ); if ( state.m_hEquipmentRequest == INVALID_HTTPREQUEST_HANDLE ) { // try again next frame @@ -4132,18 +4139,18 @@ void CTFGCServerSystem::WebapiEquipmentThinkRequest( CSteamID steamID, WebapiEqu } // This mod's appid (NOT tf2's appid) - SteamHTTP()->SetHTTPRequestGetOrPostParameter( state.m_hEquipmentRequest, "appid", CNumStr( engine->GetAppID() ) ); + STEAM_HTTP_INTERFACE()->SetHTTPRequestGetOrPostParameter( state.m_hEquipmentRequest, "appid", CNumStr( engine->GetAppID() ) ); // Item list - SteamHTTP()->SetHTTPRequestGetOrPostParameter( state.m_hEquipmentRequest, "msg", pKV->GetString( "msg", nullptr ) ); + STEAM_HTTP_INTERFACE()->SetHTTPRequestGetOrPostParameter( state.m_hEquipmentRequest, "msg", pKV->GetString( "msg", nullptr ) ); // Authentication token - SteamHTTP()->SetHTTPRequestGetOrPostParameter( state.m_hEquipmentRequest, "ticket", pKV->GetString( "ticket", nullptr ) ); + STEAM_HTTP_INTERFACE()->SetHTTPRequestGetOrPostParameter( state.m_hEquipmentRequest, "ticket", pKV->GetString( "ticket", nullptr ) ); if ( GetUniverse() != k_EUniversePublic ) { // use beta tf2 appid on non public universes - SteamHTTP()->SetHTTPRequestGetOrPostParameter( state.m_hEquipmentRequest, "game_appid", "810" ); + STEAM_HTTP_INTERFACE()->SetHTTPRequestGetOrPostParameter( state.m_hEquipmentRequest, "game_appid", "810" ); } // Is there a way we can validate the existing so cache? We could only request the new items. @@ -4156,11 +4163,11 @@ void CTFGCServerSystem::WebapiEquipmentThinkRequest( CSteamID steamID, WebapiEqu //CGCClientSharedObjectCache* pExistingSOCache = GetSOCache( steamID ); //if ( pExistingSOCache && pExistingSOCache->BIsSubscribed() ) //{ - // SteamHTTP()->SetHTTPRequestGetOrPostParameter( state.m_hInventoryRequest, "version", CNumStr( pExistingSOCache->GetVersion() ) ); + // STEAM_HTTP_INTERFACE()->SetHTTPRequestGetOrPostParameter( state.m_hInventoryRequest, "version", CNumStr( pExistingSOCache->GetVersion() ) ); //} SteamAPICall_t callResult; - if ( !SteamHTTP()->SendHTTPRequest( state.m_hEquipmentRequest, &callResult ) ) + if ( !STEAM_HTTP_INTERFACE()->SendHTTPRequest( state.m_hEquipmentRequest, &callResult ) ) { state.Backoff(); return; @@ -4229,7 +4236,7 @@ void CTFGCServerSystem::OnWebapiEquipmentReceived( CSteamID steamID, HTTPRequest state.Backoff(); state.m_eState = kWebapiEquipmentState_RequestInventory; - if ( !SteamHTTP() ) + if ( !STEAM_HTTP_INTERFACE() ) return; if( bIOFailure || !pInfo || state.m_hEquipmentRequest != pInfo->m_hRequest ) @@ -4237,7 +4244,7 @@ void CTFGCServerSystem::OnWebapiEquipmentReceived( CSteamID steamID, HTTPRequest Assert( false ); if( state.m_hEquipmentRequest != INVALID_HTTPREQUEST_HANDLE ) { - SteamHTTP()->ReleaseHTTPRequest( state.m_hEquipmentRequest ); + STEAM_HTTP_INTERFACE()->ReleaseHTTPRequest( state.m_hEquipmentRequest ); } return; } @@ -4245,20 +4252,20 @@ void CTFGCServerSystem::OnWebapiEquipmentReceived( CSteamID steamID, HTTPRequest // request failed -- backoff and retry if ( !pInfo->m_bRequestSuccessful || pInfo->m_eStatusCode != k_EHTTPStatusCode200OK ) { - SteamHTTP()->ReleaseHTTPRequest( state.m_hEquipmentRequest ); + STEAM_HTTP_INTERFACE()->ReleaseHTTPRequest( state.m_hEquipmentRequest ); return; } // Extract the result uint32 unBytes; - Verify( SteamHTTP()->GetHTTPResponseBodySize( pInfo->m_hRequest, &unBytes ) ); + Verify( STEAM_HTTP_INTERFACE()->GetHTTPResponseBodySize( pInfo->m_hRequest, &unBytes ) ); CUtlBuffer bufInventory; bufInventory.EnsureCapacity( unBytes ); bufInventory.SeekPut( CUtlBuffer::SEEK_HEAD, unBytes ); - Verify( SteamHTTP()->GetHTTPResponseBodyData( pInfo->m_hRequest, ( uint8* )bufInventory.Base(), unBytes ) ); + Verify( STEAM_HTTP_INTERFACE()->GetHTTPResponseBodyData( pInfo->m_hRequest, ( uint8* )bufInventory.Base(), unBytes ) ); // We're done with the request now - SteamHTTP()->ReleaseHTTPRequest( pInfo->m_hRequest ); + STEAM_HTTP_INTERFACE()->ReleaseHTTPRequest( pInfo->m_hRequest ); // Parse it to json and extract the data GCSDK::CWebAPIValues* pValues = GCSDK::CWebAPIValues::ParseJSON( bufInventory );