From 93a5c43cc087c202dc7f09d6186b689054efa25e Mon Sep 17 00:00:00 2001 From: Sharpyku Date: Fri, 10 Jul 2026 14:52:56 +0300 Subject: [PATCH 01/13] Season 5 (CS2 1.41.6.9) support: bump hl2sdk to cs2 HEAD + update BotNavIgnore gamedata - sdk -> alliedmodders/hl2sdk@5f891c9 (2026-07-09, fixes g_bUpdateStringTokenDatabase ABI break) - BotNavIgnore linux signature + patch payload synced with Source2ZE/CS2Fixes upstream, validated against live 1.41.6.9 libserver.so --- gamedata/cs2fixes.games.txt | 4 ++-- sdk | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gamedata/cs2fixes.games.txt b/gamedata/cs2fixes.games.txt index f81bdf08..bb93aef5 100644 --- a/gamedata/cs2fixes.games.txt +++ b/gamedata/cs2fixes.games.txt @@ -115,7 +115,7 @@ { "library" "server" "windows" "\x0F\x84\x2A\x2A\x2A\x2A\x80\xB8\x2A\x2A\x2A\x2A\x00\x0F\x84\x2A\x2A\x2A\x2A\x80\x3D\x2A\x2A\x2A\x2A\x00\x74\x15" - "linux" "\x0F\x84\x2A\x2A\x2A\x2A\x44\x0F\xB6\xB0\x2A\x2A\x2A\x2A\x45\x84\xF6\x0F\x84" + "linux" "\x0F\x84\x2A\x2A\x2A\x2A\x44\x0F\xB6\xB8\x2A\x2A\x2A\x2A\x45\x84\xFF\x0F\x84" } // Called right after this in windows "Entity %s(%s) is ambiguously parented to..." "CBaseEntity_SetParent" @@ -338,7 +338,7 @@ "BotNavIgnore" { "windows" "\xE9\x2C\x00\x00\x00\x90" - "linux" "\xE9\x25\x00\x00\x00\x90" + "linux" "\xE9\x22\x00\x00\x00\x90" } } } diff --git a/sdk b/sdk index 0df1f3b8..5f891c90 160000 --- a/sdk +++ b/sdk @@ -1 +1 @@ -Subproject commit 0df1f3b8e39a47b8840f5d5545ef5de37761683c +Subproject commit 5f891c9026230cce0fc0a3fc4b5fef1c467a1385 From 92d45e8a7cb1937bb0ee7e59a0f04f4b4452fc0c Mon Sep 17 00:00:00 2001 From: Sharpyku Date: Fri, 10 Jul 2026 14:59:10 +0300 Subject: [PATCH 02/13] Build: compile valveextensions.proto (new networkbasetypes.proto import) --- AMBuilder | 1 + 1 file changed, 1 insertion(+) diff --git a/AMBuilder b/AMBuilder index 500d4cc2..70e3af87 100644 --- a/AMBuilder +++ b/AMBuilder @@ -71,6 +71,7 @@ for sdk_target in MMSPlugin.sdk_targets: protoc_builder = builder.tools.Protoc(protoc = sdk_target.protoc, sources = [ os.path.join(sdk['path'], 'common', 'network_connection.proto'), + os.path.join(sdk['path'], 'common', 'valveextensions.proto'), os.path.join(sdk['path'], 'common', 'networkbasetypes.proto'), os.path.join(sdk['path'], 'common', 'engine_gcmessages.proto'), os.path.join(sdk['path'], 'gcsdk', 'steammessages.proto'), From dfde32d68ec511e978401b29e7635713319f3835 Mon Sep 17 00:00:00 2001 From: Sharpyku <31898532+Sharpyku@users.noreply.github.com> Date: Sat, 11 Jul 2026 01:17:40 +0300 Subject: [PATCH 03/13] fix: exempt IN_JUMP from subtick input stripping (Season 5 scroll jump clip) Since the CS2 Season 5 jump rework (buffered jump presses via CCSPlayerLegacyJump / m_flJumpPressedTime), a mousewheel +jump press exists only in the subtick move entries. Stripping those entries made scroll jumps come out clipped ("glued to the floor") while held space still worked, because the held button survives in the tick-boundary button state. Keep stripping WASD/duck subtick moves and subtick viewangles for the consistent tick feel, but let jump through. --- src/detours.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/detours.cpp b/src/detours.cpp index 2980b369..b48eb7dd 100644 --- a/src/detours.cpp +++ b/src/detours.cpp @@ -169,8 +169,11 @@ void* FASTCALL Detour_ProcessUsercmds(CCSPlayerController* pController, CUserCmd { uint64 button = iterator->button(); - // Remove normal subtick movement inputs by button & subtick movement viewangles by pitch/yaw - if ((button >= IN_JUMP && button <= IN_MOVERIGHT && button != IN_USE) || iterator->pitch_delta() != 0.0f || iterator->yaw_delta() != 0.0f) + // Remove normal subtick movement inputs by button & subtick movement viewangles by pitch/yaw. + // IN_JUMP is exempt: since the CS2 Season 5 jump rework (buffered jump presses), a mousewheel + // +jump press exists only in the subtick moves — stripping it clips/eats scroll jumps entirely, + // while held space survives in the tick-boundary button state. + if ((button > IN_JUMP && button <= IN_MOVERIGHT && button != IN_USE) || iterator->pitch_delta() != 0.0f || iterator->yaw_delta() != 0.0f) subtickMoves->erase(iterator); else iterator++; From 8780460d970c7138c6b3e0722badb0d1e804ff83 Mon Sep 17 00:00:00 2001 From: SudoGool Date: Sat, 26 Sep 2026 16:59:42 -0500 Subject: [PATCH 04/13] Port STFixes to Metamod API 18 --- AMBuilder | 7 ++++ STFixes-API18-BUILD.md | 24 +++++++++++ sdk | 2 +- src/cs2fixes.cpp | 95 +++++++++++++++++++++++++++++------------- src/cs2fixes.h | 6 ++- 5 files changed, 101 insertions(+), 33 deletions(-) create mode 100644 STFixes-API18-BUILD.md diff --git a/AMBuilder b/AMBuilder index 500d4cc2..6be8dcf3 100644 --- a/AMBuilder +++ b/AMBuilder @@ -11,6 +11,12 @@ for sdk_target in MMSPlugin.sdk_targets: binary = MMSPlugin.HL2Library(builder, cxx, MMSPlugin.plugin_name, sdk) + # Current CS2 SDK removed tier1.a; Metamod's older build helper still + # appends it to the link inputs. Drop only that obsolete archive. + binary.compiler.postlink = [ + item for item in binary.compiler.postlink if not str(item).endswith('/tier1.a') + ] + try: version = subprocess.check_output(['git', 'describe', '--tags', '--long']).decode('ascii').strip() except subprocess.SubprocessError as e: @@ -70,6 +76,7 @@ for sdk_target in MMSPlugin.sdk_targets: ] protoc_builder = builder.tools.Protoc(protoc = sdk_target.protoc, sources = [ + os.path.join(sdk['path'], 'common', 'valveextensions.proto'), os.path.join(sdk['path'], 'common', 'network_connection.proto'), os.path.join(sdk['path'], 'common', 'networkbasetypes.proto'), os.path.join(sdk['path'], 'common', 'engine_gcmessages.proto'), diff --git a/STFixes-API18-BUILD.md b/STFixes-API18-BUILD.md new file mode 100644 index 00000000..e8df8d45 --- /dev/null +++ b/STFixes-API18-BUILD.md @@ -0,0 +1,24 @@ +# STFixes Metamod API 18 port + +This branch ports STFixes-metamod to Metamod:Source 2.0 plugin API 18. + +It preserves the upstream GPLv3 license and attribution. The port replaces +the removed SourceHook hook declarations with KHook, builds against a current +CS2 SDK, and removes obsolete string-token imports from the old SDK. + +## Reproducible build + +The port was validated against the CS2 Linux `libserver.so` build used on +2026-09-26 and Metamod:Source build `05c5c63`. Build with the Steam Runtime +Sniper SDK and Clang 16. Do not load the resulting binary on a different CS2 +build without rechecking its gamedata and ABI. + +```bash +git clone --recurse-submodules https://github.com/SudoGool/STFixes-metamod.git +cd STFixes-metamod +git checkout metamod-api18 +git -C sdk fetch origin dadd5d70170fa94cf360ed3cbfffa511dfca87b4 +git -C sdk checkout --detach dadd5d70170fa94cf360ed3cbfffa511dfca87b4 +``` + +The original upstream project is [SharpTimer/STFixes-metamod](https://github.com/SharpTimer/STFixes-metamod). diff --git a/sdk b/sdk index 0df1f3b8..dadd5d70 160000 --- a/sdk +++ b/sdk @@ -1 +1 @@ -Subproject commit 0df1f3b8e39a47b8840f5d5545ef5de37761683c +Subproject commit dadd5d70170fa94cf360ed3cbfffa511dfca87b4 diff --git a/src/cs2fixes.cpp b/src/cs2fixes.cpp index 2bb2ae49..98deb822 100644 --- a/src/cs2fixes.cpp +++ b/src/cs2fixes.cpp @@ -18,6 +18,7 @@ */ #include "cs2fixes.h" +#include "khook.hpp" #include "iserver.h" #include "appframework/IAppSystem.h" @@ -76,11 +77,6 @@ void Panic(const char *msg, ...) class GameSessionConfiguration_t { }; -SH_DECL_HOOK4_void(IServerGameClients, ClientActive, SH_NOATTRIB, 0, CPlayerSlot, bool, const char *, uint64); -SH_DECL_HOOK3_void(INetworkServerService, StartupServer, SH_NOATTRIB, 0, const GameSessionConfiguration_t&, ISource2WorldSession*, const char*); -SH_DECL_MANUALHOOK1_void(CTriggerGravityPrecache, 0, 0, 0, CEntityPrecacheContext*); -SH_DECL_MANUALHOOK1_void(CTriggerGravityEndTouch, 0, 0, 0, CBaseEntity*); - CS2Fixes g_CS2Fixes; CGameEntitySystem* g_pEntitySystem = nullptr; @@ -89,8 +85,51 @@ CPlayerManager *g_playerManager = nullptr; IVEngineServer2 *g_pEngineServer2 = nullptr; CGameConfig *g_GameConfig = nullptr; CCSGameRules *g_pGameRules = nullptr; -int g_iCTriggerGravityPrecacheId = -1; -int g_iCTriggerGravityEndTouchId = -1; + +// Metamod 2.0 build 1472 uses KHook (plugin API 18). SourceHook hooks from +// the original STFixes build cannot be loaded by this Metamod version. +static KHook::Return OnClientActivePost(IServerGameClients*, CPlayerSlot slot, + bool bLoadGame, const char* pszName, uint64 xuid) +{ + if (g_playerManager) + g_CS2Fixes.Hook_ClientActive(slot, bLoadGame, pszName, xuid); + return {KHook::Action::Ignore}; +} + +static KHook::Return OnStartupServerPost(INetworkServerService*, + const GameSessionConfiguration_t& config, ISource2WorldSession* pSession, + const char* pszMapName) +{ + if (g_pEntityListener) + g_CS2Fixes.Hook_StartupServer(config, pSession, pszMapName); + return {KHook::Action::Ignore}; +} + +static KHook::Return OnGravityPrecachePost(CBaseEntity* pThis, + CEntityPrecacheContext* param) +{ + g_CS2Fixes.Hook_CTriggerGravityPrecache(pThis, param); + return {KHook::Action::Ignore}; +} + +static KHook::Return OnGravityEndTouchPost(CBaseEntity* pThis, + CBaseEntity* pOther) +{ + g_CS2Fixes.Hook_CTriggerGravityEndTouch(pThis, pOther); + return {KHook::Action::Ignore}; +} + +static KHook::Virtual + g_ClientActiveHook(&IServerGameClients::ClientActive, nullptr, OnClientActivePost); +static KHook::Virtual + g_StartupServerHook(&INetworkServerService::StartupServer, nullptr, OnStartupServerPost); +static KHook::Virtual + g_GravityPrecacheHook(nullptr, OnGravityPrecachePost); +static KHook::Virtual + g_GravityEndTouchHook(nullptr, OnGravityEndTouchPost); +// KHook::AddGlobal reads a vtable pointer from the first word of this carrier. +static void* g_TriggerGravityVTable = nullptr; CGameEntitySystem* GameEntitySystem() { @@ -148,10 +187,6 @@ bool CS2Fixes::Load(PluginId id, ISmmAPI *ismm, char *error, size_t maxlen, bool return false; } - SH_ADD_HOOK(IServerGameClients, ClientActive, g_pSource2GameClients, SH_MEMBER(this, &CS2Fixes::Hook_ClientActive), true); - SH_ADD_HOOK(INetworkServerService, StartupServer, g_pNetworkServerService, SH_MEMBER(this, &CS2Fixes::Hook_StartupServer), true); - META_CONPRINTF( "All hooks started!\n" ); - bool bRequiredInitLoaded = true; if (!addresses::Initialize(g_GameConfig)) @@ -179,33 +214,35 @@ bool CS2Fixes::Load(PluginId id, ISmmAPI *ismm, char *error, size_t maxlen, bool if (!pTriggerGravityVTable) { snprintf(error, maxlen, "Failed to find TriggerGravity vtable\n"); - bRequiredInitLoaded = false; + return false; } int offset = g_GameConfig->GetOffset("CBaseEntity::Precache"); if (offset == -1) { snprintf(error, maxlen, "Failed to find CBaseEntity::Precache\n"); - bRequiredInitLoaded = false; + return false; } - SH_MANUALHOOK_RECONFIGURE(CTriggerGravityPrecache, offset, 0, 0); - g_iCTriggerGravityPrecacheId = SH_ADD_MANUALDVPHOOK(CTriggerGravityPrecache, pTriggerGravityVTable, SH_MEMBER(this, &CS2Fixes::Hook_CTriggerGravityPrecache), true); + g_GravityPrecacheHook.Configure(offset); offset = g_GameConfig->GetOffset("CBaseEntity::EndTouch"); if (offset == -1) { snprintf(error, maxlen, "Failed to find CBaseEntity::EndTouch\n"); - bRequiredInitLoaded = false; + return false; } - SH_MANUALHOOK_RECONFIGURE(CTriggerGravityEndTouch, offset, 0, 0); - g_iCTriggerGravityEndTouchId = SH_ADD_MANUALDVPHOOK(CTriggerGravityEndTouch, pTriggerGravityVTable, SH_MEMBER(this, &CS2Fixes::Hook_CTriggerGravityEndTouch), true); - - Message( "All hooks started!\n" ); + g_GravityEndTouchHook.Configure(offset); ConVar_Register(); g_playerManager = new CPlayerManager(); g_pEntityListener = new CEntityListener(); + g_TriggerGravityVTable = reinterpret_cast(pTriggerGravityVTable); + g_ClientActiveHook.Add(g_pSource2GameClients); + g_StartupServerHook.Add(g_pNetworkServerService); + g_GravityPrecacheHook.AddGlobal(reinterpret_cast(&g_TriggerGravityVTable)); + g_GravityEndTouchHook.AddGlobal(reinterpret_cast(&g_TriggerGravityVTable)); + Message("All KHook hooks started!\n"); // run our cfg g_pEngineServer2->ServerCommand("exec stfixes-metamod/cs2fixes"); @@ -225,10 +262,10 @@ bool CS2Fixes::Load(PluginId id, ISmmAPI *ismm, char *error, size_t maxlen, bool bool CS2Fixes::Unload(char *error, size_t maxlen) { - SH_REMOVE_HOOK(IServerGameClients, ClientActive, g_pSource2GameClients, SH_MEMBER(this, &CS2Fixes::Hook_ClientActive), true); - SH_REMOVE_HOOK(INetworkServerService, StartupServer, g_pNetworkServerService, SH_MEMBER(this, &CS2Fixes::Hook_StartupServer), true); - SH_REMOVE_HOOK_ID(g_iCTriggerGravityPrecacheId); - SH_REMOVE_HOOK_ID(g_iCTriggerGravityEndTouchId); + g_ClientActiveHook.Remove(g_pSource2GameClients); + g_StartupServerHook.Remove(g_pNetworkServerService); + g_GravityPrecacheHook.RemoveGlobal(reinterpret_cast(&g_TriggerGravityVTable)); + g_GravityEndTouchHook.RemoveGlobal(reinterpret_cast(&g_TriggerGravityVTable)); ConVar_Unregister(); @@ -291,17 +328,15 @@ void CS2Fixes::Hook_StartupServer(const GameSessionConfiguration_t& config, ISou gpGlobals = g_pEngineServer2->GetServerGlobals(); } -void CS2Fixes::Hook_CTriggerGravityPrecache(CEntityPrecacheContext* param) +void CS2Fixes::Hook_CTriggerGravityPrecache(CBaseEntity* pThis, CEntityPrecacheContext* param) { const auto kv = param->m_pKeyValues; - CTriggerGravityHandler::OnPrecache(META_IFACEPTR(CBaseEntity), kv); - RETURN_META(MRES_IGNORED); + CTriggerGravityHandler::OnPrecache(pThis, kv); } -void CS2Fixes::Hook_CTriggerGravityEndTouch(CBaseEntity* pOther) +void CS2Fixes::Hook_CTriggerGravityEndTouch(CBaseEntity* pThis, CBaseEntity* pOther) { - CTriggerGravityHandler::OnEndTouch(META_IFACEPTR(CBaseEntity), pOther); - RETURN_META(MRES_IGNORED); + CTriggerGravityHandler::OnEndTouch(pThis, pOther); } void CS2Fixes::OnLevelInit(char const* pMapName, diff --git a/src/cs2fixes.h b/src/cs2fixes.h index 30d3c850..7060a4a6 100644 --- a/src/cs2fixes.h +++ b/src/cs2fixes.h @@ -23,6 +23,8 @@ #include "networksystem/inetworkserializer.h" #include +class CEntityPrecacheContext; + class CS2Fixes : public ISmmPlugin, public IMetamodListener { public: @@ -34,8 +36,8 @@ class CS2Fixes : public ISmmPlugin, public IMetamodListener public: //hooks void Hook_ClientActive( CPlayerSlot slot, bool bLoadGame, const char *pszName, uint64 xuid ); void Hook_StartupServer(const GameSessionConfiguration_t& config, ISource2WorldSession *pSession, const char *pszMapName); - void Hook_CTriggerGravityPrecache(CEntityPrecacheContext* param); - void Hook_CTriggerGravityEndTouch(CBaseEntity* pOther); + void Hook_CTriggerGravityPrecache(CBaseEntity* pThis, CEntityPrecacheContext* param); + void Hook_CTriggerGravityEndTouch(CBaseEntity* pThis, CBaseEntity* pOther); void OnLevelInit(char const* pMapName, char const* pMapEntities, char const* pOldLevel, From df1348997518f319906c4347f95992be985dd975 Mon Sep 17 00:00:00 2001 From: SudoGool <76273607+SudoGool@users.noreply.github.com> Date: Sat, 26 Sep 2026 16:59:50 -0500 Subject: [PATCH 05/13] Add Windows API18 build workflow --- .github/workflows/build-windows-api18.yml | 64 +++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 .github/workflows/build-windows-api18.yml diff --git a/.github/workflows/build-windows-api18.yml b/.github/workflows/build-windows-api18.yml new file mode 100644 index 00000000..18f44f07 --- /dev/null +++ b/.github/workflows/build-windows-api18.yml @@ -0,0 +1,64 @@ +name: Build Windows API18 release + +on: + workflow_dispatch: + +jobs: + build: + runs-on: windows-latest + permissions: + contents: write + steps: + - name: Check out API18 source + uses: actions/checkout@v4 + with: + ref: metamod-api18 + submodules: recursive + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: Prepare AMBuild and Metamod + shell: pwsh + run: | + git clone https://github.com/alliedmodders/ambuild.git "$env:RUNNER_TEMP\ambuild" + python -m pip install "$env:RUNNER_TEMP\ambuild" + git clone https://github.com/alliedmodders/metamod-source.git "$env:RUNNER_TEMP\mmsource-2.0" + git -C "$env:RUNNER_TEMP\mmsource-2.0" checkout 05c5c63 + git -C "$env:RUNNER_TEMP\mmsource-2.0" submodule update --init --recursive + "MMSOURCE20=$env:RUNNER_TEMP\mmsource-2.0" | Out-File -FilePath $env:GITHUB_ENV -Append + "HL2SDKCS2=$((Get-Location).Path)\sdk" | Out-File -FilePath $env:GITHUB_ENV -Append + + - name: Build Windows DLL + shell: pwsh + run: | + New-Item -ItemType Directory -Force build | Out-Null + Set-Location build + python ..\configure.py --enable-optimize --sdks cs2 --targets x86_64 --mms_path "$env:MMSOURCE20" --hl2sdk-root "$(Split-Path $env:HL2SDKCS2)" + ambuild + + - name: Package Windows asset + shell: pwsh + run: | + $dll = Get-ChildItem -Recurse -Path build\package -Filter stfixes-metamod.dll | Select-Object -First 1 + if (-not $dll) { throw 'Build produced no stfixes-metamod.dll' } + $stage = Join-Path $env:RUNNER_TEMP 'stfixes-stage' + New-Item -ItemType Directory -Force "$stage\addons\stfixes-metamod\bin\win64", "$stage\addons\stfixes-metamod\gamedata", "$stage\addons\metamod" | Out-Null + Copy-Item $dll.FullName "$stage\addons\stfixes-metamod\bin\win64\stfixes-metamod.dll" + Copy-Item gamedata\cs2fixes.games.txt "$stage\addons\stfixes-metamod\gamedata\cs2fixes.games.txt" + @' + "Metamod Plugin" + { + "alias" "stfixes-metamod" + "file" "addons/stfixes-metamod/bin/win64/stfixes-metamod" + } + '@ | Set-Content "$stage\addons\metamod\stfixes-metamod.vdf" + Compress-Archive -Path "$stage\*" -DestinationPath STFixes-metamod-api18-windows-x86_64.zip -Force + + - name: Upload release asset + shell: pwsh + env: + GH_TOKEN: ${{ github.token }} + run: gh release upload api18-1.0.0 STFixes-metamod-api18-windows-x86_64.zip --repo "${{ github.repository }}" --clobber From 66cb85064ed50518a3a4480ca4a290725cc82087 Mon Sep 17 00:00:00 2001 From: SudoGool <76273607+SudoGool@users.noreply.github.com> Date: Sat, 26 Sep 2026 17:06:12 -0500 Subject: [PATCH 06/13] Fix Windows KHook headers --- .github/workflows/build-windows-api18.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build-windows-api18.yml b/.github/workflows/build-windows-api18.yml index 18f44f07..832c14a7 100644 --- a/.github/workflows/build-windows-api18.yml +++ b/.github/workflows/build-windows-api18.yml @@ -28,6 +28,9 @@ jobs: git clone https://github.com/alliedmodders/metamod-source.git "$env:RUNNER_TEMP\mmsource-2.0" git -C "$env:RUNNER_TEMP\mmsource-2.0" checkout 05c5c63 git -C "$env:RUNNER_TEMP\mmsource-2.0" submodule update --init --recursive + $khook = Join-Path $env:RUNNER_TEMP 'mmsource-2.0\third_party\khook\include' + Copy-Item (Join-Path $khook 'khook.hpp') (Join-Path $env:RUNNER_TEMP 'mmsource-2.0\core\khook.hpp') + Copy-Item (Join-Path $khook 'khook') (Join-Path $env:RUNNER_TEMP 'mmsource-2.0\core\khook') -Recurse "MMSOURCE20=$env:RUNNER_TEMP\mmsource-2.0" | Out-File -FilePath $env:GITHUB_ENV -Append "HL2SDKCS2=$((Get-Location).Path)\sdk" | Out-File -FilePath $env:GITHUB_ENV -Append From a94d69166d424b5ab7915e504c682bc17ab975ab Mon Sep 17 00:00:00 2001 From: SudoGool <76273607+SudoGool@users.noreply.github.com> Date: Sat, 26 Sep 2026 17:11:17 -0500 Subject: [PATCH 07/13] Build Windows SDK libraries before API18 DLL --- .github/workflows/build-windows-api18.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-windows-api18.yml b/.github/workflows/build-windows-api18.yml index 832c14a7..5bfe9f39 100644 --- a/.github/workflows/build-windows-api18.yml +++ b/.github/workflows/build-windows-api18.yml @@ -32,7 +32,14 @@ jobs: Copy-Item (Join-Path $khook 'khook.hpp') (Join-Path $env:RUNNER_TEMP 'mmsource-2.0\core\khook.hpp') Copy-Item (Join-Path $khook 'khook') (Join-Path $env:RUNNER_TEMP 'mmsource-2.0\core\khook') -Recurse "MMSOURCE20=$env:RUNNER_TEMP\mmsource-2.0" | Out-File -FilePath $env:GITHUB_ENV -Append - "HL2SDKCS2=$((Get-Location).Path)\sdk" | Out-File -FilePath $env:GITHUB_ENV -Append + + - name: Download Windows SDK libraries + shell: bash + run: | + mkdir -p "$RUNNER_TEMP/dependencies/metamod-source" + cd "$RUNNER_TEMP/dependencies" + "$RUNNER_TEMP/mmsource-2.0/support/checkout-deps.sh" -s cs2 + echo "HL2SDKCS2=$RUNNER_TEMP/dependencies/hl2sdk-cs2" >> "$GITHUB_ENV" - name: Build Windows DLL shell: pwsh From efd53ade3edf0e8a04221695ab7a505b2026e626 Mon Sep 17 00:00:00 2001 From: SudoGool <76273607+SudoGool@users.noreply.github.com> Date: Sat, 26 Sep 2026 17:19:15 -0500 Subject: [PATCH 08/13] Build Windows SDK support libraries before STFixes --- .github/workflows/build-windows-api18.yml | 27 +++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/.github/workflows/build-windows-api18.yml b/.github/workflows/build-windows-api18.yml index 5bfe9f39..9ef0a02c 100644 --- a/.github/workflows/build-windows-api18.yml +++ b/.github/workflows/build-windows-api18.yml @@ -41,6 +41,33 @@ jobs: "$RUNNER_TEMP/mmsource-2.0/support/checkout-deps.sh" -s cs2 echo "HL2SDKCS2=$RUNNER_TEMP/dependencies/hl2sdk-cs2" >> "$GITHUB_ENV" + - name: Build Metamod SDK support libraries + shell: pwsh + run: | + $mm = Join-Path $env:RUNNER_TEMP 'mmsource-2.0' + $deps = Join-Path $env:RUNNER_TEMP 'dependencies' + $mmBuild = Join-Path $mm 'build-cs2' + New-Item -ItemType Directory -Force $mmBuild | Out-Null + Push-Location $mmBuild + python (Join-Path $mm 'configure.py') --enable-optimize --sdks cs2 --targets x86_64 --hl2sdk-root $deps + ambuild + Pop-Location + + $sdkLib = Join-Path $deps 'hl2sdk-cs2\lib\public\win64' + New-Item -ItemType Directory -Force $sdkLib | Out-Null + foreach ($name in @('tier0.lib', 'tier1.lib', 'mathlib.lib', 'interfaces.lib')) { + $candidate = Get-ChildItem -Path $mmBuild -Recurse -Filter $name -File | + Where-Object { $_.FullName -notmatch '\\package\\' } | + Select-Object -First 1 + if (-not $candidate) { + Write-Host "Metamod build tree:" + Get-ChildItem -Path $mmBuild -Recurse -File | Select-Object -ExpandProperty FullName + throw "Metamod build did not produce $name" + } + Copy-Item -LiteralPath $candidate.FullName -Destination (Join-Path $sdkLib $name) -Force + Write-Host "Installed $name from $($candidate.FullName)" + } + - name: Build Windows DLL shell: pwsh run: | From 6dc7b8931345f3fcce319bf46beeb179a33f9235 Mon Sep 17 00:00:00 2001 From: SudoGool <76273607+SudoGool@users.noreply.github.com> Date: Sat, 26 Sep 2026 17:25:06 -0500 Subject: [PATCH 09/13] Use current CS2 SDK manifest for Windows build --- .github/workflows/build-windows-api18.yml | 30 +++-------------------- 1 file changed, 3 insertions(+), 27 deletions(-) diff --git a/.github/workflows/build-windows-api18.yml b/.github/workflows/build-windows-api18.yml index 9ef0a02c..c6d73317 100644 --- a/.github/workflows/build-windows-api18.yml +++ b/.github/workflows/build-windows-api18.yml @@ -28,6 +28,9 @@ jobs: git clone https://github.com/alliedmodders/metamod-source.git "$env:RUNNER_TEMP\mmsource-2.0" git -C "$env:RUNNER_TEMP\mmsource-2.0" checkout 05c5c63 git -C "$env:RUNNER_TEMP\mmsource-2.0" submodule update --init --recursive + $manifest = Join-Path $env:RUNNER_TEMP 'mmsource-2.0\hl2sdk-manifests\manifests\cs2.json' + Invoke-WebRequest -UseBasicParsing 'https://raw.githubusercontent.com/alliedmodders/hl2sdk-manifests/master/manifests/cs2.json' -OutFile $manifest + if ((Get-Content -Raw $manifest) -match 'tier1\.lib') { throw 'CS2 manifest still requests obsolete tier1.lib' } $khook = Join-Path $env:RUNNER_TEMP 'mmsource-2.0\third_party\khook\include' Copy-Item (Join-Path $khook 'khook.hpp') (Join-Path $env:RUNNER_TEMP 'mmsource-2.0\core\khook.hpp') Copy-Item (Join-Path $khook 'khook') (Join-Path $env:RUNNER_TEMP 'mmsource-2.0\core\khook') -Recurse @@ -41,33 +44,6 @@ jobs: "$RUNNER_TEMP/mmsource-2.0/support/checkout-deps.sh" -s cs2 echo "HL2SDKCS2=$RUNNER_TEMP/dependencies/hl2sdk-cs2" >> "$GITHUB_ENV" - - name: Build Metamod SDK support libraries - shell: pwsh - run: | - $mm = Join-Path $env:RUNNER_TEMP 'mmsource-2.0' - $deps = Join-Path $env:RUNNER_TEMP 'dependencies' - $mmBuild = Join-Path $mm 'build-cs2' - New-Item -ItemType Directory -Force $mmBuild | Out-Null - Push-Location $mmBuild - python (Join-Path $mm 'configure.py') --enable-optimize --sdks cs2 --targets x86_64 --hl2sdk-root $deps - ambuild - Pop-Location - - $sdkLib = Join-Path $deps 'hl2sdk-cs2\lib\public\win64' - New-Item -ItemType Directory -Force $sdkLib | Out-Null - foreach ($name in @('tier0.lib', 'tier1.lib', 'mathlib.lib', 'interfaces.lib')) { - $candidate = Get-ChildItem -Path $mmBuild -Recurse -Filter $name -File | - Where-Object { $_.FullName -notmatch '\\package\\' } | - Select-Object -First 1 - if (-not $candidate) { - Write-Host "Metamod build tree:" - Get-ChildItem -Path $mmBuild -Recurse -File | Select-Object -ExpandProperty FullName - throw "Metamod build did not produce $name" - } - Copy-Item -LiteralPath $candidate.FullName -Destination (Join-Path $sdkLib $name) -Force - Write-Host "Installed $name from $($candidate.FullName)" - } - - name: Build Windows DLL shell: pwsh run: | From 17a3546c5b9b576c948017f0b912e3b318bfde48 Mon Sep 17 00:00:00 2001 From: SudoGool <76273607+SudoGool@users.noreply.github.com> Date: Sat, 26 Sep 2026 17:30:45 -0500 Subject: [PATCH 10/13] Use patched CS2 SDK manifest for Windows build --- .github/workflows/build-windows-api18.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-windows-api18.yml b/.github/workflows/build-windows-api18.yml index c6d73317..54eb5ffb 100644 --- a/.github/workflows/build-windows-api18.yml +++ b/.github/workflows/build-windows-api18.yml @@ -49,7 +49,7 @@ jobs: run: | New-Item -ItemType Directory -Force build | Out-Null Set-Location build - python ..\configure.py --enable-optimize --sdks cs2 --targets x86_64 --mms_path "$env:MMSOURCE20" --hl2sdk-root "$(Split-Path $env:HL2SDKCS2)" + python ..\configure.py --enable-optimize --sdks cs2 --targets x86_64 --mms_path "$env:MMSOURCE20" --hl2sdk-root "$(Split-Path $env:HL2SDKCS2)" --hl2sdk-manifests "$env:MMSOURCE20\hl2sdk-manifests" ambuild - name: Package Windows asset From 238b4e1c4c44d66e3948fac4ef16c885df6a3b7a Mon Sep 17 00:00:00 2001 From: Sharpyku <31898532+Sharpyku@users.noreply.github.com> Date: Mon, 28 Sep 2026 21:47:04 +0300 Subject: [PATCH 11/13] ci: stage the KHook headers and pin Metamod for the Linux build The API 18 port came with a new Windows workflow that copies Metamod's KHook headers into place, but the existing Linux CI was never touched, so it failed on the first translation unit: mmsource-2.0/core/ISmmPlugin.h:489:10: fatal error: 'khook.hpp' file not found core/ISmmPlugin.h includes "khook.hpp" as a sibling of itself, while Metamod ships KHook under third_party/khook/include. Copy the header and its directory next to ISmmPlugin.h before configuring, and fail loudly if they are not where we expect rather than letting the compiler report it several steps later. Also pin the Metamod checkout to 05c5c63 instead of tracking master. That is the build the port was validated against, and the plugin ABI moves without notice; tracking master means a green build today and an unloadable binary tomorrow. --- .github/workflows/build.yml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index eef29ace..be51d99c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -48,10 +48,23 @@ jobs: uses: actions/checkout@v4 with: repository: alliedmodders/metamod-source - ref: master + # Pinned, not master: this is the build the API 18 port was validated + # against, and Metamod's plugin ABI moves without warning. + ref: 05c5c63 path: mmsource-2.0 submodules: recursive + # core/ISmmPlugin.h includes "khook.hpp" as a sibling, but Metamod keeps + # KHook in third_party/khook/include. Without this the build dies with + # "fatal error: 'khook.hpp' file not found" on the first translation unit. + - name: Stage KHook headers + shell: bash + run: | + khook=mmsource-2.0/third_party/khook/include + test -f "$khook/khook.hpp" || { echo "KHook headers missing at $khook"; exit 1; } + cp "$khook/khook.hpp" mmsource-2.0/core/khook.hpp + cp -r "$khook/khook" mmsource-2.0/core/khook + - name: Checkout AMBuild uses: actions/checkout@v4 with: From 8e13d91fe697431319290c1d2d952c59ebf94c9e Mon Sep 17 00:00:00 2001 From: Sharpyku <31898532+Sharpyku@users.noreply.github.com> Date: Mon, 28 Sep 2026 22:01:19 +0300 Subject: [PATCH 12/13] ci: pin Metamod by full SHA, which is the only form checkout accepts actions/checkout does not resolve an abbreviated SHA. Given ref: 05c5c63 it treats the value as a ref name pattern and fetches refs/heads/05c5c63* and refs/tags/05c5c63*, matches nothing, and fails after three attempts: git -c protocol.version=2 fetch --no-tags --prune --depth=1 origin +refs/heads/05c5c63*:refs/remotes/origin/05c5c63* ... The process '/usr/bin/git' failed with exit code 1 Spell the commit out in full. Same commit, same intent as the previous change. --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index be51d99c..6a8b79cc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -50,7 +50,7 @@ jobs: repository: alliedmodders/metamod-source # Pinned, not master: this is the build the API 18 port was validated # against, and Metamod's plugin ABI moves without warning. - ref: 05c5c63 + ref: 05c5c63a9d595cb84021c3a51fe38366ba4a12a5 path: mmsource-2.0 submodules: recursive From 3158a0c595e2e2ae6037a64992436e0ac4ec125a Mon Sep 17 00:00:00 2001 From: Sharpyku <31898532+Sharpyku@users.noreply.github.com> Date: Fri, 23 Oct 2026 08:54:40 +0300 Subject: [PATCH 13/13] Read the server globals from the network game server (CS2 1.41.9.2) Since the CS2 update of 2026-10-22 (PatchVersion 1.41.9.2), every server with this plugin crashed about 18 seconds after boot, the moment the first client (a bot or a player) connected: [CS2Fixes] Hook_ClientActive(0, 0, "Dabisi", 0) Segmentation fault (core dumped) The core dump puts the fault in CPlayerManager::OnClientConnected+34, reading gpGlobals->maxClients with gpGlobals == NULL. The StartupServer hook did run (g_pEntitySystem is set in the same function), so it is IVEngineServer2::GetServerGlobals that now returns null under the SDK's vtable layout. Read the globals from INetworkGameServer::GetGlobals instead, which is how CounterStrikeSharp and upstream CS2Fixes get them, and which still works on the updated servers. Also refuse to track a client, rather than crash, if the globals are still unknown when it connects, and make GetPlayer return null in that case. --- src/cs2fixes.cpp | 17 ++++++++++++++--- src/playermanager.cpp | 2 +- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/cs2fixes.cpp b/src/cs2fixes.cpp index 98deb822..3a3bf5de 100644 --- a/src/cs2fixes.cpp +++ b/src/cs2fixes.cpp @@ -143,9 +143,13 @@ INetworkGameServer* GetNetworkGameServer() return g_pNetworkServerService->GetIGameServer(); } +// IVEngineServer2::GetServerGlobals returns null since the CS2 update of 2026-10-22 (1.41.9.2), so the +// first client to connect crashed the server on a null gpGlobals. The network game server still hands +// out the globals, which is how CounterStrikeSharp and upstream CS2Fixes read them. CGlobalVars* GetGlobals() { - return g_pEngineServer2->GetServerGlobals(); + INetworkGameServer* pServer = GetNetworkGameServer(); + return pServer ? pServer->GetGlobals() : nullptr; } PLUGIN_EXPOSE(CS2Fixes, g_CS2Fixes); @@ -253,7 +257,7 @@ bool CS2Fixes::Load(PluginId id, ISmmAPI *ismm, char *error, size_t maxlen, bool { g_pEntitySystem = GameEntitySystem(); g_pEntitySystem->AddListenerEntity(g_pEntityListener); - gpGlobals = g_pEngineServer2->GetServerGlobals(); + gpGlobals = GetGlobals(); } @@ -318,6 +322,13 @@ CServerSideClient *GetClientBySlot(CPlayerSlot slot) void CS2Fixes::Hook_ClientActive( CPlayerSlot slot, bool bLoadGame, const char *pszName, uint64 xuid ) { Message( "Hook_ClientActive(%d, %d, \"%s\", %lli)\n", slot, bLoadGame, pszName, xuid ); + if (!gpGlobals) + gpGlobals = GetGlobals(); + if (!gpGlobals) + { + Message("No server globals yet, client %d not tracked\n", slot.Get()); + return; + } g_playerManager->OnClientConnected(slot, xuid); } @@ -325,7 +336,7 @@ void CS2Fixes::Hook_StartupServer(const GameSessionConfiguration_t& config, ISou { g_pEntitySystem = GameEntitySystem(); g_pEntitySystem->AddListenerEntity(g_pEntityListener); - gpGlobals = g_pEngineServer2->GetServerGlobals(); + gpGlobals = GetGlobals(); } void CS2Fixes::Hook_CTriggerGravityPrecache(CBaseEntity* pThis, CEntityPrecacheContext* param) diff --git a/src/playermanager.cpp b/src/playermanager.cpp index 34e8e4c9..6a9f6b4e 100644 --- a/src/playermanager.cpp +++ b/src/playermanager.cpp @@ -128,7 +128,7 @@ void CPlayerManager::OnLateLoad() ZEPlayer *CPlayerManager::GetPlayer(CPlayerSlot slot) { - if (slot.Get() < 0 || slot.Get() >= gpGlobals->maxClients) + if (!gpGlobals || slot.Get() < 0 || slot.Get() >= gpGlobals->maxClients) return nullptr; return m_vecPlayers.at(slot.Get());