From 41bc422b197aecab6397bb62a710c42d3fbdf713 Mon Sep 17 00:00:00 2001 From: Vauff Date: Wed, 15 Jul 2026 18:11:10 -0400 Subject: [PATCH 01/25] Remove redundant IsHearingClient gamedata --- gamedata/cs2fixes.jsonc | 6 ------ src/cs2fixes.cpp | 8 ++++++-- src/detours.cpp | 10 ---------- src/detours.h | 1 - 4 files changed, 6 insertions(+), 19 deletions(-) diff --git a/gamedata/cs2fixes.jsonc b/gamedata/cs2fixes.jsonc index f18794d8..340be6e8 100644 --- a/gamedata/cs2fixes.jsonc +++ b/gamedata/cs2fixes.jsonc @@ -15,12 +15,6 @@ "windows": "48 89 5C 24 ? 48 89 74 24 ? 48 89 7C 24 ? 55 41 56 41 57 48 8D 6C 24 ? 48 81 EC ? ? ? ? 45 0F B6 F0", "linux": "55 48 8D 05 ? ? ? ? 48 89 E5 41 57 41 56 41 89 D6 31 D2" }, - "IsHearingClient": - { - "library": "engine", - "windows": "40 53 48 83 EC ? 48 8B D9 3B 51", - "linux": "55 48 89 E5 41 55 41 54 53 48 89 FB 48 83 EC ? 39 77" - }, // idk a good way to find this again, i just brute forced the vtable. offset is 136 on CTriggerPush "TriggerPush_Touch": { diff --git a/src/cs2fixes.cpp b/src/cs2fixes.cpp index 6dee2ab3..4b3ff5a6 100644 --- a/src/cs2fixes.cpp +++ b/src/cs2fixes.cpp @@ -1235,7 +1235,7 @@ bool CS2Fixes::Hook_ProcessVoiceData(const CCLCMsg_VoiceData_t& msg) { CServerSideClient* client = META_IFACEPTR(CServerSideClient); - if (!client || !GetGlobals()) + if (!client) RETURN_META_VALUE(MRES_IGNORED, true); ZEPlayer* pPlayer = g_playerManager->GetPlayer(client->GetPlayerSlot()); @@ -1243,7 +1243,11 @@ bool CS2Fixes::Hook_ProcessVoiceData(const CCLCMsg_VoiceData_t& msg) if (!pPlayer) RETURN_META_VALUE(MRES_IGNORED, true); - pPlayer->SetLastVoiceTime(GetGlobals()->curtime); + if (pPlayer->IsMuted()) + RETURN_META_VALUE(MRES_SUPERCEDE, true); + + if (GetGlobals()) + pPlayer->SetLastVoiceTime(GetGlobals()->curtime); RETURN_META_VALUE(MRES_IGNORED, true); } diff --git a/src/detours.cpp b/src/detours.cpp index 61005420..77c92554 100644 --- a/src/detours.cpp +++ b/src/detours.cpp @@ -60,7 +60,6 @@ CUtlVector g_vecDetours; DECLARE_DETOUR(UTIL_SayTextFilter, Detour_UTIL_SayTextFilter); DECLARE_DETOUR(UTIL_SayText2Filter, Detour_UTIL_SayText2Filter); -DECLARE_DETOUR(IsHearingClient, Detour_IsHearingClient); DECLARE_DETOUR(TriggerPush_Touch, Detour_TriggerPush_Touch); DECLARE_DETOUR(CBaseEntity_TakeDamageOld, Detour_CBaseEntity_TakeDamageOld); DECLARE_DETOUR(CCSPlayer_WeaponServices_CanUse, Detour_CCSPlayer_WeaponServices_CanUse); @@ -250,15 +249,6 @@ void FASTCALL Detour_TriggerPush_Touch(CTriggerPush* pPush, CBaseEntity* pOther) pOther->m_fFlags(flags); } -bool FASTCALL Detour_IsHearingClient(void* serverClient, int index) -{ - ZEPlayer* player = g_playerManager->GetPlayer(index); - if (player && player->IsMuted()) - return false; - - return IsHearingClient(serverClient, index); -} - void SayChatMessageWithTimer(IRecipientFilter& filter, const char* pText, CCSPlayerController* pPlayer, uint64 eMessageType) { VPROF("SayChatMessageWithTimer"); diff --git a/src/detours.h b/src/detours.h index 982083d7..3f5059ea 100644 --- a/src/detours.h +++ b/src/detours.h @@ -86,7 +86,6 @@ bool SetupFireOutputInternalDetour(); void FASTCALL Detour_UTIL_SayTextFilter(IRecipientFilter&, const char*, CCSPlayerController*, uint64); void FASTCALL Detour_UTIL_SayText2Filter(IRecipientFilter&, CCSPlayerController*, uint64, const char*, const char*, const char*, const char*, const char*); -bool FASTCALL Detour_IsHearingClient(void*, int); void FASTCALL Detour_TriggerPush_Touch(CTriggerPush* pPush, CBaseEntity* pOther); int64 FASTCALL Detour_CBaseEntity_TakeDamageOld(CBaseEntity* pThis, CTakeDamageInfo* pInfo, CTakeDamageResult* pResult); bool FASTCALL Detour_CCSPlayer_WeaponServices_CanUse(CCSPlayer_WeaponServices*, CBasePlayerWeapon*); From 7638baae33d931dab71ee22463a75ad65a604cbc Mon Sep 17 00:00:00 2001 From: Vauff Date: Wed, 15 Jul 2026 18:43:58 -0400 Subject: [PATCH 02/25] Ignore targetname player events in ButtonWatch These could also be detected by ButtonWatch because they fire Use/OnPressed --- src/buttonwatch.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/buttonwatch.cpp b/src/buttonwatch.cpp index ace819a0..3f3ae9f8 100644 --- a/src/buttonwatch.cpp +++ b/src/buttonwatch.cpp @@ -99,6 +99,10 @@ void ButtonWatch(const CEntityIOOutput* pThis, CEntityInstance* pActivator, CEnt CCSPlayerController* ccsPlayer = CCSPlayerController::FromPawn(static_cast(pActivator)); std::string strPlayerName = ccsPlayer->GetPlayerName(); + const char* pszButtonName = ((CBaseEntity*)pCaller)->GetName(); + + if (!V_strcasecmp("game_playerdie", pszButtonName) || !V_strcasecmp("game_playerkill", pszButtonName) || !V_strcasecmp("game_playerjoin", pszButtonName) || !V_strcasecmp("game_playerspawn", pszButtonName) || !V_strcasecmp("game_playerleave", pszButtonName)) + return; ZEPlayer* zpPlayer = ccsPlayer->GetZEPlayer(); std::string strPlayerID = ""; @@ -108,7 +112,7 @@ void ButtonWatch(const CEntityIOOutput* pThis, CEntityInstance* pActivator, CEnt strPlayerID = "(" + strPlayerID + ")"; } - std::string strButton = std::to_string(pCaller->GetEntityIndex().Get()) + " " + std::string(((CBaseEntity*)pCaller)->GetName()); + std::string strButton = std::to_string(pCaller->GetEntityIndex().Get()) + " " + std::string(pszButtonName); for (int i = 0; i < GetGlobals()->maxClients; i++) { From d61614bad3b06477a99853217cf84206cf3409ce Mon Sep 17 00:00:00 2001 From: Vauff Date: Fri, 17 Jul 2026 00:16:50 -0400 Subject: [PATCH 03/25] Update actions --- .github/workflows/build.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a961d271..59de4c99 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -37,14 +37,14 @@ jobs: container: registry.gitlab.steamos.cloud/steamrt/sniper/sdk steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: path: CS2Fixes submodules: recursive fetch-depth: 0 - name: Checkout Metamod - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: repository: alliedmodders/metamod-source ref: master @@ -52,7 +52,7 @@ jobs: submodules: recursive - name: Checkout AMBuild - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: repository: alliedmodders/ambuild path: ambuild @@ -83,7 +83,7 @@ jobs: ambuild - name: Upload artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: ${{ runner.os }} path: CS2Fixes/build/package/cs2 @@ -96,7 +96,7 @@ jobs: steps: - name: Download artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 - name: Package run: | From 873f27be55673335561f7bb4c544e096eb49b82e Mon Sep 17 00:00:00 2001 From: Vauff Date: Fri, 17 Jul 2026 23:34:32 -0400 Subject: [PATCH 04/25] Use lower severity for a common warning log --- src/gamesystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gamesystem.cpp b/src/gamesystem.cpp index 6d05c7df..ed00ec15 100644 --- a/src/gamesystem.cpp +++ b/src/gamesystem.cpp @@ -109,7 +109,7 @@ bool UnregisterGameSystem() if (!pDispatcher || !pGameSystems) { - Panic("Gamesystems and/or dispatchers is null, server is probably shutting down\n"); + Message("Gamesystems and/or dispatchers is null, server is probably shutting down\n"); return false; } From 8abdaa8910f26303ac71dcf20594e361c4a37b9b Mon Sep 17 00:00:00 2001 From: Vauff Date: Wed, 22 Jul 2026 21:09:20 -0400 Subject: [PATCH 05/25] Update SDK --- hl2sdk-manifests | 2 +- sdk | 2 +- src/addresses.h | 2 +- src/cs2_sdk/entity/ctakedamageinfo.h | 1 + src/cs2_sdk/entity/globaltypes.h | 28 ---------------------------- src/cs2fixes.h | 1 - src/detours.cpp | 3 ++- src/playermanager.h | 1 + 8 files changed, 7 insertions(+), 33 deletions(-) diff --git a/hl2sdk-manifests b/hl2sdk-manifests index f9558bcc..20b3a014 160000 --- a/hl2sdk-manifests +++ b/hl2sdk-manifests @@ -1 +1 @@ -Subproject commit f9558bcc764598cb94c676ea0d0d3870a9b40353 +Subproject commit 20b3a014264b38908c4a5d4eb263ba2c488f3dc1 diff --git a/sdk b/sdk index 5f891c90..e7ed969e 160000 --- a/sdk +++ b/sdk @@ -1 +1 @@ -Subproject commit 5f891c9026230cce0fc0a3fc4b5fef1c467a1385 +Subproject commit e7ed969ea8cb9afebd19ba6c5f262c9048890543 diff --git a/src/addresses.h b/src/addresses.h index 18a43c00..08babbe0 100644 --- a/src/addresses.h +++ b/src/addresses.h @@ -75,7 +75,7 @@ namespace addresses bool Initialize(CGameConfig* g_GameConfig); bool InitializeBanMap(CGameConfig* g_GameConfig); - inline CUtlMap* sm_mapGcBanInformation; + inline CUtlOrderedMap* sm_mapGcBanInformation; inline void(FASTCALL* SetGroundEntity)(CBaseEntity* ent, CBaseEntity* ground, CBaseEntity* unk3); inline void(FASTCALL* SetGravityScale)(CBaseEntity*, float); diff --git a/src/cs2_sdk/entity/ctakedamageinfo.h b/src/cs2_sdk/entity/ctakedamageinfo.h index 3ea47aa7..ebc907b9 100644 --- a/src/cs2_sdk/entity/ctakedamageinfo.h +++ b/src/cs2_sdk/entity/ctakedamageinfo.h @@ -19,6 +19,7 @@ #pragma once #include "ehandle.h" +#include "gametrace.h" #include enum DamageTypes_t : uint32_t diff --git a/src/cs2_sdk/entity/globaltypes.h b/src/cs2_sdk/entity/globaltypes.h index 7d8f42cf..285bb478 100644 --- a/src/cs2_sdk/entity/globaltypes.h +++ b/src/cs2_sdk/entity/globaltypes.h @@ -22,34 +22,6 @@ #include "soundflags.h" #include -enum InputBitMask_t : uint64_t -{ - // MEnumeratorIsNotAFlag - IN_NONE = 0x0, - // MEnumeratorIsNotAFlag - IN_ALL = 0xffffffffffffffff, - IN_ATTACK = 0x1, - IN_JUMP = 0x2, - IN_DUCK = 0x4, - IN_FORWARD = 0x8, - IN_BACK = 0x10, - IN_USE = 0x20, - IN_TURNLEFT = 0x80, - IN_TURNRIGHT = 0x100, - IN_MOVELEFT = 0x200, - IN_MOVERIGHT = 0x400, - IN_ATTACK2 = 0x800, - IN_RELOAD = 0x2000, - IN_SPEED = 0x10000, - IN_JOYAUTOSPRINT = 0x20000, - // MEnumeratorIsNotAFlag - IN_FIRST_MOD_SPECIFIC_BIT = 0x100000000, - IN_USEORRELOAD = 0x100000000, - IN_SCORE = 0x200000000, - IN_ZOOM = 0x400000000, - IN_LOOK_AT_WEAPON = 0x800000000, -}; - enum EInButtonState : uint32_t { IN_BUTTON_UP = 0x0, diff --git a/src/cs2fixes.h b/src/cs2fixes.h index 9fa6c05a..ea13cc11 100644 --- a/src/cs2fixes.h +++ b/src/cs2fixes.h @@ -28,7 +28,6 @@ #include "public/ics2fixes.h" #include "steam/isteamhttp.h" #include -#include #include #include diff --git a/src/detours.cpp b/src/detours.cpp index 77c92554..5671bbeb 100644 --- a/src/detours.cpp +++ b/src/detours.cpp @@ -22,6 +22,7 @@ #include "usercmd.pb.h" #include "addresses.h" +#include "bspflags.h" #include "buttonwatch.h" #include "cdetour.h" #include "commands.h" @@ -818,7 +819,7 @@ void FASTCALL Detour_GameSystem_Think_CheckSteamBan() if (!g_cvarFixGameBans.Get()) return; - CUtlMap* pMap = addresses::sm_mapGcBanInformation; + auto pMap = addresses::sm_mapGcBanInformation; unsigned int count = pMap->Count(); // After player has been kicked, remove any ban entries, to prevent spreading to all new joining players diff --git a/src/playermanager.h b/src/playermanager.h index 835b0468..6480dab0 100644 --- a/src/playermanager.h +++ b/src/playermanager.h @@ -25,6 +25,7 @@ #include "entity/cpointworldtext.h" #include "entity/lights.h" #include "gamesystem.h" +#include "in_buttons.h" #include "steam/isteamuser.h" #include "steam/steam_api_common.h" #include "steam/steamclientpublic.h" From 0686a807d790ef22407aa83e33b40bbed6531b51 Mon Sep 17 00:00:00 2001 From: Vauff Date: Sun, 26 Jul 2026 16:41:39 -0400 Subject: [PATCH 06/25] Fix unparented beams/lasers hitting infinite loops on spawn --- CS2Fixes.vcxproj | 1 + CS2Fixes.vcxproj.filters | 3 +++ gamedata/cs2fixes.jsonc | 14 ++++++++++++++ src/cs2_sdk/entity/cbeam.h | 30 ++++++++++++++++++++++++++++++ src/detours.cpp | 23 +++++++++++++++++++++++ src/detours.h | 5 ++++- 6 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 src/cs2_sdk/entity/cbeam.h diff --git a/CS2Fixes.vcxproj b/CS2Fixes.vcxproj index 61b6b5a9..d82eb31b 100644 --- a/CS2Fixes.vcxproj +++ b/CS2Fixes.vcxproj @@ -232,6 +232,7 @@ + diff --git a/CS2Fixes.vcxproj.filters b/CS2Fixes.vcxproj.filters index 68bb9f35..23ee92a5 100644 --- a/CS2Fixes.vcxproj.filters +++ b/CS2Fixes.vcxproj.filters @@ -244,6 +244,9 @@ Header Files\cs2_sdk\entity + + Header Files\cs2_sdk\entity + Header Files diff --git a/gamedata/cs2fixes.jsonc b/gamedata/cs2fixes.jsonc index 340be6e8..f3515276 100644 --- a/gamedata/cs2fixes.jsonc +++ b/gamedata/cs2fixes.jsonc @@ -381,6 +381,20 @@ "library": "server", "windows": "48 89 5C 24 ? 48 89 74 24 ? 48 89 7C 24 ? 55 48 8D 6C 24 ? 48 81 EC ? ? ? ? 48 8B DA 48 8B F1 48 8D 55", "linux": "55 48 89 E5 41 57 49 89 FF 48 89 F7 41 56 41 55 41 54 53 48 81 EC ? ? ? ? E8" + }, + // These two functions are called by CEnvBeam & CEnvLaser member functions, one of those functions has "env_beams cannot have the end entity be the beam itself" string + // In both call locations, SetBeamOrigin is called first, then SetBeamEndPos + "SetBeamOrigin": + { + "library": "server", + "windows": "48 89 5C 24 ? 57 48 81 EC ? ? ? ? 48 8B FA 48 8B D9 E8 ? ? ? ? 48 8B CB", + "linux": "55 48 89 E5 41 54 49 89 F4 53 48 89 FB 48 83 EC ? 0F 1F 80" + }, + "SetBeamEndPos": + { + "library": "server", + "windows": "48 8B C4 48 89 58 ? 57 48 81 EC ? ? ? ? 0F 29 70 ? 48 8B FA 0F 29 78 ? 48 8B D9", + "linux": "55 48 89 E5 41 57 41 56 41 55 41 54 49 89 F4 53 48 89 FB 48 81 EC ? ? ? ? 66 0F 1F 44 00" } }, "Offsets": diff --git a/src/cs2_sdk/entity/cbeam.h b/src/cs2_sdk/entity/cbeam.h new file mode 100644 index 00000000..ec7fe90d --- /dev/null +++ b/src/cs2_sdk/entity/cbeam.h @@ -0,0 +1,30 @@ +/** + * ============================================================================= + * CS2Fixes + * Copyright (C) 2023-2026 Source2ZE + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#pragma once + +#include "cbasemodelentity.h" + +class CBeam : public CBaseModelEntity +{ +public: + DECLARE_SCHEMA_CLASS(CBeam); + + SCHEMA_FIELD(VectorWS, m_vecEndPos) +}; \ No newline at end of file diff --git a/src/detours.cpp b/src/detours.cpp index 5671bbeb..3d21b9fc 100644 --- a/src/detours.cpp +++ b/src/detours.cpp @@ -32,6 +32,7 @@ #include "detours.h" #include "entities.h" #include "entity/cbasemodelentity.h" +#include "entity/cbeam.h" #include "entity/ccsplayercontroller.h" #include "entity/ccsplayerpawn.h" #include "entity/ccsweaponbase.h" @@ -89,6 +90,8 @@ DECLARE_DETOUR(CCSPlayer_ItemServices_CanAcquire, Detour_CCSPlayer_ItemServices_ DECLARE_DETOUR(CS_Script_SetModel, Detour_CS_Script_SetModel); DECLARE_DETOUR(CBaseModelEntity_SetModel, Detour_CBaseModelEntity_SetModel); DECLARE_DETOUR(CCSGameRules_GoToIntermission, Detour_CCSGameRules_GoToIntermission); +DECLARE_DETOUR(SetBeamOrigin, Detour_SetBeamOrigin); +DECLARE_DETOUR(SetBeamEndPos, Detour_SetBeamEndPos); CConVar g_cvarBlockMolotovSelfDmg("cs2f_block_molotov_self_dmg", FCVAR_NONE, "Whether to block self-damage from molotovs", false); CConVar g_cvarBlockAllDamage("cs2f_block_all_dmg", FCVAR_NONE, "Whether to block all damage to players", false); @@ -869,6 +872,26 @@ void FASTCALL Detour_CCSGameRules_GoToIntermission(CCSGameRules* pThis, bool bAb return CCSGameRules_GoToIntermission(pThis, bAbortedMatch); } +void FASTCALL Detour_SetBeamOrigin(CBeam* pThis, const Vector* pVecPosition) +{ + // Game code still works for parented beams/lasers + if (pThis->m_CBodyComponent()->m_pSceneNode()->m_pParent()) + SetBeamOrigin(pThis, pVecPosition); + + // If no parent, then game code would hit infinite loop, just reimplement this simple path ourselves + pThis->SetAbsOrigin(*pVecPosition); +} + +void FASTCALL Detour_SetBeamEndPos(CBeam* pThis, const Vector* pVecPosition) +{ + // Game code still works for parented beams/lasers + if (pThis->m_CBodyComponent()->m_pSceneNode()->m_pParent()) + SetBeamEndPos(pThis, pVecPosition); + + // If no parent, then game code would hit infinite loop, just reimplement this simple path ourselves + pThis->m_vecEndPos = *(VectorWS*)(pVecPosition); +} + bool InitDetours(CGameConfig* gameConfig) { bool success = true; diff --git a/src/detours.h b/src/detours.h index 3f5059ea..b48e7b05 100644 --- a/src/detours.h +++ b/src/detours.h @@ -53,6 +53,7 @@ class Vector; class QAngle; class CEconItemView; class CCSGameRules; +class CBeam; struct CTakeDamageResult; // Add callback functions to this map that wish to hook into Detour_CEntityIOOutput_FireOutputInternal @@ -115,4 +116,6 @@ void FASTCALL Detour_GameSystem_Think_CheckSteamBan(); AcquireResult FASTCALL Detour_CCSPlayer_ItemServices_CanAcquire(CCSPlayer_ItemServices* pItemServices, CEconItemView* pEconItem, AcquireMethod iAcquireMethod, uint64_t unk4); void FASTCALL Detour_CS_Script_SetModel(uint64_t unk1); void FASTCALL Detour_CBaseModelEntity_SetModel(CBaseModelEntity* pModel, const char* pszModel); -void FASTCALL Detour_CCSGameRules_GoToIntermission(CCSGameRules* pThis, bool bAbortedMatch); \ No newline at end of file +void FASTCALL Detour_CCSGameRules_GoToIntermission(CCSGameRules* pThis, bool bAbortedMatch); +void FASTCALL Detour_SetBeamOrigin(CBeam* pThis, const Vector* pVecWorldPosition); +void FASTCALL Detour_SetBeamEndPos(CBeam* pThis, const Vector* pVecWorldPosition); \ No newline at end of file From c35505690f9bd5b9acc1e2257f1ecc54acff58f6 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 28 Jul 2026 16:02:01 -0400 Subject: [PATCH 07/25] Add custom ConVar whitelist & cfg parser systems (#456) * Finish ConVar whitelist system by hooking into Valve's * Add custom cfg parser to bypass ConVar whitelist Because Valve can't do it * Small tweaks --- AMBuilder | 2 + CS2Fixes.vcxproj | 4 + CS2Fixes.vcxproj.filters | 12 + PackageScript | 2 + cfg/cs2fixes/cs2fixes.cfg | 1 + cfg/cs2fixes/server.cfg.example | 3 + configs/cvar_whitelist.jsonc.example | 563 +++++++++++++++++++++++++++ gamedata/cs2fixes.jsonc | 7 + src/cfgparser.cpp | 80 ++++ src/cfgparser.h | 29 ++ src/cs2fixes.cpp | 38 +- src/cvarwhitelist.cpp | 122 ++++++ src/cvarwhitelist.h | 54 +++ src/detours.cpp | 10 + src/detours.h | 3 +- src/map_votes.cpp | 15 +- src/map_votes.h | 2 +- src/mapmigrations.cpp | 8 +- src/mapmigrations.h | 2 +- 19 files changed, 924 insertions(+), 33 deletions(-) create mode 100644 cfg/cs2fixes/server.cfg.example create mode 100644 configs/cvar_whitelist.jsonc.example create mode 100644 src/cfgparser.cpp create mode 100644 src/cfgparser.h create mode 100644 src/cvarwhitelist.cpp create mode 100644 src/cvarwhitelist.h diff --git a/AMBuilder b/AMBuilder index 033d025c..b79020ae 100644 --- a/AMBuilder +++ b/AMBuilder @@ -70,6 +70,8 @@ for sdk_target in MMSPlugin.sdk_targets: 'src/buttonwatch.cpp', 'src/topdefender.cpp', 'src/idlemanager.cpp', + 'src/cvarwhitelist.cpp', + 'src/cfgparser.cpp', 'src/mapmigrations.cpp', 'sdk/entity2/entitysystem.cpp', 'sdk/entity2/entityidentity.cpp', diff --git a/CS2Fixes.vcxproj b/CS2Fixes.vcxproj index d82eb31b..9b0b1648 100644 --- a/CS2Fixes.vcxproj +++ b/CS2Fixes.vcxproj @@ -213,6 +213,8 @@ + + @@ -291,6 +293,8 @@ + + diff --git a/CS2Fixes.vcxproj.filters b/CS2Fixes.vcxproj.filters index 23ee92a5..ecb355ea 100644 --- a/CS2Fixes.vcxproj.filters +++ b/CS2Fixes.vcxproj.filters @@ -179,6 +179,12 @@ Source Files + + Source Files + + + Source Files + Source Files\utils @@ -394,6 +400,12 @@ Header Files + + Header Files + + + Header Files + Header Files\cs2_sdk\entity diff --git a/PackageScript b/PackageScript index 6e96f562..b68cbc84 100644 --- a/PackageScript +++ b/PackageScript @@ -115,7 +115,9 @@ for task in MMSPlugin.binaries: builder.AddCopy(os.path.join('configs', 'admins.jsonc.example'), configs_folder) builder.AddCopy(os.path.join('configs', 'discordbots.jsonc.example'), configs_folder) builder.AddCopy(os.path.join('configs', 'maplist.jsonc.example'), configs_folder) + builder.AddCopy(os.path.join('configs', 'cvar_whitelist.jsonc.example'), configs_folder) builder.AddCopy(os.path.join('cfg', MMSPlugin.metadata['name'], 'cs2fixes.cfg'), cfg_folder) + builder.AddCopy(os.path.join('cfg', MMSPlugin.metadata['name'], 'server.cfg.example'), cfg_folder) builder.AddCopy(os.path.join('cfg', MMSPlugin.metadata['name'], 'maps', 'de_somemap.cfg'), mapcfg_folder) builder.AddCopy(os.path.join('configs', 'zr', 'playerclass.jsonc.example'), zr_folder) builder.AddCopy(os.path.join('configs', 'zr', 'weapons.cfg.example'), zr_folder) diff --git a/cfg/cs2fixes/cs2fixes.cfg b/cfg/cs2fixes/cs2fixes.cfg index e7cdac87..3efc1a4a 100644 --- a/cfg/cs2fixes/cs2fixes.cfg +++ b/cfg/cs2fixes/cs2fixes.cfg @@ -24,6 +24,7 @@ cs2f_prevent_using_players 0 // Whether to prevent +use from hitting players ( cs2f_map_steamids_enable 0 // Whether to make Steam ID's available to maps cs2f_fix_game_bans 0 // Whether to fix CS2 game bans spreading to all new joining players cs2f_free_armor 0 // Whether kevlar (1+) and/or helmet (2) are given automatically +cs2f_cvarwhitelist_enable 0 // Whether to enable the custom ConVar whitelist for maps cs2f_block_particle_msgs 0 // Whether to block CUserMsg_ParticleManager messages to fix lag/crashes, experimental cs2f_disable_setmodel 0 // Whether to disable SetModel usage from maps (custom input, cs_script function) cs2f_allow_duck_spam 0 // Whether to allow duck spamming by removing the duck slowdown, clients will only partially predict [0 = disabled, 1 = both teams, 2 = T only, 3 = CT only] diff --git a/cfg/cs2fixes/server.cfg.example b/cfg/cs2fixes/server.cfg.example new file mode 100644 index 00000000..927a578f --- /dev/null +++ b/cfg/cs2fixes/server.cfg.example @@ -0,0 +1,3 @@ +// This config is executed every map start like cfg/server.cfg, but fixes a bug for properly bypassing the ConVar whitelist +// Like exec, use exec_custom to execute another cfg through this custom cfg parser: +// exec_custom gamemode_casual_custom diff --git a/configs/cvar_whitelist.jsonc.example b/configs/cvar_whitelist.jsonc.example new file mode 100644 index 00000000..e2efb939 --- /dev/null +++ b/configs/cvar_whitelist.jsonc.example @@ -0,0 +1,563 @@ +{ + // Since maps can change cvars that are enabled here, make sure they all get reset to desired values on map change by server config(s)! + + /* Map specific overrides can also be set up as so: + "ze_my_first_ze_map": + { + "sv_airaccelerate": false, + "sv_noclipspeed": true + }, + "ze_last_man_standing_p": + { + "zr_infect_spawn_mz_ratio": true + }, + */ + + // Default whitelist, from workshop_cvar_whitelist.txt + // We disable several of these in our default config, because they don't make sense to be whitelisted, explanations are commented + "ammo_grenade_limit_default": true, + "ammo_grenade_limit_flashbang": true, + "ammo_grenade_limit_total": true, + "ammo_item_limit_healthshot": true, + "annotation_auto_load": true, + "annotation_load": true, + "bot_add": false, // Not suitable for Zombie Escape servers, bots usually not supported outside of debug + "bot_add_ct": false, // Not suitable for Zombie Escape servers, bots usually not supported outside of debug + "bot_add_t": false, // Not suitable for Zombie Escape servers, bots usually not supported outside of debug + "bot_allow_grenades": true, + "bot_allow_machine_guns": true, + "bot_allow_pistols": true, + "bot_allow_rifles": true, + "bot_allow_rogues": true, + "bot_allow_shotguns": true, + "bot_allow_snipers": true, + "bot_allow_sub_machine_guns": true, + "bot_auto_vacate": true, + "bot_autodifficulty_threshold_high": true, + "bot_autodifficulty_threshold_low": true, + "bot_chatter": true, + "bot_controllable": true, + "bot_coop_idle_max_vision_distance": true, + "bot_crouch": true, + "bot_defer_to_human_goals": true, + "bot_defer_to_human_items": true, + "bot_difficulty": true, + "bot_dont_shoot": true, + "bot_eco_limit": true, + "bot_flipout": true, + "bot_force_duck": true, + "bot_freeze": true, + "bot_ignore_enemies": true, + "bot_ignore_players": true, + "bot_join_after_player": true, + "bot_join_delay": true, + "bot_join_in_warmup": true, + "bot_join_team": true, + "bot_kick": true, + "bot_kill": true, + "bot_knives_only": true, + "bot_max_visible_smoke_length": true, + "bot_max_vision_distance_override": true, + "bot_mimic": true, + "bot_prefix": true, + "bot_quota": false, // Not suitable for Zombie Escape servers, bots usually not supported outside of debug + "bot_quota_mode": true, + "bot_stop": true, + "bot_walk": true, + "bot_zombie": true, + "buddha": true, + "buddha_reset_hp": true, + "callvote": true, + "cash_player_bomb_defused": true, + "cash_player_bomb_planted": true, + "cash_player_damage_hostage": true, + "cash_player_get_killed": true, + "cash_player_interact_with_hostage": true, + "cash_player_killed_enemy_default": true, + "cash_player_killed_enemy_factor": true, + "cash_player_killed_hostage": true, + "cash_player_killed_teammate": true, + "cash_player_rescued_hostage": true, + "cash_player_respawn_amount": true, + "cash_team_bonus_shorthanded": true, + "cash_team_elimination_bomb_map": true, + "cash_team_elimination_hostage_map_ct": true, + "cash_team_elimination_hostage_map_t": true, + "cash_team_hostage_alive": true, + "cash_team_hostage_interaction": true, + "cash_team_loser_bonus": true, + "cash_team_loser_bonus_consecutive_rounds": true, + "cash_team_per_dead_enemy": true, + "cash_team_planted_bomb_but_defused": true, + "cash_team_rescued_hostage": true, + "cash_team_terrorist_win_bomb": true, + "cash_team_win_by_defusing_bomb": true, + "cash_team_win_by_hostage_rescue": true, + "cash_team_win_by_time_running_out_bomb": true, + "cash_team_win_by_time_running_out_hostage": true, + "cl_crosshair_drawoutline": true, + "cl_crosshair_dynamic_maxdist_splitratio": true, + "cl_crosshair_dynamic_splitalpha_innermod": true, + "cl_crosshair_dynamic_splitalpha_outermod": true, + "cl_crosshair_dynamic_splitdist": true, + "cl_crosshair_friendly_warning": true, + "cl_crosshair_outlinethickness": true, + "cl_crosshair_recoil": true, + "cl_crosshair_sniper_show_normal_inaccuracy": true, + "cl_crosshair_sniper_width": true, + "cl_crosshair_t": true, + "cl_crosshairalpha": true, + "cl_crosshaircolor": true, + "cl_crosshaircolor_b": true, + "cl_crosshaircolor_g": true, + "cl_crosshaircolor_r": true, + "cl_crosshairdot": true, + "cl_crosshairgap": true, + "cl_crosshairgap_useweaponvalue": true, + "cl_crosshairsize": true, + "cl_crosshairstyle": true, + "cl_crosshairthickness": true, + "cl_crosshairusealpha": true, + "cl_draw_only_deathnotices": true, + "cl_drawhud": true, + "cl_drawhud_force_deathnotices": true, + "cl_frametime_summary_report_detailed": true, + "cl_hud_color": true, + "cl_hud_radar_scale": true, + "cl_hud_telemetry_frametime_show": true, + "cl_lock_camera": true, + "cl_player_ragdolls_collide": true, + "cl_prefer_lefthanded": true, + "cl_radar_always_centered": true, + "cl_radar_icon_scale_min": true, + "cl_radar_rotate": true, + "cl_radar_scale": true, + "cl_radar_square_with_scoreboard": true, + "cl_showfps": true, + "cl_teamcounter_playercount_instead_of_avatars": true, + "cl_teamid_overhead_colors_show": true, + "contributionscore_assist": true, + "contributionscore_assist_reqs": true, + "contributionscore_bomb_defuse_major": true, + "contributionscore_bomb_defuse_minor": true, + "contributionscore_bomb_exploded": true, + "contributionscore_bomb_planted": true, + "contributionscore_cash_bundle": true, + "contributionscore_crate_break": true, + "contributionscore_hostage_kill": true, + "contributionscore_hostage_rescue_major": true, + "contributionscore_hostage_rescue_minor": true, + "contributionscore_kill": true, + "contributionscore_kill_factor": true, + "contributionscore_kill_reqs": true, + "contributionscore_objective_kill": true, + "contributionscore_participation": true, + "contributionscore_suicide": true, + "contributionscore_team_kill": true, + "crosshair": true, + "custom_bot_difficulty": true, + "disconnect": true, + "dsp_volume": true, + "echo": true, + "ent_create": true, + "ent_fire": true, + "ent_fire_output": true, + "ent_setname": true, + "exec": true, + "execifexists": true, + "ff_damage_bullet_penetration": true, + "ff_damage_reduction_bullets": true, + "ff_damage_reduction_grenade": true, + "ff_damage_reduction_grenade_self": true, + "ff_damage_reduction_other": true, + "fps_max": true, + "game_alias": false, // Server command, should not be controlled by maps + "game_mode": false, // Server setting, should not be controlled by maps + "game_type": false, // Server setting, should not be controlled by maps + "give": true, + "god": true, + "healthshot_allow_use_at_full": true, + "healthshot_health": true, + "healthshot_healthboost_damage_multiplier": true, + "healthshot_healthboost_speed_multiplier": true, + "healthshot_healthboost_time": true, + "hinttext_displaytime": true, + "host_timescale": true, + "host_writeconfig_with_prompt": true, + "hostage_debug": true, + "hostname": false, // Server setting, should not be controlled by maps + "hud_scaling": true, + "hud_showtargetid": true, + "inferno_child_spawn_max_depth": true, + "inferno_max_flames": true, + "inferno_max_range": true, + "log_color": false, // Server command, should not be controlled by maps + "log_flags": false, // Server command, should not be controlled by maps + "log_verbosity": false, // Server command, should not be controlled by maps + "map_workshop": false, // Server command, should not be executed by maps + "molotov_throw_detonate_time": true, + "mp_afterroundmoney": true, + "mp_allowspectators": true, + "mp_anyone_can_pickup_c4": true, + "mp_autokick": false, // Server setting, should not be controlled by maps + "mp_autoteambalance": false, // Not suitable for Zombie Escape servers + "mp_bot_ai_bt": true, + "mp_bot_ai_bt_clear_cache": true, + "mp_buy_allow_grenades": true, + "mp_buy_allow_guns": true, + "mp_buy_anywhere": true, + "mp_buy_during_immunity": true, + "mp_buytime": true, + "mp_c4_cannot_be_defused": true, + "mp_c4timer": true, + "mp_consecutive_loss_max": true, + "mp_coop_force_join_t": true, + "mp_coopmission_bot_difficulty_offset": true, + "mp_ct_default_grenades": true, + "mp_ct_default_melee": true, + "mp_ct_default_primary": true, + "mp_ct_default_secondary": true, + "mp_damage_headshot_only": true, + "mp_damage_scale_ct_body": true, + "mp_damage_scale_ct_head": true, + "mp_damage_scale_t_body": true, + "mp_damage_scale_t_head": true, + "mp_damage_vampiric_amount": true, + "mp_death_drop_c4": true, + "mp_death_drop_defuser": true, + "mp_death_drop_grenade": true, + "mp_death_drop_gun": true, + "mp_death_drop_healthshot": true, + "mp_deathcam_skippable": true, + "mp_default_team_winner_no_objective": true, + "mp_defuser_allocation": true, + "mp_disconnect_kills_bots": false, // Server setting, should not be controlled by maps + "mp_disconnect_kills_players": false, // Server setting, should not be controlled by maps + "mp_display_kill_assists": true, + "mp_dm_bonus_percent": true, + "mp_dm_bonusweapon_dogtags": true, + "mp_dm_dogtag_score": true, + "mp_dm_kill_base_score": true, + "mp_dm_teammode": true, + "mp_dm_teammode_bonus_score": true, + "mp_dm_teammode_dogtag_score": true, + "mp_dm_teammode_kill_score": true, + "mp_dogtag_despawn_on_killer_death": true, + "mp_dogtag_despawn_time": true, + "mp_dogtag_pickup_rule": true, + "mp_drop_grenade_enable": true, + "mp_drop_knife_enable": true, + "mp_economy_reset_rounds": true, + "mp_endmatch_votenextleveltime": false, // Server setting, should not be controlled by maps + "mp_endmatch_votenextmap": false, // Server setting, should not be controlled by maps + "mp_endmatch_votenextmap_keepcurrent": false, // Server setting, should not be controlled by maps + "mp_equipment_reset_rounds": true, + "mp_force_pick_time": false, // Server setting, should not be controlled by maps + "mp_forcecamera": true, + "mp_forcerespawn": true, + "mp_fraglimit": false, // Server setting, should not be controlled by maps + "mp_free_armor": false, // Breaks buy menu rebuys, cs2f_free_armor doesn't + "mp_freezetime": true, + "mp_friendlyfire": true, + "mp_give_player_c4": true, + "mp_global_damage_per_second": true, + "mp_guardian_ai_bt_difficulty_adjust_wave_interval": true, + "mp_guardian_ai_bt_difficulty_cap_beginning_round": true, + "mp_guardian_ai_bt_difficulty_initial_value": true, + "mp_guardian_ai_bt_difficulty_max_next_level_bots": true, + "mp_guardian_bomb_plant_custom_x_mark_location": true, + "mp_guardian_bot_money_per_wave": true, + "mp_guardian_player_dist_max": true, + "mp_guardian_player_dist_min": true, + "mp_guardian_special_kills_needed": true, + "mp_guardian_special_weapon_needed": true, + "mp_guardian_target_site": true, + "mp_halftime": false, // Not suitable for Zombie Escape servers + "mp_halftime_pausetimer": true, + "mp_hostagepenalty": true, + "mp_hostages_max": true, + "mp_hostages_rescuetime": true, + "mp_hostages_spawn_force_positions": true, + "mp_hostages_spawn_force_positions_xyz": true, + "mp_hostages_spawn_same_every_round": true, + "mp_humanteam": false, // Not suitable for Zombie Escape servers + "mp_ignore_round_win_conditions": false, // Not suitable for Zombie Escape servers + "mp_items_prohibited": true, + "mp_join_grace_time": false, // Not suitable for Zombie Escape servers + "mp_limitteams": false, // Not suitable for Zombie Escape servers + "mp_match_can_clinch": false, // Server setting, should not be controlled by maps + "mp_match_end_changelevel": false, // Server setting, should not be controlled by maps + "mp_match_end_restart": false, // Server setting, should not be controlled by maps + "mp_match_restart_delay": false, // Server setting, should not be controlled by maps + "mp_max_armor": true, + "mp_maxmoney": true, + "mp_maxrounds": false, // Server setting, should not be controlled by maps + "mp_only_cts_rescue_hostages": true, + "mp_plant_c4_anywhere": true, + "mp_playercashawards": false, // Not suitable for Zombie Escape servers + "mp_promoted_item_enabled": true, + "mp_randomspawn": true, + "mp_randomspawn_dist": true, + "mp_randomspawn_los": true, + "mp_respawn_immunitytime": true, + "mp_respawn_on_death_ct": false, // Not suitable for Zombie Escape servers + "mp_respawn_on_death_t": false, // Not suitable for Zombie Escape servers + "mp_respawnwavetime_ct": true, + "mp_respawnwavetime_t": true, + "mp_restartgame": false, // Also resets timeleft, which should not be controlled by maps, just use a nuke or an info_map_parameters>FireWinCondition>10 input (won't add score to either team) + "mp_retake_ct_loadout_bonus_card": true, + "mp_retake_ct_loadout_bonus_card_availability": true, + "mp_retake_ct_loadout_default_pistol_round": true, + "mp_retake_ct_loadout_enemy_card": true, + "mp_retake_ct_loadout_full_buy_round": true, + "mp_retake_ct_loadout_light_buy_round": true, + "mp_retake_ct_loadout_upgraded_pistol_round": true, + "mp_retake_max_consecutive_rounds_same_target_site": true, + "mp_retake_t_loadout_bonus_card": true, + "mp_retake_t_loadout_bonus_card_availability": true, + "mp_retake_t_loadout_default_pistol_round": true, + "mp_retake_t_loadout_enemy_card": true, + "mp_retake_t_loadout_full_buy_round": true, + "mp_retake_t_loadout_light_buy_round": true, + "mp_retake_t_loadout_upgraded_pistol_round": true, + "mp_round_restart_delay": true, + "mp_roundtime": true, + "mp_roundtime_defuse": true, + "mp_roundtime_deployment": true, + "mp_roundtime_hostage": true, + "mp_solid_enemies": true, + "mp_solid_teammates": true, + "mp_spawnprotectiontime": false, // Server setting, should not be controlled by maps + "mp_spectators_max": true, + "mp_starting_losses": true, + "mp_startmoney": true, + "mp_suicide_penalty": true, + "mp_t_default_grenades": true, + "mp_t_default_melee": true, + "mp_t_default_primary": true, + "mp_t_default_secondary": true, + "mp_tagging_scale": false, // Server setting, should not be controlled by maps + "mp_taser_recharge_time": true, + "mp_team_intro_time": false, // Server setting, should not be controlled by maps + "mp_teamcashawards": true, + "mp_teammatchstat_holdtime": true, + "mp_teammates_are_enemies": true, + "mp_teamname_1": true, + "mp_teamname_2": true, + "mp_technical_timeout_duration_s": true, + "mp_technical_timeout_per_team": true, + "mp_timelimit": false, // Server setting, should not be controlled by maps + "mp_use_respawn_waves": false, // Not suitable for Zombie Escape servers + "mp_warmup_end": true, + "mp_warmup_items_drop_policy": true, + "mp_warmup_items_nocost": true, + "mp_warmup_items_nocount_policy": true, + "mp_warmup_offline_enabled": true, + "mp_warmup_online_enabled": true, + "mp_warmup_pausetimer": true, + "mp_warmuptime": true, + "mp_warmuptime_all_players_connected": true, + "mp_warmuptime_match_cancelled": true, + "mp_weapon_self_inflict_amount": true, + "mp_weapons_allow_heavy": true, + "mp_weapons_allow_heavyassaultsuit": true, + "mp_weapons_allow_map_placed": true, + "mp_weapons_allow_pistols": true, + "mp_weapons_allow_rifles": true, + "mp_weapons_allow_smgs": true, + "mp_weapons_allow_typecount": true, + "mp_weapons_allow_zeus": true, + "mp_weapons_max_gun_purchases_per_weapon_per_match": true, + "mp_win_panel_display_time": true, + "noclip": true, + "player_ping_token_cooldown": true, + "r_csgo_render_decals": true, + "r_csgo_render_decals_on_translucent": true, + "r_decals_overide_fadestarttime_params": true, + "r_drawviewmodel": true, + "r_fullscreen_gamma": true, + "regenerate_weapon_skins": true, + "restart": true, + "safezonex": true, + "safezoney": true, + "say": true, + "say_team": true, + "setang": true, + "setpos": true, + "slot1": true, + "slot2": true, + "slot3": true, + "slot4": true, + "slot5": true, + "slot6": true, + "slot7": true, + "slot8": true, + "slot9": true, + "snd_deathcamera_volume": true, + "snd_gamevolume": true, + "snd_headphone_eq": true, + "snd_mapobjective_volume": true, + "snd_menumusic_volume": true, + "snd_musicvolume": true, + "snd_mvp_volume": true, + "snd_roundaction_volume": true, + "snd_roundend_volume": true, + "snd_roundstart_volume": true, + "snd_spatialize_lerp": true, + "snd_steamaudio_enable_perspective_correction": true, + "snd_tensecondwarning_volume": true, + "spawn_group_load": true, + "spawn_group_unload": true, + "speaker_config": true, + "spec_freeze_deathanim_time": true, + "spec_freeze_time": true, + "spec_freeze_time_lock": true, + "spec_freeze_traveltime": true, + "spec_replay_bot": true, + "spec_replay_enable": false, // Server setting, should not be controlled by maps + "spec_replay_leadup_time": true, + "subclass_create": true, + "sv_accelerate": true, + "sv_accelerate_use_weapon_speed": true, + "sv_airaccelerate": true, + "sv_allow_annotations_access_level": true, + "sv_allow_votes": false, // Server setting, should not be controlled by maps + "sv_alltalk": false, // Server setting, should not be controlled by maps + "sv_alternateticks": true, + "sv_auto_adjust_bot_difficulty": true, + "sv_auto_full_alltalk_during_warmup_half_end": true, + "sv_autobunnyhopping": true, + "sv_autobuyammo": true, + "sv_autoexec_mapname_cfg": true, + "sv_bot_buy_decoy_weight": true, + "sv_bot_buy_flash_weight": true, + "sv_bot_buy_grenade_chance": true, + "sv_bot_buy_hegrenade_weight": true, + "sv_bot_buy_molotov_weight": true, + "sv_bot_buy_smoke_weight": true, + "sv_bots_get_easier_each_win": true, + "sv_bounce": true, + "sv_buy_status_override": true, + "sv_cheats": false, // Server setting, should not be controlled by maps + "sv_deadtalk": false, // Server setting, should not be controlled by maps + "sv_disable_immunity_alpha": true, + "sv_disable_radar": true, + "sv_disable_teamselect_menu": false, // Server setting, should not be controlled by maps + "sv_disconnected_players_cleanup_delay": false, // Server setting, should not be controlled by maps + "sv_enablebunnyhopping": true, + "sv_extract_ammo_from_dropped_weapons": true, + "sv_falldamage_scale": true, + "sv_falldamage_to_below_player_multiplier": true, + "sv_falldamage_to_below_player_ratio": true, + "sv_friction": true, + "sv_game_mode_flags": false, // Server setting, should not be controlled by maps + "sv_gameinstructor_disable": true, + "sv_gameinstructor_enable": true, + "sv_give_item": true, + "sv_gravity": true, + "sv_grenade_trajectory_prac_pipreview": true, + "sv_grenade_trajectory_prac_trailtime": true, + "sv_grenade_trajectory_time_spectator": true, + "sv_guardian_extra_equipment_ct": true, + "sv_guardian_extra_equipment_t": true, + "sv_guardian_refresh_ammo_for_items_on_waves": true, + "sv_guardian_spawn_health_ct": true, + "sv_guardian_spawn_health_t": true, + "sv_health_approach_enabled": true, + "sv_health_approach_speed": true, + "sv_hegrenade_damage_multiplier": true, + "sv_hegrenade_radius_multiplier": true, + "sv_hide_roundtime_until_seconds": true, + "sv_highlight_distance": true, + "sv_highlight_duration": true, + "sv_human_autojoin_team": false, // Server setting, should not be controlled by maps + "sv_ignoregrenaderadio": true, + "sv_infinite_ammo": true, + "sv_jump_impulse": true, + "sv_jump_spam_penalty_time": true, + "sv_kick_ban_duration": false, // Server setting, should not be controlled by maps + "sv_ladder_scale_speed": true, + "sv_max_deathmatch_respawns_per_tick": true, + "sv_maxspeed": true, + "sv_maxvelocity": true, + "sv_minimum_desired_chicken_count": true, + "sv_outofammo_indicator": true, + "sv_pure": false, // Server command, should not be controlled by maps + "sv_pure_kick_clients": false, // Server setting, should not be controlled by maps + "sv_radio_throttle_window": false, // Server setting, should not be controlled by maps + "sv_regeneration_force_on": true, + "sv_regeneration_wait_time": true, + "sv_show_team_equipment_force_on": true, + "sv_showbullethits": true, + "sv_showhitregistration": true, + "sv_showimpacts": true, + "sv_showimpacts_penetration": true, + "sv_skirmish_id": true, + "sv_staminajumpcost": true, + "sv_staminalandcost": true, + "sv_staminamax": true, + "sv_staminarecoveryrate": true, + "sv_steamauth_enforce": false, // Server setting, should not be controlled by maps + "sv_stopspeed": true, + "sv_talk_enemy_dead": false, // Server setting, should not be controlled by maps + "sv_talk_enemy_living": false, // Server setting, should not be controlled by maps + "sv_teamid_overhead_maxdist": false, // Server setting, should not be controlled by maps + "sv_teamid_overhead_maxdist_spec": false, // Server setting, should not be controlled by maps + "sv_versus_screen_scene_id": true, + "sv_vote_to_changelevel_before_match_point": false, // Server setting, should not be controlled by maps + "sv_vote_to_changelevel_rndmin": false, // Server setting, should not be controlled by maps + "sv_warmup_to_freezetime_delay": true, + "sv_wateraccelerate": true, + "sv_waterfriction": true, + "tv_delay": false, // Server setting, should not be controlled by maps + "tv_delay1": false, // Server setting, should not be controlled by maps + "viewmodel_fov": true, + "viewmodel_offset_x": true, + "viewmodel_offset_y": true, + "viewmodel_offset_z": true, + "viewmodel_presetpos": true, + "weapon_accuracy_nospread": true, + "weapon_air_spread_scale": true, + "weapon_auto_cleanup_time": true, + "weapon_max_before_cleanup": true, + "weapon_recoil_scale": true, + "weapon_reticle_knife_show": true, + "weapon_sound_falloff_multiplier": true, + "sv_legacy_jump": false, // Server setting, should not be controlled by maps + "radio": true, + "radio1": true, + "radio2": true, + "radio3": true, + "player_ping": true, + "mp_teamlogo_1": true, + "mp_teamlogo_2": true, + "noclip_fixup": true, + "fov_cs_debug": true, + "sv_show_teammate_death_notification": true, + "sv_standable_normal": true, + "sv_walkable_normal": true, + "+jump": true, + "-jump": true, + "+duck": true, + "-duck": true, + "+forward": true, + "-forward": true, + "+back": true, + "-back": true, + "+left": true, + "-left": true, + "+right": true, + "-right": true, + "+sprint": true, + "-sprint": true, + + // Custom stuff + "endround": true, + "cs2f_use_old_push": true, + "cs2f_infinite_reserve_ammo": true, + "cs2f_free_armor": true, + // Often over-abused but still have some legitimate uses, recommend either setting up map specific overrides or just globally enabling these + "zr_infect_spawn_time_min": false, + "zr_infect_spawn_time_max": false, + "zr_infect_spawn_mz_ratio": false +} \ No newline at end of file diff --git a/gamedata/cs2fixes.jsonc b/gamedata/cs2fixes.jsonc index f3515276..982ff121 100644 --- a/gamedata/cs2fixes.jsonc +++ b/gamedata/cs2fixes.jsonc @@ -395,6 +395,13 @@ "library": "server", "windows": "48 8B C4 48 89 58 ? 57 48 81 EC ? ? ? ? 0F 29 70 ? 48 8B FA 0F 29 78 ? 48 8B D9", "linux": "55 48 89 E5 41 57 41 56 41 55 41 54 49 89 F4 53 48 89 FB 48 81 EC ? ? ? ? 66 0F 1F 44 00" + }, + // Only called in an if statement by function with "DISALLOWED WORKSHOP CONVAR: %s" string + "IsCommandWhitelisted": + { + "library": "server", + "windows": "48 83 EC ? 4C 8B C2 48 8D 0D ? ? ? ? 48 8D 54 24 ? FF", + "linux": "55 48 89 F2 48 8D 35 ? ? ? ? 48 89 E5 48 83 EC" } }, "Offsets": diff --git a/src/cfgparser.cpp b/src/cfgparser.cpp new file mode 100644 index 00000000..adfe3adf --- /dev/null +++ b/src/cfgparser.cpp @@ -0,0 +1,80 @@ +/** + * ============================================================================= + * CS2Fixes + * Copyright (C) 2023-2026 Source2ZE + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include "cfgparser.h" +#include "commands.h" + +#include + +CCfgParser* g_pCfgParser = nullptr; + +CON_COMMAND_F(exec_custom, " - Execute a cfg through the custom cfg parser", FCVAR_SPONLY | FCVAR_LINKED_CONCOMMAND) +{ + if (args.ArgC() < 2) + { + Message("Usage: exec_custom \n"); + return; + } + + g_pCfgParser->ParseCfg(args[1]); +} + +void CCfgParser::ApplyGameSettings(const char* pszMapName) +{ + // Run plugin cfg + g_pCfgParser->ParseCfg("cs2fixes/cs2fixes"); + + // Run custom server cfg + g_pCfgParser->ParseCfg("cs2fixes/server"); + + if (!V_strcmp(pszMapName, "")) + return; + + // Run map cfg (if present) + // We call ParseCfg indirectly through exec_custom, so any commands within the map cfg will be added to the command buffer after nested executes in previous configs + char cmd[MAX_PATH]; + V_snprintf(cmd, sizeof(cmd), "exec_custom cs2fixes/maps/%s", pszMapName); + g_pEngineServer2->ServerCommand(cmd); +} + +void CCfgParser::ParseCfg(const char* pszCfgPath) +{ + char szPath[MAX_PATH]; + V_snprintf(szPath, sizeof(szPath), "%s/csgo/cfg/%s.cfg", Plat_GetGameDirectory(), pszCfgPath); + std::ifstream cfgFile(szPath); + + if (!cfgFile.is_open()) + { + Message("Unable to open & execute custom cfg file \"%s\"\n", pszCfgPath); + return; + } + + Message("Executing custom cfg file \"%s\"\n", pszCfgPath); + + std::string strCommand; + + while (std::getline(cfgFile, strCommand)) + { + if (!strCommand.empty() && strCommand.back() == '\r') + strCommand.pop_back(); + + if (!strCommand.empty()) + g_pEngineServer2->ServerCommand(strCommand.c_str()); + } +} diff --git a/src/cfgparser.h b/src/cfgparser.h new file mode 100644 index 00000000..78433b7d --- /dev/null +++ b/src/cfgparser.h @@ -0,0 +1,29 @@ +/** + * ============================================================================= + * CS2Fixes + * Copyright (C) 2023-2026 Source2ZE + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#pragma once + +class CCfgParser +{ +public: + void ApplyGameSettings(const char* pszMapName); + void ParseCfg(const char* pszCfgPath); +}; + +extern CCfgParser* g_pCfgParser; diff --git a/src/cs2fixes.cpp b/src/cs2fixes.cpp index 4b3ff5a6..6017f9eb 100644 --- a/src/cs2fixes.cpp +++ b/src/cs2fixes.cpp @@ -22,10 +22,12 @@ #include "adminsystem.h" #include "appframework/IAppSystem.h" +#include "cfgparser.h" #include "commands.h" #include "common.h" #include "cs_gameevents.pb.h" #include "ctimer.h" +#include "cvarwhitelist.h" #include "detours.h" #include "discord.h" #include "entities.h" @@ -355,6 +357,8 @@ bool CS2Fixes::Load(PluginId id, ISmmAPI* ismm, char* error, size_t maxlen, bool g_pIdleSystem = new CIdleSystem(); g_pPanoramaVoteHandler = new CPanoramaVoteHandler(); g_pEWHandler = new CEWHandler(); + g_pConvarWhitelist = new CConVarWhitelist(); + g_pCfgParser = new CCfgParser(); g_pMapMigrations = new CMapMigrations(); RegisterWeaponCommands(); @@ -378,7 +382,7 @@ bool CS2Fixes::Load(PluginId id, ISmmAPI* ismm, char* error, size_t maxlen, bool }); // run our cfg - g_pEngineServer2->ServerCommand("exec cs2fixes/cs2fixes"); + g_pCfgParser->ParseCfg("cs2fixes/cs2fixes"); srand(time(0)); @@ -504,6 +508,12 @@ bool CS2Fixes::Unload(char* error, size_t maxlen) delete g_pEWHandler; } + if (g_pConvarWhitelist) + delete g_pConvarWhitelist; + + if (g_pCfgParser) + delete g_pCfgParser; + if (g_pMapMigrations) delete g_pMapMigrations; @@ -1031,8 +1041,22 @@ void CS2Fixes::Hook_CheckTransmit(CCheckTransmitInfo** ppInfoList, int infoCount void CS2Fixes::Hook_ApplyGameSettings(KeyValues* pKV) { - g_pMapVoteSystem->ApplyGameSettings(pKV); - g_pMapMigrations->ApplyGameSettings(pKV); + const char* pszMapName; + uint64 iWorkshopId; + + if (pKV->FindKey("launchoptions") && pKV->FindKey("launchoptions")->FindKey("levelname")) + pszMapName = pKV->FindKey("launchoptions")->GetString("levelname"); + else + pszMapName = ""; + + if (pKV->FindKey("launchoptions") && pKV->FindKey("launchoptions")->FindKey("customgamemode")) + iWorkshopId = pKV->FindKey("launchoptions")->GetUint64("customgamemode"); + else + iWorkshopId = 0; + + g_pCfgParser->ApplyGameSettings(pszMapName); + g_pMapVoteSystem->ApplyGameSettings(pszMapName, iWorkshopId); + g_pMapMigrations->ApplyGameSettings(iWorkshopId); } void CS2Fixes::Hook_CreateWorkshopMapGroup(const char* name, const CUtlStringList& mapList) @@ -1321,14 +1345,6 @@ void CS2Fixes::OnLevelInit(char const* pMapName, { Message("OnLevelInit(%s)\n", pMapName); - // run our cfg - g_pEngineServer2->ServerCommand("exec cs2fixes/cs2fixes"); - - // Run map cfg (if present) - char cmd[MAX_PATH]; - V_snprintf(cmd, sizeof(cmd), "exec cs2fixes/maps/%s", pMapName); - g_pEngineServer2->ServerCommand(cmd); - // Only patch BotNavIgnore while a map is loaded, else adding bots will crash if (V_strcmp(pMapName, "error")) g_CommonPatches[1].PerformPatch(g_GameConfig); diff --git a/src/cvarwhitelist.cpp b/src/cvarwhitelist.cpp new file mode 100644 index 00000000..3fe5a958 --- /dev/null +++ b/src/cvarwhitelist.cpp @@ -0,0 +1,122 @@ +/** + * ============================================================================= + * CS2Fixes + * Copyright (C) 2023-2026 Source2ZE + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include "cvarwhitelist.h" +#include "commands.h" +#include "utils.h" +#undef snprintf +#include "vendor/nlohmann/json.hpp" +#include + +CConVarWhitelist* g_pConvarWhitelist = nullptr; + +CConVar g_cvarConVarWhitelistEnable("cs2f_cvarwhitelist_enable", FCVAR_NONE, "Whether to enable the custom ConVar whitelist for maps", false); + +CON_COMMAND_CHAT_FLAGS(cvarwhitelist_reload, "- Reload the ConVar whitelist config file", ADMFLAG_ROOT) +{ + if (!g_cvarConVarWhitelistEnable.Get()) + return; + + if (g_pConvarWhitelist->LoadConfig()) + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "ConVar whitelist config reloaded!"); + else + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Failed to reload ConVar whitelist config!"); +} + +bool CConVarWhitelist::LoadConfig() +{ + m_bConfigLoaded = false; + m_vecGlobalWhitelist.clear(); + m_mapOverrides.clear(); + + const char* pszCvarWhitelistPath = "addons/cs2fixes/configs/cvar_whitelist.jsonc"; + char szPath[MAX_PATH]; + V_snprintf(szPath, sizeof(szPath), "%s%s%s", Plat_GetGameDirectory(), "/csgo/", pszCvarWhitelistPath); + std::ifstream cvarWhitelistFile(szPath); + + if (!cvarWhitelistFile.is_open()) + { + Panic("Failed to open %s, ConVar whitelist not loaded!\n", pszCvarWhitelistPath); + return false; + } + + json jsonWhitelist = json::parse(cvarWhitelistFile, nullptr, false, true); + + if (jsonWhitelist.is_discarded() || !jsonWhitelist.is_object()) + { + Panic("Failed parsing JSON from %s, ConVar whitelist not loaded!\n", pszCvarWhitelistPath); + return false; + } + + for (auto& [strKey, jsonValue] : jsonWhitelist.items()) + { + if (jsonValue.is_boolean()) + { + m_vecGlobalWhitelist.push_back({StringToLower(strKey), jsonValue.get()}); + } + else if (jsonValue.is_object()) + { + std::vector vecWhitelist; + + for (auto& [strConvar, jsonInnerValue] : jsonValue.items()) + { + if (!jsonInnerValue.is_boolean()) + { + Panic("Found invalid value in %s, ConVar whitelist not loaded!\n", pszCvarWhitelistPath); + return false; + } + + vecWhitelist.push_back({StringToLower(strConvar), jsonInnerValue.get()}); + } + + m_mapOverrides[strKey] = vecWhitelist; + } + else + { + Panic("Found invalid value in %s, ConVar whitelist not loaded!\n", pszCvarWhitelistPath); + return false; + } + } + + m_bConfigLoaded = true; + return true; +} + +bool CConVarWhitelist::IsWhitelisted(std::string strName) +{ + auto globalIt = std::find_if(m_vecGlobalWhitelist.begin(), m_vecGlobalWhitelist.end(), [strName](const auto& wv) { + return wv.strConVar == StringToLower(strName); + }); + + // Cannot load map overrides, only check global whitelist + if (!GetGlobals()) + return globalIt != m_vecGlobalWhitelist.end() && globalIt->bEnabled; + + auto mapVector = m_mapOverrides[GetGlobals()->mapname.ToCStr()]; + auto mapIt = std::find_if(mapVector.begin(), mapVector.end(), [strName](const auto& wv) { + return wv.strConVar == StringToLower(strName); + }); + + // If a map override is present, it takes priority + if (mapIt != mapVector.end()) + return mapIt->bEnabled; + + // Or fall back to global whitelist + return globalIt != m_vecGlobalWhitelist.end() && globalIt->bEnabled; +} \ No newline at end of file diff --git a/src/cvarwhitelist.h b/src/cvarwhitelist.h new file mode 100644 index 00000000..ecb77a70 --- /dev/null +++ b/src/cvarwhitelist.h @@ -0,0 +1,54 @@ +/** + * ============================================================================= + * CS2Fixes + * Copyright (C) 2023-2026 Source2ZE + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#pragma once + +#include "convar.h" +#undef snprintf +#include "vendor/nlohmann/json_fwd.hpp" + +using json = nlohmann::json; + +extern CConVar g_cvarConVarWhitelistEnable; + +struct WhitelistValue +{ + std::string strConVar; + bool bEnabled; +}; + +class CConVarWhitelist +{ +public: + CConVarWhitelist() + { + LoadConfig(); + } + + bool LoadConfig(); + bool IsWhitelisted(std::string strName); + bool IsConfigLoaded() { return m_bConfigLoaded; }; + +private: + bool m_bConfigLoaded = false; + std::vector m_vecGlobalWhitelist; + std::map> m_mapOverrides; +}; + +extern CConVarWhitelist* g_pConvarWhitelist; \ No newline at end of file diff --git a/src/detours.cpp b/src/detours.cpp index 3d21b9fc..eea5ac10 100644 --- a/src/detours.cpp +++ b/src/detours.cpp @@ -29,6 +29,7 @@ #include "common.h" #include "ctimer.h" #include "customio.h" +#include "cvarwhitelist.h" #include "detours.h" #include "entities.h" #include "entity/cbasemodelentity.h" @@ -92,6 +93,7 @@ DECLARE_DETOUR(CBaseModelEntity_SetModel, Detour_CBaseModelEntity_SetModel); DECLARE_DETOUR(CCSGameRules_GoToIntermission, Detour_CCSGameRules_GoToIntermission); DECLARE_DETOUR(SetBeamOrigin, Detour_SetBeamOrigin); DECLARE_DETOUR(SetBeamEndPos, Detour_SetBeamEndPos); +DECLARE_DETOUR(IsCommandWhitelisted, Detour_IsCommandWhitelisted); CConVar g_cvarBlockMolotovSelfDmg("cs2f_block_molotov_self_dmg", FCVAR_NONE, "Whether to block self-damage from molotovs", false); CConVar g_cvarBlockAllDamage("cs2f_block_all_dmg", FCVAR_NONE, "Whether to block all damage to players", false); @@ -892,6 +894,14 @@ void FASTCALL Detour_SetBeamEndPos(CBeam* pThis, const Vector* pVecPosition) pThis->m_vecEndPos = *(VectorWS*)(pVecPosition); } +bool FASTCALL Detour_IsCommandWhitelisted(void* pAddonManager, const char* pszCommandName) +{ + if (!g_cvarConVarWhitelistEnable.Get() || !g_pConvarWhitelist->IsConfigLoaded()) + return IsCommandWhitelisted(pAddonManager, pszCommandName); + + return g_pConvarWhitelist->IsWhitelisted(pszCommandName); +} + bool InitDetours(CGameConfig* gameConfig) { bool success = true; diff --git a/src/detours.h b/src/detours.h index b48e7b05..19f4cdc7 100644 --- a/src/detours.h +++ b/src/detours.h @@ -118,4 +118,5 @@ void FASTCALL Detour_CS_Script_SetModel(uint64_t unk1); void FASTCALL Detour_CBaseModelEntity_SetModel(CBaseModelEntity* pModel, const char* pszModel); void FASTCALL Detour_CCSGameRules_GoToIntermission(CCSGameRules* pThis, bool bAbortedMatch); void FASTCALL Detour_SetBeamOrigin(CBeam* pThis, const Vector* pVecWorldPosition); -void FASTCALL Detour_SetBeamEndPos(CBeam* pThis, const Vector* pVecWorldPosition); \ No newline at end of file +void FASTCALL Detour_SetBeamEndPos(CBeam* pThis, const Vector* pVecWorldPosition); +bool FASTCALL Detour_IsCommandWhitelisted(void* pAddonManager, const char* pszCommandName); \ No newline at end of file diff --git a/src/map_votes.cpp b/src/map_votes.cpp index 148b159a..6005b524 100644 --- a/src/map_votes.cpp +++ b/src/map_votes.cpp @@ -1136,24 +1136,11 @@ void CMapVoteSystem::ClearInvalidNominations() } } -void CMapVoteSystem::ApplyGameSettings(KeyValues* pKV) +void CMapVoteSystem::ApplyGameSettings(const char* pszMapName, uint64 iWorkshopId) { if (!g_cvarVoteManagerEnable.Get()) return; - const char* pszMapName; - uint64 iWorkshopId; - - if (pKV->FindKey("launchoptions") && pKV->FindKey("launchoptions")->FindKey("levelname")) - pszMapName = pKV->FindKey("launchoptions")->GetString("levelname"); - else - pszMapName = ""; - - if (pKV->FindKey("launchoptions") && pKV->FindKey("launchoptions")->FindKey("customgamemode")) - iWorkshopId = pKV->FindKey("launchoptions")->GetUint64("customgamemode"); - else - iWorkshopId = 0; - auto pair = GetMapInfoByIdentifiers(pszMapName, iWorkshopId); if (pair.first != -1) diff --git a/src/map_votes.h b/src/map_votes.h index 136bfbdf..178f65cc 100644 --- a/src/map_votes.h +++ b/src/map_votes.h @@ -210,7 +210,7 @@ class CMapVoteSystem std::shared_ptr GetForcedNextMap() { return m_pForcedNextMap; } void SetForcedNextMap(std::shared_ptr pForcedNextMap) { m_pForcedNextMap = pForcedNextMap; } std::unordered_map GetNominatedMaps(); - void ApplyGameSettings(KeyValues* pKV); + void ApplyGameSettings(const char* pszMapName, uint64 iWorkshopId); void OnLevelShutdown(); std::vector> GetMapCooldowns() { return m_vecCooldowns; } std::string ConvertFloatToString(float fValue, int precision); diff --git a/src/mapmigrations.cpp b/src/mapmigrations.cpp index 5de64a0e..03db6b16 100644 --- a/src/mapmigrations.cpp +++ b/src/mapmigrations.cpp @@ -33,15 +33,13 @@ const time_t g_time20260420 = 1776725888; CConVar g_cvarMapMigrations20260121("cs2f_mapmigrations_20260121", FCVAR_NONE, "Current mode for 2026-01-21 CS2 update map migrations. [0 = Force disabled, 1 = Force enabled, 2 = Automatically enabled for maps updated before 2026-01-21 & disabled if updated after]", 2); CConVar g_cvarMapMigrations20260420("cs2f_mapmigrations_20260420", FCVAR_NONE, "Current mode for 2026-04-20 CS2 update map migrations. [0 = Force disabled, 1 = Force enabled, 2 = Automatically enabled for maps updated before 2026-04-20 & disabled if updated after]", 2); -void CMapMigrations::ApplyGameSettings(KeyValues* pKV) +void CMapMigrations::ApplyGameSettings(uint64 iWorkshopId) { m_timeMapUpdated = std::numeric_limits::max(); // Don't run on default maps - if (!pKV->FindKey("launchoptions") || !pKV->FindKey("launchoptions")->FindKey("customgamemode")) - return; - - CMapMigrationWorkshopDetailsQuery::Create(pKV->FindKey("launchoptions")->GetUint64("customgamemode")); + if (iWorkshopId != 0) + CMapMigrationWorkshopDetailsQuery::Create(iWorkshopId); } void CMapMigrations::OnRoundPrestart() diff --git a/src/mapmigrations.h b/src/mapmigrations.h index c19fde12..6d4cca13 100644 --- a/src/mapmigrations.h +++ b/src/mapmigrations.h @@ -52,7 +52,7 @@ class CMapMigrationWorkshopDetailsQuery : public std::enable_shared_from_this Date: Tue, 28 Jul 2026 17:53:22 -0400 Subject: [PATCH 08/25] Fix nom player limits only being checked once & add player count cache --- src/map_votes.cpp | 4 ++-- src/playermanager.cpp | 13 +++++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/map_votes.cpp b/src/map_votes.cpp index 6005b524..4847bd6d 100644 --- a/src/map_votes.cpp +++ b/src/map_votes.cpp @@ -1123,8 +1123,8 @@ void CMapVoteSystem::ClearInvalidNominations() auto pMap = GetMapByIndex(iNominatedMapIndex); - // Check if nominated index still meets criteria for nomination - if (!pMap->IsEnabled()) + // Check if nominated map still meets criteria for nomination + if (!pMap->IsAvailable()) { ClearPlayerInfo(i); CCSPlayerController* pPlayer = CCSPlayerController::FromSlot(i); diff --git a/src/playermanager.cpp b/src/playermanager.cpp index 14212c5f..bbc3c642 100644 --- a/src/playermanager.cpp +++ b/src/playermanager.cpp @@ -1850,11 +1850,20 @@ void CPlayerManager::ResetPlayerFlags(int slot) int CPlayerManager::GetOnlinePlayerCount(bool bCountBots) { - int iOnlinePlayers = 0; + // Minimal caching, sometimes we call this function a lot of times + static int iOnlinePlayers = 0; + static bool bBotsCached = bCountBots; + static float flLastUpdateTime = -1.0f; - if (!GetClientList()) + if ((GetGlobals() && GetGlobals()->curtime == flLastUpdateTime && bBotsCached == bCountBots) || !GetClientList()) return iOnlinePlayers; + iOnlinePlayers = 0; + bBotsCached = bCountBots; + + if (GetGlobals()) + flLastUpdateTime = GetGlobals()->curtime; + for (int i = 0; i < GetClientList()->Count(); i++) { CServerSideClient* pClient = (*GetClientList())[i]; From 6f69439d491d6fd571a549c04265e91a3e7aac11 Mon Sep 17 00:00:00 2001 From: Vauff Date: Mon, 3 Aug 2026 18:46:46 -0400 Subject: [PATCH 09/25] Propagate all HTTP API error states Even though all the GenerateRequest error states are deterministic, they can still help avoid silent failures during dev --- src/httpmanager.cpp | 42 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/src/httpmanager.cpp b/src/httpmanager.cpp index 29fc3143..c642879a 100644 --- a/src/httpmanager.cpp +++ b/src/httpmanager.cpp @@ -64,7 +64,14 @@ HTTPManager::TrackedRequest::~TrackedRequest() void HTTPManager::TrackedRequest::OnHTTPRequestCompleted(HTTPRequestCompleted_t* arg, bool bFailed) { - if (bFailed || (!m_callbackError && (arg->m_eStatusCode < 200 || arg->m_eStatusCode > 299))) + bool bTransportError = bFailed || !arg->m_bRequestSuccessful; + bool bHTTPError = arg->m_eStatusCode < 200 || arg->m_eStatusCode > 299; + + if (bTransportError && m_callbackError) + { + m_callbackError(arg->m_hRequest, k_EHTTPStatusCodeInvalid, json()); + } + else if (bTransportError || (bHTTPError && !m_callbackError)) { Message("HTTP request to %s failed with status code %i\n", m_strUrl.c_str(), arg->m_eStatusCode); } @@ -88,9 +95,9 @@ void HTTPManager::TrackedRequest::OnHTTPRequestCompleted(HTTPRequestCompleted_t* Message("Failed parsing JSON from HTTP response: %s\n", (char*)response); } - if (!jsonResponse.is_discarded() && (arg->m_eStatusCode < 200 || arg->m_eStatusCode > 299)) + if (!jsonResponse.is_discarded() && bHTTPError) m_callbackError(arg->m_hRequest, arg->m_eStatusCode, jsonResponse); - else if (arg->m_eStatusCode < 200 || arg->m_eStatusCode > 299) + else if (bHTTPError) { // Allow error callback even if invalid json, since error code can provide useful info m_callbackError(arg->m_hRequest, arg->m_eStatusCode, json()); @@ -143,7 +150,7 @@ void HTTPManager::GenerateRequest(EHTTPMethod method, std::string strUrl, std::s { if (!GetSteamHTTP()) { - Panic("A web request was attempted on null ISteamHTTP, returning early.\n"); + Panic("A web request for %s was attempted on null ISteamHTTP, returning early.\n", strUrl.c_str()); return; } @@ -152,6 +159,11 @@ void HTTPManager::GenerateRequest(EHTTPMethod method, std::string strUrl, std::s #endif HTTPRequestHandle hReq = GetSteamHTTP()->CreateHTTPRequest(method, strUrl.c_str()); + if (hReq == INVALID_HTTPREQUEST_HANDLE) + { + Panic("Failed to CreateHTTPRequest for %s\n", strUrl.c_str()); + return; + } bool shouldHaveBody = method == k_EHTTPMethodPOST || method == k_EHTTPMethodPATCH @@ -159,14 +171,32 @@ void HTTPManager::GenerateRequest(EHTTPMethod method, std::string strUrl, std::s || method == k_EHTTPMethodDELETE; if (shouldHaveBody && !GetSteamHTTP()->SetHTTPRequestRawPostBody(hReq, "application/json", (uint8*)(strText.c_str()), strText.length())) + { + Panic("Failed to SetHTTPRequestRawPostBody for %s\n", strUrl.c_str()); + GetSteamHTTP()->ReleaseHTTPRequest(hReq); return; + } if (headers != nullptr) + { for (HTTPHeader header : *headers) - GetSteamHTTP()->SetHTTPRequestHeaderValue(hReq, header.GetName(), header.GetValue()); + { + if (!GetSteamHTTP()->SetHTTPRequestHeaderValue(hReq, header.GetName(), header.GetValue())) + { + Panic("Failed to SetHTTPRequestHeaderValue for %s\n", strUrl.c_str()); + GetSteamHTTP()->ReleaseHTTPRequest(hReq); + return; + } + } + } SteamAPICall_t hCall; - GetSteamHTTP()->SendHTTPRequest(hReq, &hCall); + if (!GetSteamHTTP()->SendHTTPRequest(hReq, &hCall)) + { + Panic("Failed to SendHTTPRequest for %s\n", strUrl.c_str()); + GetSteamHTTP()->ReleaseHTTPRequest(hReq); + return; + } new TrackedRequest(hReq, hCall, strUrl, strText, callbackCompleted, callbackError); } From 0c2301555d7adda7936029e31b545aa07e5d7e93 Mon Sep 17 00:00:00 2001 From: Vauff Date: Mon, 3 Aug 2026 18:47:22 -0400 Subject: [PATCH 10/25] Add optional timeout config to HTTP API --- src/httpmanager.cpp | 32 +++++++++++++++++++++----------- src/httpmanager.h | 17 +++++++++++------ 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/src/httpmanager.cpp b/src/httpmanager.cpp index c642879a..15246aec 100644 --- a/src/httpmanager.cpp +++ b/src/httpmanager.cpp @@ -115,38 +115,41 @@ void HTTPManager::TrackedRequest::OnHTTPRequestCompleted(HTTPRequestCompleted_t* } void HTTPManager::Get(std::string strUrl, CompletedCallback callbackCompleted, - ErrorCallback callbackError, std::vector* headers) + ErrorCallback callbackError, std::vector* headers, + int absoluteTimeoutMs) { - GenerateRequest(k_EHTTPMethodGET, strUrl, "", callbackCompleted, callbackError, headers); + GenerateRequest(k_EHTTPMethodGET, strUrl, "", callbackCompleted, callbackError, headers, absoluteTimeoutMs); } void HTTPManager::Post(std::string strUrl, std::string strText, CompletedCallback callbackCompleted, - ErrorCallback callbackError, std::vector* headers) + ErrorCallback callbackError, std::vector* headers, + int absoluteTimeoutMs) { - GenerateRequest(k_EHTTPMethodPOST, strUrl, strText, callbackCompleted, callbackError, headers); + GenerateRequest(k_EHTTPMethodPOST, strUrl, strText, callbackCompleted, callbackError, headers, absoluteTimeoutMs); } void HTTPManager::Put(std::string strUrl, std::string strText, CompletedCallback callbackCompleted, - ErrorCallback callbackError, std::vector* headers) + ErrorCallback callbackError, std::vector* headers, + int absoluteTimeoutMs) { - GenerateRequest(k_EHTTPMethodPUT, strUrl, strText, callbackCompleted, callbackError, headers); + GenerateRequest(k_EHTTPMethodPUT, strUrl, strText, callbackCompleted, callbackError, headers, absoluteTimeoutMs); } void HTTPManager::Patch(std::string strUrl, std::string strText, CompletedCallback callbackCompleted, - ErrorCallback callbackError, std::vector* headers) + ErrorCallback callbackError, std::vector* headers, int absoluteTimeoutMs) { - GenerateRequest(k_EHTTPMethodPATCH, strUrl, strText, callbackCompleted, callbackError, headers); + GenerateRequest(k_EHTTPMethodPATCH, strUrl, strText, callbackCompleted, callbackError, headers, absoluteTimeoutMs); } void HTTPManager::Delete(std::string strUrl, std::string strText, CompletedCallback callbackCompleted, - ErrorCallback callbackError, std::vector* headers) + ErrorCallback callbackError, std::vector* headers, int absoluteTimeoutMs) { - GenerateRequest(k_EHTTPMethodDELETE, strUrl, strText, callbackCompleted, callbackError, headers); + GenerateRequest(k_EHTTPMethodDELETE, strUrl, strText, callbackCompleted, callbackError, headers, absoluteTimeoutMs); } void HTTPManager::GenerateRequest(EHTTPMethod method, std::string strUrl, std::string strText, CompletedCallback callbackCompleted, ErrorCallback callbackError, - std::vector* headers) + std::vector* headers, int absoluteTimeoutMs) { if (!GetSteamHTTP()) { @@ -190,6 +193,13 @@ void HTTPManager::GenerateRequest(EHTTPMethod method, std::string strUrl, std::s } } + if (absoluteTimeoutMs != 0 && !GetSteamHTTP()->SetHTTPRequestAbsoluteTimeoutMS(hReq, static_cast(absoluteTimeoutMs))) + { + Panic("Failed to SetHTTPRequestAbsoluteTimeoutMS for %s\n", strUrl.c_str()); + GetSteamHTTP()->ReleaseHTTPRequest(hReq); + return; + } + SteamAPICall_t hCall; if (!GetSteamHTTP()->SendHTTPRequest(hReq, &hCall)) { diff --git a/src/httpmanager.h b/src/httpmanager.h index 7a8024b4..1c5046e0 100644 --- a/src/httpmanager.h +++ b/src/httpmanager.h @@ -60,15 +60,20 @@ class HTTPManager { public: void Get(std::string strUrl, CompletedCallback callbackCompleted, - ErrorCallback callbackError = nullptr, std::vector* headers = nullptr); + ErrorCallback callbackError = nullptr, std::vector* headers = nullptr, + int absoluteTimeoutMs = 0); void Post(std::string strUrl, std::string strText, CompletedCallback callbackCompleted, - ErrorCallback callbackError = nullptr, std::vector* headers = nullptr); + ErrorCallback callbackError = nullptr, std::vector* headers = nullptr, + int absoluteTimeoutMs = 0); void Put(std::string strUrl, std::string strText, CompletedCallback callbackCompleted, - ErrorCallback callbackError = nullptr, std::vector* headers = nullptr); + ErrorCallback callbackError = nullptr, std::vector* headers = nullptr, + int absoluteTimeoutMs = 0); void Patch(std::string strUrl, std::string strText, CompletedCallback callbackCompleted, - ErrorCallback callbackError = nullptr, std::vector* headers = nullptr); + ErrorCallback callbackError = nullptr, std::vector* headers = nullptr, + int absoluteTimeoutMs = 0); void Delete(std::string strUrl, std::string strText, CompletedCallback callbackCompleted, - ErrorCallback callbackError = nullptr, std::vector* headers = nullptr); + ErrorCallback callbackError = nullptr, std::vector* headers = nullptr, + int absoluteTimeoutMs = 0); bool HasAnyPendingRequests() const { return m_PendingRequests.size() > 0; } private: @@ -96,7 +101,7 @@ class HTTPManager std::vector m_PendingRequests; void GenerateRequest(EHTTPMethod method, std::string strUrl, std::string strText, CompletedCallback callbackCompleted, ErrorCallback callbackError, - std::vector* headers); + std::vector* headers, int absoluteTimeoutMs = 0); }; extern HTTPManager g_HTTPManager; From 963afd905d36049ac1a7e8b5eb741ac8c5a78cee Mon Sep 17 00:00:00 2001 From: Vauff Date: Mon, 3 Aug 2026 19:24:15 -0400 Subject: [PATCH 11/25] Add CCheckTransmitInfoExtended --- CS2Fixes.vcxproj | 1 + CS2Fixes.vcxproj.filters | 3 +++ gamedata/cs2fixes.jsonc | 5 ----- src/cs2_sdk/cchecktransmitinfo.h | 30 ++++++++++++++++++++++++++++++ src/cs2fixes.cpp | 15 +++++---------- 5 files changed, 39 insertions(+), 15 deletions(-) create mode 100644 src/cs2_sdk/cchecktransmitinfo.h diff --git a/CS2Fixes.vcxproj b/CS2Fixes.vcxproj index 9b0b1648..53a10c64 100644 --- a/CS2Fixes.vcxproj +++ b/CS2Fixes.vcxproj @@ -265,6 +265,7 @@ + diff --git a/CS2Fixes.vcxproj.filters b/CS2Fixes.vcxproj.filters index ecb355ea..f5c960ec 100644 --- a/CS2Fixes.vcxproj.filters +++ b/CS2Fixes.vcxproj.filters @@ -397,6 +397,9 @@ Header Files\cs2_sdk + + Header Files\cs2_sdk + Header Files diff --git a/gamedata/cs2fixes.jsonc b/gamedata/cs2fixes.jsonc index 982ff121..9947690e 100644 --- a/gamedata/cs2fixes.jsonc +++ b/gamedata/cs2fixes.jsonc @@ -500,11 +500,6 @@ "windows": 26, "linux": 27 }, - "CheckTransmitPlayerSlot": - { - "windows": 576, - "linux": 576 - }, // engine // "tried to sprint to a non-client", there will be a check above like this: if ( a2 >= *(v5 + 632) ), note that this is a CUtlVector "CNetworkGameServer_ClientList": diff --git a/src/cs2_sdk/cchecktransmitinfo.h b/src/cs2_sdk/cchecktransmitinfo.h new file mode 100644 index 00000000..c1d1fb86 --- /dev/null +++ b/src/cs2_sdk/cchecktransmitinfo.h @@ -0,0 +1,30 @@ +#pragma once + +#include "bitvec.h" +#include "eiface.h" +#include "playerslot.h" +#include "utlvector.h" + +// https://github.com/Wend4r/sourcesdk/blob/main/public/iservernetworkable.h + +struct vis_info_t_extended +{ + uint32 m_uVisBitsBufSize; + SpawnGroupHandle_t m_SpawnGroupHandle; + CBitVec<4096> m_VisBits; +}; +COMPILE_TIME_ASSERT(sizeof(vis_info_t_extended) == 520); + +class CCheckTransmitInfoExtended +{ +public: + CBitVec* m_pTransmitEntity; // entities visible/sent to client + CBitVec* m_pTransmitNonPlayers; // non-player entities needing deletion deltas + CBitVec* m_pTransmitOutOfPVS; // entities that left PVS but still need delta update + CBitVec* m_pTransmitAlways; // entity n is always sent even if not in PVS (HLTV and Replay only) + CUtlVector m_vecTargetSlots; + vis_info_t_extended m_VisInfo; + CPlayerSlot m_nPlayerSlot; + bool m_bFullUpdate = false; +}; +COMPILE_TIME_ASSERT(sizeof(CCheckTransmitInfoExtended) == 584); \ No newline at end of file diff --git a/src/cs2fixes.cpp b/src/cs2fixes.cpp index 6017f9eb..86c3abec 100644 --- a/src/cs2fixes.cpp +++ b/src/cs2fixes.cpp @@ -22,6 +22,7 @@ #include "adminsystem.h" #include "appframework/IAppSystem.h" +#include "cchecktransmitinfo.h" #include "cfgparser.h" #include "commands.h" #include "common.h" @@ -962,19 +963,13 @@ void CS2Fixes::Hook_CheckTransmit(CCheckTransmitInfo** ppInfoList, int infoCount for (int i = 0; i < infoCount; i++) { - auto& pInfo = ppInfoList[i]; - - // the offset happens to have a player index here, - // though this is probably part of the client class that contains the CCheckTransmitInfo - static int offset = g_GameConfig->GetOffset("CheckTransmitPlayerSlot"); - int iPlayerSlot = (int)*((uint8*)pInfo + offset); - - CCSPlayerController* pSelfController = CCSPlayerController::FromSlot(iPlayerSlot); + auto& pInfo = (CCheckTransmitInfoExtended*&)(ppInfoList[i]); + CCSPlayerController* pSelfController = CCSPlayerController::FromSlot(pInfo->m_nPlayerSlot); if (!pSelfController || !pSelfController->IsConnected()) continue; - auto pSelfZEPlayer = g_playerManager->GetPlayer(iPlayerSlot); + auto pSelfZEPlayer = g_playerManager->GetPlayer(pInfo->m_nPlayerSlot); if (!pSelfZEPlayer) continue; @@ -983,7 +978,7 @@ void CS2Fixes::Hook_CheckTransmit(CCheckTransmitInfo** ppInfoList, int infoCount { CCSPlayerController* pController = CCSPlayerController::FromSlot(j); // Always transmit to themselves - if (!pController || pController->m_bIsHLTV || j == iPlayerSlot) + if (!pController || pController->m_bIsHLTV || j == pInfo->m_nPlayerSlot.Get()) continue; // Don't transmit other players' flashlights From df6bc9e47fb17e58b22798a9a6518951eda1b5f1 Mon Sep 17 00:00:00 2001 From: Vauff Date: Sun, 9 Aug 2026 15:16:06 -0400 Subject: [PATCH 12/25] Fix hide crash (missing client entity) --- src/cs2fixes.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/cs2fixes.cpp b/src/cs2fixes.cpp index 86c3abec..68663c07 100644 --- a/src/cs2fixes.cpp +++ b/src/cs2fixes.cpp @@ -1010,6 +1010,7 @@ void CS2Fixes::Hook_CheckTransmit(CCheckTransmitInfo** ppInfoList, int infoCount if (pSelfZEPlayer->ShouldBlockTransmit(j) && pOtherZEPlayer && !pOtherZEPlayer->IsLeader() && g_pEWHandler->FindItemInstanceByOwner(j, false, 0) == -1) { pInfo->m_pTransmitEntity->Clear(pPawn->entindex()); + pInfo->m_pTransmitNonPlayers->Set(pPawn->entindex()); if (g_cvarHideWeapons.Get()) { @@ -1020,7 +1021,10 @@ void CS2Fixes::Hook_CheckTransmit(CCheckTransmitInfo** ppInfoList, int infoCount auto pWeapon = (*pVecWeapons)[i].Get(); if (pWeapon) + { pInfo->m_pTransmitEntity->Clear(pWeapon->entindex()); + pInfo->m_pTransmitNonPlayers->Set(pWeapon->entindex()); + } } } } From 7f36a2cc915072e23bd5a921272d4f7d091bf766 Mon Sep 17 00:00:00 2001 From: Vauff Date: Mon, 10 Aug 2026 23:42:31 -0400 Subject: [PATCH 13/25] Remove explicit weapon hiding This feature is now redundant, since the hide crash fix also fixed entities parented to hidden entities desyncing --- cfg/cs2fixes/cs2fixes.cfg | 1 - src/commands.cpp | 1 - src/commands.h | 1 - src/cs2fixes.cpp | 16 ---------------- 4 files changed, 19 deletions(-) diff --git a/cfg/cs2fixes/cs2fixes.cfg b/cfg/cs2fixes/cs2fixes.cfg index 3efc1a4a..3a7b746b 100644 --- a/cfg/cs2fixes/cs2fixes.cfg +++ b/cfg/cs2fixes/cs2fixes.cfg @@ -60,7 +60,6 @@ cs2f_burn_slowdown 0.6 // The slowdown of each burn damage tick as a multipl cs2f_burn_interval 0.3 // The interval between burn damage ticks // Hide settings -cs2f_hide_weapons 0 // Whether to hide weapons along with their holders cs2f_hide_distance_default 250 // The default distance for hide cs2f_hide_distance_max 2000 // The max distance for hide cs2f_hide_teammates_only 0 // Whether to hide teammates only diff --git a/src/commands.cpp b/src/commands.cpp index 2d8799b1..39ee90cf 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -431,7 +431,6 @@ CON_COMMAND_CHAT(noshake, "- toggle noshake") } CConVar g_cvarEnableHide("cs2f_hide_enable", FCVAR_NONE, "Whether to enable hide (WARNING: randomly crashes clients since 2023-12-13 CS2 update)", false); -CConVar g_cvarHideWeapons("cs2f_hide_weapons", FCVAR_NONE, "Whether to hide weapons along with their holders", false); CConVar g_cvarDefaultHideDistance("cs2f_hide_distance_default", FCVAR_NONE, "The default distance for hide", 250, true, 0, false, 0); CConVar g_cvarMaxHideDistance("cs2f_hide_distance_max", FCVAR_NONE, "The max distance for hide", 2000, true, 0, false, 0); diff --git a/src/commands.h b/src/commands.h index a4aa4ac4..d8ce7975 100644 --- a/src/commands.h +++ b/src/commands.h @@ -33,7 +33,6 @@ extern CConVar g_cvarEnableStopSound; extern CConVar g_cvarEnableNoShake; extern CConVar g_cvarMaxShakeAmp; extern CConVar g_cvarEnableHide; -extern CConVar g_cvarHideWeapons; #define CMDFLAG_NONE (0) #define CMDFLAG_NOHELP (1 << 0) // Don't show in !help menu diff --git a/src/cs2fixes.cpp b/src/cs2fixes.cpp index 68663c07..f0a789df 100644 --- a/src/cs2fixes.cpp +++ b/src/cs2fixes.cpp @@ -1011,22 +1011,6 @@ void CS2Fixes::Hook_CheckTransmit(CCheckTransmitInfo** ppInfoList, int infoCount { pInfo->m_pTransmitEntity->Clear(pPawn->entindex()); pInfo->m_pTransmitNonPlayers->Set(pPawn->entindex()); - - if (g_cvarHideWeapons.Get()) - { - auto pVecWeapons = pPawn->m_pWeaponServices->m_hMyWeapons(); - - FOR_EACH_VEC(*pVecWeapons, i) - { - auto pWeapon = (*pVecWeapons)[i].Get(); - - if (pWeapon) - { - pInfo->m_pTransmitEntity->Clear(pWeapon->entindex()); - pInfo->m_pTransmitNonPlayers->Set(pWeapon->entindex()); - } - } - } } } From 5046892ef4e53200f56969180e978a5b3d6377c0 Mon Sep 17 00:00:00 2001 From: Vauff Date: Thu, 13 Aug 2026 19:34:43 -0400 Subject: [PATCH 14/25] Fix user prefs not loading when a player authenticates early --- src/cs2_sdk/cchecktransmitinfo.h | 6 +++--- src/playermanager.cpp | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/cs2_sdk/cchecktransmitinfo.h b/src/cs2_sdk/cchecktransmitinfo.h index c1d1fb86..1a3f472d 100644 --- a/src/cs2_sdk/cchecktransmitinfo.h +++ b/src/cs2_sdk/cchecktransmitinfo.h @@ -18,10 +18,10 @@ COMPILE_TIME_ASSERT(sizeof(vis_info_t_extended) == 520); class CCheckTransmitInfoExtended { public: - CBitVec* m_pTransmitEntity; // entities visible/sent to client + CBitVec* m_pTransmitEntity; // entities visible/sent to client CBitVec* m_pTransmitNonPlayers; // non-player entities needing deletion deltas - CBitVec* m_pTransmitOutOfPVS; // entities that left PVS but still need delta update - CBitVec* m_pTransmitAlways; // entity n is always sent even if not in PVS (HLTV and Replay only) + CBitVec* m_pTransmitOutOfPVS; // entities that left PVS but still need delta update + CBitVec* m_pTransmitAlways; // entity n is always sent even if not in PVS (HLTV and Replay only) CUtlVector m_vecTargetSlots; vis_info_t_extended m_VisInfo; CPlayerSlot m_nPlayerSlot; diff --git a/src/playermanager.cpp b/src/playermanager.cpp index bbc3c642..ee61cdb2 100644 --- a/src/playermanager.cpp +++ b/src/playermanager.cpp @@ -794,13 +794,13 @@ bool CPlayerManager::OnClientConnected(CPlayerSlot slot, uint64 xuid, const char return false; } + pPlayer->SetConnected(); + m_vecPlayers[slot.Get()] = pPlayer; + // Sometimes clients can be already auth'd at this point if (g_pEngineServer2->IsClientFullyAuthenticated(slot)) pPlayer->OnAuthenticated(); - pPlayer->SetConnected(); - m_vecPlayers[slot.Get()] = pPlayer; - ResetPlayerFlags(slot.Get()); g_pMapVoteSystem->ClearPlayerInfo(slot.Get()); From e1e3c2f560606a35cf17f8c7b4ca4dc3a21e89a7 Mon Sep 17 00:00:00 2001 From: komashchenko Date: Sun, 16 Aug 2026 07:23:35 +0300 Subject: [PATCH 15/25] Replace some gamedata lookups with VScript function bindings (#459) * Replace some gamedata lookups with VScript function bindings * Fix function pointer conversion * Resolve Teleport and VScript bindings during plugin initialization * Remove redundant checks * Revert Teleport + formatter run --------- Co-authored-by: Vauff --- AMBuilder | 1 + CS2Fixes.vcxproj | 2 + CS2Fixes.vcxproj.filters | 6 ++ gamedata/cs2fixes.jsonc | 42 ---------- src/addresses.cpp | 37 +++++++- src/addresses.h | 14 +++- src/cs2_sdk/entity/cbaseentity.h | 10 +-- src/cs2_sdk/entity/ccsplayercontroller.h | 3 +- src/cs2fixes.cpp | 3 + src/utils/virtual.h | 1 + src/utils/vscript_function.cpp | 47 +++++++++++ src/utils/vscript_function.h | 102 +++++++++++++++++++++++ 12 files changed, 211 insertions(+), 57 deletions(-) create mode 100644 src/utils/vscript_function.cpp create mode 100644 src/utils/vscript_function.h diff --git a/AMBuilder b/AMBuilder index b79020ae..7fb0fa26 100644 --- a/AMBuilder +++ b/AMBuilder @@ -50,6 +50,7 @@ for sdk_target in MMSPlugin.sdk_targets: 'src/utils/weapon.cpp', 'src/utils/hud_manager.cpp', 'src/utils/utils.cpp', + 'src/utils/vscript_function.cpp', 'src/cs2_sdk/entity/services.cpp', 'src/cs2_sdk/schema.cpp', 'src/ctimer.cpp', diff --git a/CS2Fixes.vcxproj b/CS2Fixes.vcxproj index 53a10c64..4249b95e 100644 --- a/CS2Fixes.vcxproj +++ b/CS2Fixes.vcxproj @@ -223,6 +223,7 @@ + @@ -300,6 +301,7 @@ + diff --git a/CS2Fixes.vcxproj.filters b/CS2Fixes.vcxproj.filters index f5c960ec..12b82371 100644 --- a/CS2Fixes.vcxproj.filters +++ b/CS2Fixes.vcxproj.filters @@ -194,6 +194,9 @@ Source Files\utils + + Source Files\utils + Source Files\cs2_sdk\entity @@ -223,6 +226,9 @@ Header Files\utils + + Header Files\utils + Header Files\cs2_sdk diff --git a/gamedata/cs2fixes.jsonc b/gamedata/cs2fixes.jsonc index 9947690e..8cdb75a7 100644 --- a/gamedata/cs2fixes.jsonc +++ b/gamedata/cs2fixes.jsonc @@ -102,13 +102,6 @@ "windows": "40 55 53 56 57 41 54 48 8D 6C 24 ? 48 81 EC ? ? ? ? 4D 8B E0", "linux": "55 66 0F EF C0 48 89 E5 41 57 41 56 41 55 49 89 FD 31 FF" }, - // Should be xref'd right above "flGravity", takes a float arg - "CBaseEntity::SetGravityScale": - { - "library": "server", - "windows": "48 89 5C 24 ? 57 48 83 EC ? F3 0F 10 81 ? ? ? ? 48 8B F9 0F 29 74 24 ? 0F 28 F1 0F 2E C6 7A ? 74 ? BA ? ? ? ? 41 B8 ? ? ? ? 48 81 C1 ? ? ? ? E8 ? ? ? ? 48 8B CF F3 0F 11 B7 ? ? ? ? E8 ? ? ? ? 48 8B 5C 24 ? 0F 28 74 24 ? 48 83 C4 ? 5F C3 CC CC CC CC CC 48 89 5C 24", - "linux": "55 48 89 E5 41 57 41 56 41 55 41 54 53 48 89 FB 48 81 EC ? ? ? ? 0F 2E 87 ? ? ? ? 7A ? 75 ? 48 81 C4 ? ? ? ? 5B 41 5C 41 5D 41 5E 41 5F 5D C3 0F 1F 40 ? 31 C9 BE ? ? ? ? 66 0F EF C9 F3 0F 11 85 ? ? ? ? 48 8D BD ? ? ? ? 48 C7 85 ? ? ? ? ? ? ? ? 0F 29 8D ? ? ? ? 4C 8D A5 ? ? ? ? 48 C7 85 ? ? ? ? ? ? ? ? 48 C7 85 ? ? ? ? ? ? ? ? 48 C7 85 ? ? ? ? ? ? ? ? 48 C7 85 ? ? ? ? ? ? ? ? C7 85 ? ? ? ? ? ? ? ? 66 89 8D ? ? ? ? E8 ? ? ? ? 48 8B 85 ? ? ? ? 48 8D 15 ? ? ? ? 83 85 ? ? ? ? ? F3 0F 10 85 ? ? ? ? C7 00 ? ? ? ? 48 8B 03 48 8B 80 ? ? ? ? 48 39 D0 0F 85 ? ? ? ? 8B 95 ? ? ? ? 4C 8D 7B ? 85 D2 0F 85 ? ? ? ? 80 BB ? ? ? ? ? 75" - }, // "Game System %s is defined twice!\n" // Note that this signature points to the instruction with sm_pFirst which is the first qword referenced in the function. "IGameSystem_InitAllSystems_pFirst": @@ -193,15 +186,6 @@ "windows": "48 89 5C 24 10 57 48 83 EC 30 48 8B DA 48 8B F9 48 85 C9", "linux": "48 85 FF 74 ? 55 48 89 E5 41 55 41 54 49 89 FC" }, - // Look for "SetEntityName", that will be the vscript binding definition - // Scroll a bit down and you'll find something like this (note the offset): *(_QWORD *)(v453 + 64) = sub_1807B0350; - // that function is just a jump to the one we want - "CEntityIdentity_SetEntityName": - { - "library": "server", - "windows": "48 89 5C 24 10 57 48 83 EC 20 48 8B D9 4C 8B C2", - "linux": "55 48 89 F2 48 89 E5 41 55 41 54 53" - }, // "Error - cannot add bots after game is over." "BotNavIgnore": { @@ -209,14 +193,6 @@ "windows": "0F 84 ? ? ? ? 80 B8 ? ? ? ? 00 0F 84 ? ? ? ? 80 3D ? ? ? ? 00 74 15", "linux": "0F 84 ? ? ? ? 44 0F B6 B8 ? ? ? ? 45 84 FF 0F 84" }, - // next to "soundname", in windows it's the last referenced sub while in linux it's right after - // this is a vscript binding though so it may be removed in the future? - "CBaseEntity_EmitSoundParams": - { - "library": "server", - "windows": "48 89 5C 24 ? 48 89 74 24 ? 48 89 7C 24 ? 55 48 8B EC 48 81 EC ? ? ? ? 33 C0", - "linux": "48 B8 ? ? ? ? ? ? ? ? 55 0F 28 D0" - }, // "ParticleEffect", found in a function with 9 arguments "DispatchParticleEffect": { @@ -416,12 +392,6 @@ "windows": 53, "linux": 52 }, - // String: "%s<%i><%s><%s>" ChangeTeam() CTMDBG..." - "CCSPlayerController_ChangeTeam": - { - "windows": 103, - "linux": 102 - }, "CBaseEntity::Use": { "windows": 145, @@ -447,18 +417,6 @@ "windows": 163, "linux": 162 }, - // For these two, look for the names, you'll find vscript bindings - // Scroll down to where the var + 64 gets set to a function, that calls the offset we want - "IsPlayerPawn": - { - "windows": 169, - "linux": 168 - }, - "IsPlayerController": - { - "windows": 170, - "linux": 169 - }, "CollisionRulesChanged": { "windows": 186, diff --git a/src/addresses.cpp b/src/addresses.cpp index 6a0fe6d7..5fae6ed6 100644 --- a/src/addresses.cpp +++ b/src/addresses.cpp @@ -21,6 +21,10 @@ #include "gameconfig.h" #include "utils/module.h" +#include "entityinstance.h" +#include "tier1/strtools.h" +#include "vscript/ivscript.h" + #include "tier0/memdbgon.h" #define RESOLVE_SIG(gameConfig, name, variable) \ @@ -29,6 +33,14 @@ return false; \ Message("Found %s at 0x%p\n", name, variable); +#define RESOLVE_SF(scriptDesc, funcName, variable) \ + if (!variable.Initialize(GetVScriptFunction(scriptDesc, funcName))) \ + return false; \ + if (variable.IsVirtual()) \ + Message("Found %s::%s at vtable index %i\n", scriptDesc->m_pszClassname, funcName, variable.GetOffset()); \ + else \ + Message("Found %s::%s at 0x%p\n", scriptDesc->m_pszClassname, funcName, variable.GetPtr()); + bool addresses::Initialize(CGameConfig* g_GameConfig) { modules::engine = new CModule(ROOTBIN, "engine2"); @@ -51,7 +63,6 @@ bool addresses::Initialize(CGameConfig* g_GameConfig) #endif RESOLVE_SIG(g_GameConfig, "SetGroundEntity", addresses::SetGroundEntity); - RESOLVE_SIG(g_GameConfig, "CBaseEntity::SetGravityScale", addresses::SetGravityScale); RESOLVE_SIG(g_GameConfig, "CCSPlayerController_SwitchTeam", addresses::CCSPlayerController_SwitchTeam); RESOLVE_SIG(g_GameConfig, "CBasePlayerController_SetPawn", addresses::CBasePlayerController_SetPawn); RESOLVE_SIG(g_GameConfig, "CBaseModelEntity_SetModel", addresses::CBaseModelEntity_SetModel); @@ -62,8 +73,6 @@ bool addresses::Initialize(CGameConfig* g_GameConfig) RESOLVE_SIG(g_GameConfig, "CGameRules_TerminateRound", addresses::CGameRules_TerminateRound); RESOLVE_SIG(g_GameConfig, "CreateEntityByName", addresses::CreateEntityByName); RESOLVE_SIG(g_GameConfig, "DispatchSpawn", addresses::DispatchSpawn); - RESOLVE_SIG(g_GameConfig, "CEntityIdentity_SetEntityName", addresses::CEntityIdentity_SetEntityName); - RESOLVE_SIG(g_GameConfig, "CBaseEntity_EmitSoundParams", addresses::CBaseEntity_EmitSoundParams); RESOLVE_SIG(g_GameConfig, "DispatchParticleEffect", addresses::DispatchParticleEffect); RESOLVE_SIG(g_GameConfig, "CBaseEntity_EmitSoundFilter", addresses::CBaseEntity_EmitSoundFilter); RESOLVE_SIG(g_GameConfig, "CBaseEntity_SetMoveType", addresses::CBaseEntity_SetMoveType); @@ -101,3 +110,25 @@ bool addresses::InitializeBanMap(CGameConfig* g_GameConfig) Message("Found %s at 0x%p\n", "CCSGameRules__sm_mapGcBanInformation", addresses::sm_mapGcBanInformation); return true; } + +bool addresses::InitializeVScriptFunctions() +{ + void* pCBaseEntityVTable = modules::server->FindVirtualTable("CBaseEntity"); + if (!pCBaseEntityVTable) + { + Message("Failed to find CBaseEntity vtable\n"); + return false; + } + + // GetScriptDesc ignores this, so the vtable pointer is sufficient here. + ScriptClassDesc_t* pCBaseEntityScriptDesc = reinterpret_cast(reinterpret_cast(&pCBaseEntityVTable)->GetScriptDesc()); + + RESOLVE_SF(pCBaseEntityScriptDesc, "SetGravity", SetGravityScale); + RESOLVE_SF(pCBaseEntityScriptDesc, "SetEntityName", ScriptSetEntityName); + RESOLVE_SF(pCBaseEntityScriptDesc, "EmitSoundParams", ScriptEmitSoundParams); + RESOLVE_SF(pCBaseEntityScriptDesc, "SetTeam", ChangeTeam); + RESOLVE_SF(pCBaseEntityScriptDesc, "IsPlayerPawn", IsPlayerPawn); + RESOLVE_SF(pCBaseEntityScriptDesc, "IsPlayerController", IsPlayerController); + + return true; +} diff --git a/src/addresses.h b/src/addresses.h index 08babbe0..d70fb3b6 100644 --- a/src/addresses.h +++ b/src/addresses.h @@ -21,6 +21,7 @@ #include "platform.h" #include "stdint.h" #include "utils/module.h" +#include "utils/vscript_function.h" #include "utlstring.h" #include "variant.h" @@ -74,11 +75,11 @@ namespace addresses { bool Initialize(CGameConfig* g_GameConfig); bool InitializeBanMap(CGameConfig* g_GameConfig); + bool InitializeVScriptFunctions(); inline CUtlOrderedMap* sm_mapGcBanInformation; inline void(FASTCALL* SetGroundEntity)(CBaseEntity* ent, CBaseEntity* ground, CBaseEntity* unk3); - inline void(FASTCALL* SetGravityScale)(CBaseEntity*, float); inline void(FASTCALL* CCSPlayerController_SwitchTeam)(CCSPlayerController* pController, uint32 team); inline void(FASTCALL* CBasePlayerController_SetPawn)(CBasePlayerController* pController, CCSPlayerPawn* pPawn, bool a3, bool a4, bool a5, bool a6); inline void(FASTCALL* CBaseModelEntity_SetModel)(CBaseModelEntity* pModel, const char* pszModel); @@ -97,8 +98,6 @@ namespace addresses inline void(FASTCALL* CGameRules_TerminateRound)(CGameRules* pGameRules, float delay, unsigned int reason, int64 a4); inline CBaseEntity*(FASTCALL* CreateEntityByName)(const char* className, int iForceEdictIndex); inline void(FASTCALL* DispatchSpawn)(CBaseEntity* pEntity, CEntityKeyValues* pEntityKeyValues); - inline void(FASTCALL* CEntityIdentity_SetEntityName)(CEntityIdentity* pEntity, const char* pName); - inline void(FASTCALL* CBaseEntity_EmitSoundParams)(CBaseEntity* pEntity, const char* pszSound, int nPitch, float flVolume, float flDelay); inline int(FASTCALL* DispatchParticleEffect)(const char* pszParticleName, int iAttachType, CBaseEntity* pEntity, char iAttachmentPoint, CUtlSymbolLarge iAttachmentName, bool bResetAllParticlesOnEntity, int nSplitScreenPlayerSlot, IRecipientFilter* a7, byte* a8); inline StartSoundEventInfo(FASTCALL* CBaseEntity_EmitSoundFilter)(IRecipientFilter& filter, CEntityIndex ent, const EmitSound_t& params); @@ -108,4 +107,11 @@ namespace addresses inline void(FASTCALL* CCSPlayer_WeaponServices_EquipWeapon)(CCSPlayer_WeaponServices* pWeaponServices, CBasePlayerWeapon* pPlayerWeapon); inline void(FASTCALL* GetSpawnGroups)(CSpawnGroupMgrGameSystem* pSpawnGroupMgr, CUtlVector* pList); inline void(FASTCALL* CBasePlayerPawn_SnapViewAngles)(CBasePlayerPawn* pPawn, QAngle* pAngles); -} // namespace addresses \ No newline at end of file + + inline CVScriptFunction SetGravityScale; + inline CVScriptFunction ScriptSetEntityName; + inline CVScriptFunction ScriptEmitSoundParams; + inline CVScriptFunction ChangeTeam; + inline CVScriptFunction IsPlayerPawn; + inline CVScriptFunction IsPlayerController; +} // namespace addresses diff --git a/src/cs2_sdk/entity/cbaseentity.h b/src/cs2_sdk/entity/cbaseentity.h index bcf1955f..6d3b3a93 100644 --- a/src/cs2_sdk/entity/cbaseentity.h +++ b/src/cs2_sdk/entity/cbaseentity.h @@ -165,7 +165,7 @@ class CBaseEntity : public CEntityInstance void SetName(const char* pName) { - addresses::CEntityIdentity_SetEntityName(m_pEntity, pName); + addresses::ScriptSetEntityName(this, pName); } void TakeDamage(CTakeDamageInfo& info) @@ -196,14 +196,12 @@ class CBaseEntity : public CEntityInstance bool IsPawn() { - static int offset = g_GameConfig->GetOffset("IsPlayerPawn"); - return CALL_VIRTUAL(bool, offset, this); + return addresses::IsPlayerPawn(this); } bool IsController() { - static int offset = g_GameConfig->GetOffset("IsPlayerController"); - return CALL_VIRTUAL(bool, offset, this); + return addresses::IsPlayerController(this); } void AcceptInput(const char* pInputName, variant_t value = variant_t(""), CEntityInstance* pActivator = nullptr, CEntityInstance* pCaller = nullptr) @@ -226,7 +224,7 @@ class CBaseEntity : public CEntityInstance // Emit a sound event void EmitSound(const char* pszSound, int nPitch = 100, float flVolume = 1.0, float flDelay = 0.0) { - addresses::CBaseEntity_EmitSoundParams(this, pszSound, nPitch, flVolume, flDelay); + addresses::ScriptEmitSoundParams(this, pszSound, nPitch, flVolume, flDelay); } StartSoundEventInfo EmitSoundFilter(IRecipientFilter& filter, const char* pszSound, float flVolume = 1.0, float flPitch = 1.0) diff --git a/src/cs2_sdk/entity/ccsplayercontroller.h b/src/cs2_sdk/entity/ccsplayercontroller.h index cc994b9d..5376c666 100644 --- a/src/cs2_sdk/entity/ccsplayercontroller.h +++ b/src/cs2_sdk/entity/ccsplayercontroller.h @@ -84,8 +84,7 @@ class CCSPlayerController : public CBasePlayerController void ChangeTeam(int iTeam) { - static int offset = g_GameConfig->GetOffset("CCSPlayerController_ChangeTeam"); - CALL_VIRTUAL(void, offset, this, iTeam); + addresses::ChangeTeam(this, iTeam); } void SwitchTeam(int iTeam) diff --git a/src/cs2fixes.cpp b/src/cs2fixes.cpp index f0a789df..803b7840 100644 --- a/src/cs2fixes.cpp +++ b/src/cs2fixes.cpp @@ -207,6 +207,9 @@ bool CS2Fixes::Load(PluginId id, ISmmAPI* ismm, char* error, size_t maxlen, bool if (!addresses::Initialize(g_GameConfig)) bRequiredInitLoaded = false; + if (!addresses::InitializeVScriptFunctions()) + bRequiredInitLoaded = false; + if (!InitPatches(g_GameConfig)) bRequiredInitLoaded = false; diff --git a/src/utils/virtual.h b/src/utils/virtual.h index 32c696a7..8440e434 100644 --- a/src/utils/virtual.h +++ b/src/utils/virtual.h @@ -19,6 +19,7 @@ #pragma once #include "platform.h" +#include "tier0/dbg.h" #define CALL_VIRTUAL(retType, idx, ...) \ vmt::CallVirtual(idx, __VA_ARGS__) diff --git a/src/utils/vscript_function.cpp b/src/utils/vscript_function.cpp new file mode 100644 index 00000000..5519b81a --- /dev/null +++ b/src/utils/vscript_function.cpp @@ -0,0 +1,47 @@ +/** + * ============================================================================= + * CS2Fixes + * Copyright (C) 2023-2026 Source2ZE + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include "vscript_function.h" +#include "entityclass.h" +#include "entitysystem.h" + +void* GetVScriptFunction(ScriptClassDesc_t* pScriptDesc, const char* pszFuncName) +{ + // https://github.com/Wend4r/sourcesdk/blob/758e43823f02fcd40498d33a42cd93243258fe1e/public/vscript/ivscript.h#L280 + struct ScriptFunctionBindingCurrent_t + { + ScriptFuncDescriptor_t m_desc; + ScriptClassDesc_t* m_pClassDesc; + ScriptBindingFunc_t m_pfnBinding; + void* m_pFunction; + ScriptFuncBindingFlags_t m_flags; + }; + + const auto& functionBindings = *reinterpret_cast*>(&pScriptDesc->m_FunctionBindings); + FOR_EACH_VEC(functionBindings, i) + { + auto& functionBinding = functionBindings.Element(i); + if (V_strcmp(functionBinding.m_desc.m_pszScriptName, pszFuncName) != 0) + continue; + + return functionBinding.m_pFunction; + } + + return nullptr; +} diff --git a/src/utils/vscript_function.h b/src/utils/vscript_function.h new file mode 100644 index 00000000..fe990fd1 --- /dev/null +++ b/src/utils/vscript_function.h @@ -0,0 +1,102 @@ +/** + * ============================================================================= + * CS2Fixes + * Copyright (C) 2023-2026 Source2ZE + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#pragma once + +#include "platform.h" +#include "virtual.h" + +struct ScriptClassDesc_t; +class CBaseEntity; +class Vector; +class QAngle; + +void* GetVScriptFunction(ScriptClassDesc_t* pScriptDesc, const char* pszFuncName); + +template +class CVScriptFunction +{ +public: + CVScriptFunction() : + m_pFunction(nullptr), m_bVirtual(false) {} + + inline bool Initialize(void* pFunction) + { + if (!pFunction) + return false; + +#ifdef PLATFORM_LINUX + // Itanium ABI: a pointer may encode the virtual index + uintptr_t ptr = reinterpret_cast(pFunction); + if (ptr & 1) + { + m_bVirtual = true; + m_iOffset = static_cast((ptr - 1) >> 3); + + return true; + } +#endif + + m_pFunction = reinterpret_cast(pFunction); + + return true; + } + + inline Ret operator()(Class* pThis, Args... args) + { + if (m_bVirtual) + { + auto pFunction = vmt::GetVMethod(m_iOffset, pThis); + + return pFunction(pThis, args...); + } + + return m_pFunction(pThis, args...); + } + + inline bool IsVirtual() const + { + return m_bVirtual; + } + + inline void* GetPtr() const + { + if (IsVirtual()) + return nullptr; + + return reinterpret_cast(m_pFunction); + } + + inline int GetOffset() const + { + if (IsVirtual()) + return m_iOffset; + + return -1; + } + +protected: + union + { + Ret(FASTCALL* m_pFunction)(Class*, Args...); + int m_iOffset; + }; + + bool m_bVirtual; +}; From 989c923097984047ff211efaaa246923e34f81e7 Mon Sep 17 00:00:00 2001 From: Vauff Date: Sun, 16 Aug 2026 02:04:46 -0400 Subject: [PATCH 16/25] Migrate to build containers & start SteamRT4 builds --- .github/workflows/build.yml | 41 +++++++++++++++++-------------------- Dockerfile | 9 +------- 2 files changed, 20 insertions(+), 30 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 59de4c99..d02a62ff 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,7 @@ on: jobs: build: - name: Build + name: ${{ matrix.name }} Build runs-on: ${{ matrix.os }} env: HL2SDKCS2: ${{ github.workspace }}/CS2Fixes/sdk @@ -30,11 +30,15 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest] include: - os: windows-latest + name: Windows - os: ubuntu-latest - container: registry.gitlab.steamos.cloud/steamrt/sniper/sdk + name: SteamRT3 + container: ghcr.io/source2ze/build-containers:steamrt3 + - os: ubuntu-latest + name: SteamRT4 + container: ghcr.io/source2ze/build-containers:steamrt4 steps: - name: Checkout uses: actions/checkout@v7 @@ -52,27 +56,15 @@ jobs: submodules: recursive - name: Checkout AMBuild + if: matrix.os == 'windows-latest' uses: actions/checkout@v7 with: repository: alliedmodders/ambuild path: ambuild - name: Install AMBuild - shell: bash - run: | - if [ "$RUNNER_OS" == "Windows" ]; then - pip install setuptools - fi - cd ambuild && python setup.py install && cd .. - - - name: Install Clang 21 - if: matrix.os == 'ubuntu-latest' - run: | - echo "deb http://apt.llvm.org/bullseye/ llvm-toolchain-bullseye-21 main" >> /etc/apt/sources.list.d/llvm.list - echo "deb-src http://apt.llvm.org/bullseye/ llvm-toolchain-bullseye-21 main" >> /etc/apt/sources.list.d/llvm.list - wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - - apt update && apt install -y clang-21 - ln -sf /usr/bin/clang-21 /usr/bin/clang && ln -sf /usr/bin/clang++-21 /usr/bin/clang++ + if: matrix.os == 'windows-latest' + run: pip install setuptools && cd ambuild && python setup.py install && cd .. - name: Build working-directory: CS2Fixes @@ -85,7 +77,7 @@ jobs: - name: Upload artifact uses: actions/upload-artifact@v7 with: - name: ${{ runner.os }} + name: ${{ matrix.name }} path: CS2Fixes/build/package/cs2 release: @@ -102,9 +94,14 @@ jobs: run: | version=`echo $GITHUB_REF | sed "s/refs\/tags\///"` ls -Rall - if [ -d "./Linux/" ]; then - cd ./Linux/ - tar -czf ../${{ github.event.repository.name }}-${version}-linux.tar.gz * + if [ -d "./SteamRT3/" ]; then + cd ./SteamRT3/ + tar -czf ../${{ github.event.repository.name }}-${version}-steamrt3.tar.gz * + cd - + fi + if [ -d "./SteamRT4/" ]; then + cd ./SteamRT4/ + tar -czf ../${{ github.event.repository.name }}-${version}-steamrt4.tar.gz * cd - fi if [ -d "./Windows/" ]; then diff --git a/Dockerfile b/Dockerfile index 1608da65..a68009bc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,7 @@ -FROM registry.gitlab.steamos.cloud/steamrt/sniper/sdk +FROM ghcr.io/source2ze/build-containers:steamrt3 WORKDIR /app -RUN echo "deb http://apt.llvm.org/bullseye/ llvm-toolchain-bullseye-21 main" >> /etc/apt/sources.list.d/llvm.list -RUN echo "deb-src http://apt.llvm.org/bullseye/ llvm-toolchain-bullseye-21 main" >> /etc/apt/sources.list.d/llvm.list -RUN wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - -RUN apt update && apt install -y clang-21 -RUN ln -sf /usr/bin/clang-21 /usr/bin/clang && ln -sf /usr/bin/clang++-21 /usr/bin/clang++ -RUN git clone https://github.com/alliedmodders/ambuild -RUN cd ambuild && python setup.py install && cd .. RUN git clone https://github.com/alliedmodders/metamod-source RUN git config --global --add safe.directory /app From 58d9c7852379b56c569425339d6f00b9154538df Mon Sep 17 00:00:00 2001 From: Vauff Date: Wed, 19 Aug 2026 00:39:03 -0400 Subject: [PATCH 17/25] Update to VS 2026 --- CS2Fixes.vcxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CS2Fixes.vcxproj b/CS2Fixes.vcxproj index 4249b95e..85d63b9b 100644 --- a/CS2Fixes.vcxproj +++ b/CS2Fixes.vcxproj @@ -30,26 +30,26 @@ DynamicLibrary true - v143 + v145 Unicode DynamicLibrary false - v143 + v145 true Unicode DynamicLibrary true - v143 + v145 MultiByte DynamicLibrary false - v143 + v145 true MultiByte From e5a3557cfb4c6b1a648d784bcc5c1f4622e477ec Mon Sep 17 00:00:00 2001 From: Vauff Date: Wed, 19 Aug 2026 19:52:57 -0400 Subject: [PATCH 18/25] Fix several exploits in chat command processing - Fixed different command capitalization bypassing the chat command hook - Fixed possible null dereference on ZEPlayer in chat command hook - Fixed chat messages from not fully ingame players sometimes being treated as console messages --- src/cs2fixes.cpp | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/cs2fixes.cpp b/src/cs2fixes.cpp index 803b7840..f9c41187 100644 --- a/src/cs2fixes.cpp +++ b/src/cs2fixes.cpp @@ -536,15 +536,24 @@ void CS2Fixes::Hook_DispatchConCommand(ConCommandRef cmdHandle, const CCommandCo if (!g_cvarEnableCommands.Get()) RETURN_META(MRES_IGNORED); - bool bSay = !V_strcmp(args.Arg(0), "say"); - bool bTeamSay = !V_strcmp(args.Arg(0), "say_team"); + bool bSay = !V_stricmp(args.Arg(0), "say"); + bool bTeamSay = !V_stricmp(args.Arg(0), "say_team"); if (iCommandPlayerSlot != -1 && (bSay || bTeamSay)) { - auto pController = CCSPlayerController::FromSlot(iCommandPlayerSlot); - bool bGagged = pController && pController->GetZEPlayer()->IsGagged(); - bool bFlooding = pController && pController->GetZEPlayer()->IsFlooding(); - bool bIsAdmin = pController && pController->GetZEPlayer()->IsAdminFlagSet(ADMFLAG_GENERIC); + CCSPlayerController* pController = CCSPlayerController::FromSlot(iCommandPlayerSlot); + ZEPlayer* pPlayer = pController ? pController->GetZEPlayer() : nullptr; + + // Block chat messages from players not fully ingame, can be interpreted as console messages + if (!pPlayer || !pPlayer->IsInGame()) + { + Message("Blocked chat message from user ID %i not fully in game\n", g_pEngineServer2->GetPlayerUserId(iCommandPlayerSlot).Get()); + RETURN_META(MRES_SUPERCEDE); + } + + bool bGagged = pPlayer->IsGagged(); + bool bFlooding = pPlayer->IsFlooding(); + bool bIsAdmin = pPlayer->IsAdminFlagSet(ADMFLAG_GENERIC); bool bAdminChat = bTeamSay && *args[1] == '@'; bool bSilent = *args[1] == '/' || bAdminChat; bool bCommand = *args[1] == '!' || *args[1] == '/'; @@ -857,7 +866,7 @@ void CS2Fixes::Hook_ClientCommand(CPlayerSlot slot, const CCommand& args) RETURN_META(MRES_SUPERCEDE); } - if (g_cvarEnableZR.Get() && slot != -1 && !V_strncmp(args.Arg(0), "jointeam", 8)) + if (g_cvarEnableZR.Get() && slot != -1 && !V_strnicmp(args.Arg(0), "jointeam", 8)) { ZR_Hook_ClientCommand_JoinTeam(slot, args); RETURN_META(MRES_SUPERCEDE); From 3622854e0876c3fc8ad497764f46d68e4da20aff Mon Sep 17 00:00:00 2001 From: tilgep Date: Fri, 21 Aug 2026 00:44:06 +0100 Subject: [PATCH 19/25] EntWatch clantag colours (#462) --- cfg/cs2fixes/cs2fixes.cfg | 2 ++ src/entwatch.cpp | 18 +++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/cfg/cs2fixes/cs2fixes.cfg b/cfg/cs2fixes/cs2fixes.cfg index 3a7b746b..35d00599 100644 --- a/cfg/cs2fixes/cs2fixes.cfg +++ b/cfg/cs2fixes/cs2fixes.cfg @@ -160,6 +160,8 @@ cs2f_enable_button_watch 0 // INCOMPATIBLE WITH CS#. Whether to enable button entwatch_enable 0 // INCOMPATIBLE WITH CS#. Whether to enable EntWatch features entwatch_auto_filter 1 // Whether to automatically block non-item holders from triggering uses entwatch_clantag 1 // Whether to set item holder's clantag and set 9999 score +entwatch_clantag_colour_mode 1 // Whether to set item clantag colours (0=No colour, 1=Use item colour, 2=Use entwatch_clantag_colour value) +entwatch_clantag_colour "FFFFFF" // Hex colour (RRGGBB) to use if entwatch_clantag_colour_mode 2 entwatch_hud 1 // Whether to enable the EntWatch hud and related commands entwatch_score 9999 // Score to give item holders (0 = dont change score at all) Requires entwatch_clantag 1 entwatch_glow 1000 // Distance that dropped item weapon glow will be visible (0 = glow disabled) diff --git a/src/entwatch.cpp b/src/entwatch.cpp index 0ae9d07f..c4d827c3 100644 --- a/src/entwatch.cpp +++ b/src/entwatch.cpp @@ -72,6 +72,8 @@ SH_DECL_MANUALHOOK1_void(CTriggerMultiple_EndTouch, 0, 0, 0, CBaseEntity*); CConVar g_cvarEnableEntWatch("entwatch_enable", FCVAR_NONE, "INCOMPATIBLE WITH CS#. Whether to enable EntWatch features", false); CConVar g_cvarEnableFiltering("entwatch_auto_filter", FCVAR_NONE, "Whether to automatically block non-item holders from triggering uses", true); CConVar g_cvarUseEntwatchClantag("entwatch_clantag", FCVAR_NONE, "Whether to set item holder's clantag and set score", true); +CConVar g_cvarClantagColourMode("entwatch_clantag_colour_mode", FCVAR_NONE, "Whether to set item clantag colours (0=No colour, 1=Use item colour, 2=Use entwatch_clantag_colour value)", 1, true, 0, true, 2); +CConVar g_cvarClantagColour("entwatch_clantag_colour", FCVAR_NONE, "Hex colour (RRGGBB) to use if entwatch_clantag_colour_mode 2", "FFFFFF"); CConVar g_cvarItemHolderScore("entwatch_score", FCVAR_NONE, "Score to give item holders (0 = dont change score at all) Requires entwatch_clantag 1", 9999, true, 0, false, 0); CConVar g_cvarEnableEntwatchHud("entwatch_hud", FCVAR_NONE, "Whether to enable the EntWatch hud and related commands", true); @@ -1988,7 +1990,21 @@ float EW_UpdateHud() if (g_cvarUseEntwatchClantag.Get()) { - V_snprintf(pItem->sClantag, sizeof(EWItemInstance::sClantag), "[%s]%s:", sItemText.c_str(), pItem->szShortName.c_str()); + switch (g_cvarClantagColourMode.Get()) + { + case 0: + V_snprintf(pItem->sClantag, sizeof(EWItemInstance::sClantag), "[%s]%s:", + sItemText.c_str(), pItem->szShortName.c_str()); + break; + case 1: + V_snprintf(pItem->sClantag, sizeof(EWItemInstance::sClantag), "[%s]%s", + pItem->colorGlow.r(), pItem->colorGlow.g(), pItem->colorGlow.b(), sItemText.c_str(), pItem->szShortName.c_str()); + break; + case 2: + V_snprintf(pItem->sClantag, sizeof(EWItemInstance::sClantag), "[%s]%s", + g_cvarClantagColour.Get().String(), sItemText.c_str(), pItem->szShortName.c_str()); + break; + } if (pItem->bHasThisClantag) pOwner->SetClanTag(pItem->sClantag); } From ac326a980d91d9ad9994297b68912c18900cacf4 Mon Sep 17 00:00:00 2001 From: Vauff Date: Mon, 24 Aug 2026 21:53:09 -0400 Subject: [PATCH 20/25] Update signatures for 2026-08-24 CS2 update --- gamedata/cs2fixes.jsonc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gamedata/cs2fixes.jsonc b/gamedata/cs2fixes.jsonc index 8cdb75a7..5f4784bd 100644 --- a/gamedata/cs2fixes.jsonc +++ b/gamedata/cs2fixes.jsonc @@ -160,7 +160,7 @@ { "library": "server", "windows": "48 8B C4 4C 89 48 ? 48 89 48 ? 55 41 54", - "linux": "55 48 89 E5 41 57 41 89 F7 41 56 48 8D 35" + "linux": "55 48 89 E5 41 57 41 56 41 55 41 54 41 89 F4 53 48 8D 35 ? ? ? ? 48 89 FB" }, "CCSPlayer_WeaponServices_CanUse": { @@ -344,12 +344,12 @@ "windows": "75 ? 48 8B 03 48 8B CB FF 90 ? ? ? ? 84 C0 74 ? 48 8D 05", "linux": "75 ? 48 8B 03 48 89 DF FF 90 ? ? ? ? 84 C0 74 ? 48 8D 05" }, - // "Going to intermission...\n" + // "Going to intermission..." "CCSGameRules_GoToIntermission": { "library": "server", "windows": "48 89 5C 24 ? 55 56 57 41 54 41 55 41 56 41 57 48 8D AC 24 ? ? ? ? 48 81 EC ? ? ? ? 4C 8B E9", - "linux": "55 31 C0 48 89 E5 41 57 41 56 41 55 49 89 FD 41 54 48 8D 3D" + "linux": "55 31 C0 48 89 E5 41 57 41 56 41 55 41 54 41 89 F4" }, // Called in return by function with "Usage: setang_exact pitch yaw " string "CBasePlayerPawn_SnapViewAngles": From 10afbd6073ce7e46e8ccee7b1c13bba2da6fdfac Mon Sep 17 00:00:00 2001 From: Vauff Date: Tue, 25 Aug 2026 03:31:58 -0400 Subject: [PATCH 21/25] Add AI usage guidelines --- AGENTS.md | 1 + CONTRIBUTING.md | 9 +++++++++ 2 files changed, 10 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/AGENTS.md b/AGENTS.md index 9de14e55..be89d8cd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1 +1,2 @@ +- Your primary job is to assist human developers, if making an upstream contribution then [CONTRIBUTING.md](CONTRIBUTING.md) must be followed - For compilation tests, run `docker compose up` in the project directory \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..a793a7f9 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,9 @@ +# Use of AI Tools + +**Human Accountability:** A human contributor will be held fully responsible for the code, documentation, description, and (to a reasonable extent) testing of a pull request. They must review all AI output. + +**Disclosure Requirements:** Substantial use of AI tools must be disclosed in pull request descriptions. + +**No Autonomous Agents:** AI tools are barred from autonomously opening pull requests, pushing commits, and making comments. + +**Quality Over Volume:** Maintainers have explicit permission to close low-evidence or generic AI dumps without deep review. \ No newline at end of file From 980824137408012618b0611100972099f54f19fc Mon Sep 17 00:00:00 2001 From: tilgep Date: Tue, 25 Aug 2026 10:39:36 +0100 Subject: [PATCH 22/25] Fix entwatch score reset bug --- src/entwatch.cpp | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/entwatch.cpp b/src/entwatch.cpp index c4d827c3..d5be7ca2 100644 --- a/src/entwatch.cpp +++ b/src/entwatch.cpp @@ -1486,20 +1486,13 @@ void CEWHandler::ResetAllClantags() if (!GetGlobals()) return; - // Reset everyone's scores and tags for insurance + // Reset everyone's tags for (int i = 0; i < GetGlobals()->maxClients; i++) { CCSPlayerController* pController = CCSPlayerController::FromSlot(i); if (!pController) continue; - // Bring score down below entwatch_score so new item holders show above - if (pController->m_iScore >= g_cvarItemHolderScore.Get()) - { - int score = pController->m_iScore % g_cvarItemHolderScore.Get(); - pController->m_iScore = score; - } - pController->SetClanTag(""); } } From 35445bc42d2f070cb5bc4447512b0d06b6a5b8d0 Mon Sep 17 00:00:00 2001 From: tilgep Date: Tue, 25 Aug 2026 10:44:23 +0100 Subject: [PATCH 23/25] Idle manager updates --- src/cs2fixes.cpp | 20 ++++++++++++++++---- src/idlemanager.cpp | 6 +++++- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/cs2fixes.cpp b/src/cs2fixes.cpp index f9c41187..8afa9235 100644 --- a/src/cs2fixes.cpp +++ b/src/cs2fixes.cpp @@ -577,6 +577,10 @@ void CS2Fixes::Hook_DispatchConCommand(ConCommandRef cmdHandle, const CCommandCo { SH_CALL(g_pCVar, &ICvar::DispatchConCommand) (cmdHandle, ctx, args); + + // Reset idle time if message is sent to chat + if (g_cvarIdleKickTime.Get() > 0.0f) + pPlayer->UpdateLastInputTime(); } else if (bFlooding) { @@ -850,12 +854,20 @@ void CS2Fixes::Hook_ClientCommand(CPlayerSlot slot, const CCommand& args) Message("Hook_ClientCommand(%d, \"%s\")\n", slot, args.GetCommandString()); #endif + ZEPlayer* pPlayer = g_playerManager->GetPlayer(slot); + if (g_cvarIdleKickTime.Get() > 0.0f) { - ZEPlayer* pPlayer = g_playerManager->GetPlayer(slot); - - if (pPlayer) - pPlayer->UpdateLastInputTime(); + CCSPlayerController* pController = CCSPlayerController::FromSlot(slot); + if (pPlayer && pController) + { + // Only spectators doing spectator commands reset idle timer + if (pController->m_iTeamNum() == CS_TEAM_SPECTATOR && + (!V_stricmp(args[0], "spec_mode") || + !V_stricmp(args[0], "spec_prev") || + !V_stricmp(args[0], "spec_next"))) + pPlayer->UpdateLastInputTime(); + } } if (g_cvarVoteManagerEnable.Get() && V_stricmp(args[0], "endmatch_votenextmap") == 0 && args.ArgC() == 2) diff --git a/src/idlemanager.cpp b/src/idlemanager.cpp index 55de5127..f1b2c3da 100644 --- a/src/idlemanager.cpp +++ b/src/idlemanager.cpp @@ -78,6 +78,8 @@ void CIdleSystem::CheckForIdleClients() } } +const uint64 ACTIVE_BUTTONS = IN_ATTACK + IN_JUMP + IN_DUCK + IN_FORWARD + IN_BACK + IN_MOVELEFT + IN_MOVERIGHT; + // Logged inputs and time for the logged inputs are updated every time this function is run. void CIdleSystem::UpdateIdleTimes() { @@ -106,7 +108,9 @@ void CIdleSystem::UpdateIdleTimes() if (pMovement) { const auto buttonStates = pMovement->m_nButtons().m_pButtonStates(); - iCurrentMovement = buttonStates[0]; + + // Only count certain inputs for resetting idle state + iCurrentMovement = buttonStates[0] & ACTIVE_BUTTONS; } const auto buttonsChanged = pPlayer->GetLastInputs() ^ iCurrentMovement; From f5a95f61a744e3e9257c434763df2985b9e616df Mon Sep 17 00:00:00 2001 From: Fara <44729057+Faramour@users.noreply.github.com> Date: Tue, 25 Aug 2026 21:35:40 +0200 Subject: [PATCH 24/25] Add low population features to Map Vote System (#461) * Add low population features to Map Vote System * simplify a bit + cvar changes * fix pending cooldowns not being properly reset --------- Co-authored-by: Vauff --- cfg/cs2fixes/cs2fixes.cfg | 1 + src/map_votes.cpp | 30 +++++++++++++++++++++++++----- src/map_votes.h | 5 +++-- src/playermanager.cpp | 4 ++-- 4 files changed, 31 insertions(+), 9 deletions(-) diff --git a/cfg/cs2fixes/cs2fixes.cfg b/cfg/cs2fixes/cs2fixes.cfg index 35d00599..b1a7be39 100644 --- a/cfg/cs2fixes/cs2fixes.cfg +++ b/cfg/cs2fixes/cs2fixes.cfg @@ -98,6 +98,7 @@ cs2f_vote_maps_cooldown 6.0 // Default number of hours until a map can be pla cs2f_vote_maps_cooldown_rng 0.0 // Randomness range in both directions to apply to map cooldowns cs2f_vote_max_nominations 10 // Number of nominations to include per vote, out of a maximum of 10 cs2f_vote_max_maps 10 // Number of total maps to include per vote, including nominations, out of a maximum of 10 +cs2f_vote_bypass_cd_max_count 0 // Maximum amount of players allowed to enable bypassing map cooldowns // User preferences settings cs2f_user_prefs_api "" // User Preferences REST API endpoint diff --git a/src/map_votes.cpp b/src/map_votes.cpp index 4847bd6d..72dd2479 100644 --- a/src/map_votes.cpp +++ b/src/map_votes.cpp @@ -43,6 +43,7 @@ CConVar g_cvarVoteMapsCooldown("cs2f_vote_maps_cooldown", FCVAR_NONE, "De CConVar g_cvarVoteMapsCooldownRng("cs2f_vote_maps_cooldown_rng", FCVAR_NONE, "Randomness range in both directions to apply to map cooldowns", 0.0f); CConVar g_cvarVoteMaxNominations("cs2f_vote_max_nominations", FCVAR_NONE, "Number of nominations to include per vote, out of a maximum of 10", 10, true, 0, true, 10); CConVar g_cvarVoteMaxMaps("cs2f_vote_max_maps", FCVAR_NONE, "Number of total maps to include per vote, including nominations, out of a maximum of 10", 10, true, 2, true, 10); +CConVar g_cvarVoteBypassCooldownMaxCount("cs2f_vote_bypass_cd_max_count", FCVAR_NONE, "Maximum amount of players allowed to enable bypassing map cooldowns", 0, true, 0, false, 0); CON_COMMAND_CHAT_FLAGS(reload_map_list, "- Reload map list, also reloads current map on completion", ADMFLAG_ROOT) { @@ -1108,11 +1109,16 @@ bool CMapVoteSystem::WriteMapCooldownsToFile() return true; } -void CMapVoteSystem::ClearInvalidNominations() +void CMapVoteSystem::OnPlayerCountChange() { if (!g_cvarVoteManagerEnable.Get() || m_bIsVoteOngoing || !GetGlobals()) return; + int iOnlinePlayers = g_playerManager->GetOnlinePlayerCount(false); + + if (iOnlinePlayers > m_iSessionMaxPlayerCount) + m_iSessionMaxPlayerCount = iOnlinePlayers; + for (int i = 0; i < GetGlobals()->maxClients; i++) { int iNominatedMapIndex = m_arrPlayerNominations[i]; @@ -1153,15 +1159,22 @@ void CMapVoteSystem::ApplyGameSettings(const char* pszMapName, uint64 iWorkshopI void CMapVoteSystem::OnLevelShutdown() { - // Put the map on cooldown as we transition to the next map - PutMapOnCooldown(GetCurrentMap()->GetName()); + bool bApplyCooldowns = m_iSessionMaxPlayerCount > g_cvarVoteBypassCooldownMaxCount.Get(); + + if (bApplyCooldowns) + { + // Put the map on cooldown as we transition to the next map + PutMapOnCooldown(GetCurrentMap()->GetName()); + } - // Fully apply pending group cooldowns + // Fully apply or discard pending group cooldowns for (std::shared_ptr pCooldown : m_vecCooldowns) { if (pCooldown->GetPendingCooldown() > 0.0f) { - PutMapOnCooldown(pCooldown->GetMapName(), pCooldown->GetPendingCooldown()); + if (bApplyCooldowns) + PutMapOnCooldown(pCooldown->GetMapName(), pCooldown->GetPendingCooldown()); + pCooldown->SetPendingCooldown(0.0f); } } @@ -1177,6 +1190,8 @@ void CMapVoteSystem::OnLevelShutdown() if (m_timeMapListModified != std::filesystem::last_write_time(szPath)) ReloadMapList(false); } + + m_iSessionMaxPlayerCount = 0; } std::string CMapVoteSystem::ConvertFloatToString(float fValue, int precision) @@ -1316,6 +1331,11 @@ std::shared_ptr CMapVoteSystem::GetMapCooldown(const char* pszMapName return pCooldown; } +bool CCooldown::IsOnCooldown() +{ + return GetCurrentCooldown() > 0.0f && g_playerManager->GetOnlinePlayerCount(false) > g_cvarVoteBypassCooldownMaxCount.Get(); +} + float CCooldown::GetCurrentCooldown() { time_t timeCurrent = std::time(0); diff --git a/src/map_votes.h b/src/map_votes.h index 178f65cc..ac55efac 100644 --- a/src/map_votes.h +++ b/src/map_votes.h @@ -50,8 +50,8 @@ class CCooldown void SetTimeCooldown(time_t timeCooldown) { m_timeCooldown = timeCooldown; }; float GetPendingCooldown() { return m_fPendingCooldown; }; void SetPendingCooldown(float fPendingCooldown) { m_fPendingCooldown = fPendingCooldown; }; - bool IsOnCooldown() { return GetCurrentCooldown() > 0.0f; } bool IsPending() { return m_fPendingCooldown > 0.0f && m_fPendingCooldown == GetCurrentCooldown(); }; + bool IsOnCooldown(); float GetCurrentCooldown(); private: @@ -206,7 +206,7 @@ class CMapVoteSystem std::shared_ptr GetCurrentMap() { return m_pCurrentMap; } void SetCurrentMap(std::shared_ptr pCurrentMap) { m_pCurrentMap = pCurrentMap; } int GetDownloadQueueSize() { return m_DownloadQueue.size(); } - void ClearInvalidNominations(); + void OnPlayerCountChange(); std::shared_ptr GetForcedNextMap() { return m_pForcedNextMap; } void SetForcedNextMap(std::shared_ptr pForcedNextMap) { m_pForcedNextMap = pForcedNextMap; } std::unordered_map GetNominatedMaps(); @@ -247,6 +247,7 @@ class CMapVoteSystem std::weak_ptr m_pDownloadProgressTimer; std::weak_ptr m_pRateLimitedDownloadTimer; std::vector> m_vecWorkshopDetailsQueries; + int m_iSessionMaxPlayerCount = 0; }; extern CMapVoteSystem* g_pMapVoteSystem; \ No newline at end of file diff --git a/src/playermanager.cpp b/src/playermanager.cpp index ee61cdb2..3bfe9c84 100644 --- a/src/playermanager.cpp +++ b/src/playermanager.cpp @@ -804,7 +804,7 @@ bool CPlayerManager::OnClientConnected(CPlayerSlot slot, uint64 xuid, const char ResetPlayerFlags(slot.Get()); g_pMapVoteSystem->ClearPlayerInfo(slot.Get()); - g_pMapVoteSystem->ClearInvalidNominations(); + g_pMapVoteSystem->OnPlayerCountChange(); return true; } @@ -829,7 +829,7 @@ void CPlayerManager::OnClientDisconnect(CPlayerSlot slot) // One tick delay, to ensure player count decrements CTimer::Create(0.01f, TIMERFLAG_MAP, []() { g_pVoteManager->CheckRTVStatus(); - g_pMapVoteSystem->ClearInvalidNominations(); + g_pMapVoteSystem->OnPlayerCountChange(); return -1.0f; }); From 37aa5877fcf2e8be8e7a2db33d3a1e6314663b04 Mon Sep 17 00:00:00 2001 From: Vauff Date: Tue, 25 Aug 2026 15:48:40 -0400 Subject: [PATCH 25/25] Tidying --- cfg/cs2fixes/cs2fixes.cfg | 2 +- src/commands.cpp | 2 +- src/cs2fixes.cpp | 5 +---- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/cfg/cs2fixes/cs2fixes.cfg b/cfg/cs2fixes/cs2fixes.cfg index b1a7be39..847f0545 100644 --- a/cfg/cs2fixes/cs2fixes.cfg +++ b/cfg/cs2fixes/cs2fixes.cfg @@ -9,7 +9,7 @@ cs2f_noblock_grenades 0 // Whether to use noblock on grenade projectiles cs2f_block_team_messages 0 // Whether to block team join messages cs2f_movement_unlocker_enable 0 // Whether to enable movement unlocker, clients will not predict cs2f_use_old_push 0 // Whether to use the old CSGO trigger_push behavior (Necessary for surf and other modes that heavily use ported pushes) -cs2f_hide_enable 0 // Whether to enable hide (WARNING: randomly crashes clients since 2023-12-13 CS2 update) +cs2f_hide_enable 0 // Whether to enable hide cs2f_votemanager_enable 0 // Whether to enable votemanager features such as map vote fix, nominations, RTV and extends cs2f_trigger_timer_enable 0 // Whether to process countdown messages said by Console (e.g. Hold for 10 seconds) and append the round time where the countdown resolves cs2f_block_nav_lookup 0 // Whether to block navigation mesh lookup, improves server performance but breaks bot navigation diff --git a/src/commands.cpp b/src/commands.cpp index 39ee90cf..402198b5 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -430,7 +430,7 @@ CON_COMMAND_CHAT(noshake, "- toggle noshake") ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "You have %s noshake.", bSet ? "enabled" : "disabled"); } -CConVar g_cvarEnableHide("cs2f_hide_enable", FCVAR_NONE, "Whether to enable hide (WARNING: randomly crashes clients since 2023-12-13 CS2 update)", false); +CConVar g_cvarEnableHide("cs2f_hide_enable", FCVAR_NONE, "Whether to enable hide", false); CConVar g_cvarDefaultHideDistance("cs2f_hide_distance_default", FCVAR_NONE, "The default distance for hide", 250, true, 0, false, 0); CConVar g_cvarMaxHideDistance("cs2f_hide_distance_max", FCVAR_NONE, "The max distance for hide", 2000, true, 0, false, 0); diff --git a/src/cs2fixes.cpp b/src/cs2fixes.cpp index 8afa9235..bd603b2d 100644 --- a/src/cs2fixes.cpp +++ b/src/cs2fixes.cpp @@ -862,10 +862,7 @@ void CS2Fixes::Hook_ClientCommand(CPlayerSlot slot, const CCommand& args) if (pPlayer && pController) { // Only spectators doing spectator commands reset idle timer - if (pController->m_iTeamNum() == CS_TEAM_SPECTATOR && - (!V_stricmp(args[0], "spec_mode") || - !V_stricmp(args[0], "spec_prev") || - !V_stricmp(args[0], "spec_next"))) + if (pController->m_iTeamNum() == CS_TEAM_SPECTATOR && (!V_stricmp(args[0], "spec_mode") || !V_stricmp(args[0], "spec_prev") || !V_stricmp(args[0], "spec_next"))) pPlayer->UpdateLastInputTime(); } }