diff --git a/AMBuilder b/AMBuilder index d48208dc..6a3edacb 100644 --- a/AMBuilder +++ b/AMBuilder @@ -36,7 +36,6 @@ for sdk_target in MMSPlugin.sdk_targets: 'src/adminsystem.cpp', 'src/commands.cpp', 'src/addresses.cpp', - 'src/detours.cpp', 'src/entities.cpp', 'src/events.cpp', 'src/utils/entity.cpp', @@ -51,6 +50,7 @@ for sdk_target in MMSPlugin.sdk_targets: 'src/gameconfig.cpp', 'src/gamesystem.cpp', 'src/votemanager.cpp', + 'src/hookmanager.cpp', 'src/httpmanager.cpp', 'src/discord.cpp', 'src/map_votes.cpp', diff --git a/CS2Fixes.vcxproj b/CS2Fixes.vcxproj index e564762d..36fc5ad4 100644 --- a/CS2Fixes.vcxproj +++ b/CS2Fixes.vcxproj @@ -193,13 +193,13 @@ - + @@ -264,13 +264,13 @@ - + diff --git a/CS2Fixes.vcxproj.filters b/CS2Fixes.vcxproj.filters index 2696c312..60943f02 100644 --- a/CS2Fixes.vcxproj.filters +++ b/CS2Fixes.vcxproj.filters @@ -65,9 +65,6 @@ Source Files - - Source Files - Source Files @@ -86,6 +83,9 @@ Source Files\sdk + + Source Files + Source Files @@ -256,9 +256,6 @@ Header Files - - Header Files - Header Files @@ -274,6 +271,9 @@ Header Files + + Header Files + Header Files diff --git a/src/adminsystem.cpp b/src/adminsystem.cpp index 1cdc6726..69e575ad 100644 --- a/src/adminsystem.cpp +++ b/src/adminsystem.cpp @@ -21,7 +21,6 @@ #include "KeyValues.h" #include "commands.h" #include "ctimer.h" -#include "detours.h" #include "discord.h" #include "entity/cbaseentity.h" #include "entity/cgamerules.h" @@ -29,6 +28,7 @@ #include "entwatch.h" #include "filesystem.h" #include "gamesystem.h" +#include "hookmanager.h" #include "hud_manager.h" #include "icvar.h" #include "interfaces/interfaces.h" diff --git a/src/buttonwatch.cpp b/src/buttonwatch.cpp index b8e0c00a..cb7df77e 100644 --- a/src/buttonwatch.cpp +++ b/src/buttonwatch.cpp @@ -22,7 +22,6 @@ #include "commands.h" #include "cs2fixes.h" #include "ctimer.h" -#include "detours.h" #include "entity.h" #include "entity/cbaseplayercontroller.h" #include "entity/ccsplayercontroller.h" diff --git a/src/buttonwatch.h b/src/buttonwatch.h index d7e9a825..792aeae4 100644 --- a/src/buttonwatch.h +++ b/src/buttonwatch.h @@ -18,7 +18,7 @@ */ #pragma once -#include "detours.h" +#include "cs2_sdk/entityio.h" extern CConVar g_cvarEnableButtonWatch; diff --git a/src/commands.cpp b/src/commands.cpp index 51642931..dab1855d 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -21,7 +21,6 @@ #include "adminsystem.h" #include "common.h" #include "ctimer.h" -#include "detours.h" #include "discord.h" #include "engine/igameeventsystem.h" #include "entity/cbaseentity.h" @@ -31,6 +30,7 @@ #include "entity/ccsweaponbase.h" #include "entity/cparticlesystem.h" #include "entity/lights.h" +#include "hookmanager.h" #include "httpmanager.h" #include "leader.h" #include "networksystem/inetworkmessages.h" diff --git a/src/cs2_sdk/entity/cbaseentity.h b/src/cs2_sdk/entity/cbaseentity.h index e502d01d..93b96729 100644 --- a/src/cs2_sdk/entity/cbaseentity.h +++ b/src/cs2_sdk/entity/cbaseentity.h @@ -19,8 +19,8 @@ #pragma once +#include "../../addresses.h" #include "../../gameconfig.h" -#include "../detours.h" #include "ccollisionproperty.h" #include "ctakedamageinfo.h" #include "ehandle.h" diff --git a/src/cs2fixes.cpp b/src/cs2fixes.cpp index d8a6c785..6c29e66d 100644 --- a/src/cs2fixes.cpp +++ b/src/cs2fixes.cpp @@ -26,7 +26,6 @@ #include "common.h" #include "cs_gameevents.pb.h" #include "ctimer.h" -#include "detours.h" #include "discord.h" #include "entities.h" #include "entity/ccsplayercontroller.h" @@ -38,6 +37,7 @@ #include "gameconfig.h" #include "gameevents.pb.h" #include "gamesystem.h" +#include "hookmanager.h" #include "httpmanager.h" #include "hud_manager.h" #include "icvar.h" @@ -64,37 +64,6 @@ #include "tier0/memdbgon.h" -class GameSessionConfiguration_t -{}; - -KHook::Virtual gameFrameHook(&IServerGameDLL::GameFrame, &g_CS2Fixes, nullptr, &CS2Fixes::Hook_GameFrame_Post); -KHook::Virtual gameServerSteamAPIActivatedHook(&IServerGameDLL::GameServerSteamAPIActivated, &g_CS2Fixes, &CS2Fixes::Hook_GameServerSteamAPIActivated, nullptr); -KHook::Virtual applyGameSettingsHook(&IServerGameDLL::ApplyGameSettings, &g_CS2Fixes, &CS2Fixes::Hook_ApplyGameSettings, nullptr); -KHook::Virtual clientActiveHook(&IServerGameClients::ClientActive, &g_CS2Fixes, nullptr, &CS2Fixes::Hook_ClientActive_Post); -KHook::Virtual clientDisconnectHook(&IServerGameClients::ClientDisconnect, &g_CS2Fixes, nullptr, &CS2Fixes::Hook_ClientDisconnect_Post); -KHook::Virtual clientPutInServerHook(&IServerGameClients::ClientPutInServer, &g_CS2Fixes, nullptr, &CS2Fixes::Hook_ClientPutInServer_Post); -KHook::Virtual clientSettingsChangedHook(&IServerGameClients::ClientSettingsChanged, &g_CS2Fixes, &CS2Fixes::Hook_ClientSettingsChanged, nullptr); -KHook::Virtual onClientConnectedHook(&IServerGameClients::OnClientConnected, &g_CS2Fixes, &CS2Fixes::Hook_OnClientConnected, nullptr); -KHook::Virtual clientConnectHook(&IServerGameClients::ClientConnect, &g_CS2Fixes, &CS2Fixes::Hook_ClientConnect, nullptr); -KHook::Virtual clientCommandHook(&IServerGameClients::ClientCommand, &g_CS2Fixes, &CS2Fixes::Hook_ClientCommand, nullptr); -KHook::Virtual postEventAbstractHook(&IGameEventSystem::PostEventAbstract, &g_CS2Fixes, &CS2Fixes::Hook_PostEventAbstract, nullptr); -KHook::Virtual startupServerHook(&INetworkServerService::StartupServer, &g_CS2Fixes, nullptr, &CS2Fixes::Hook_StartupServer_Post); -KHook::Virtual checkTransmitHook(&ISource2GameEntities::CheckTransmit, &g_CS2Fixes, nullptr, &CS2Fixes::Hook_CheckTransmit_Post); -KHook::Virtual dispatchConCommandHook(&ICvar::DispatchConCommand, &g_CS2Fixes, &CS2Fixes::Hook_DispatchConCommand, nullptr); -KHook::Virtual loadEventsFromFileHook(&IGameEventManager2::LoadEventsFromFile, &g_CS2Fixes, &CS2Fixes::Hook_LoadEventsFromFile, nullptr); -KHook::Virtual spawnHook(&CEntitySystem::Spawn, &g_CS2Fixes, nullptr, &CS2Fixes::Hook_Spawn_Post); -KHook::Virtual setGameSpawnGroupMgrHook(&INetworkGameServer::SetGameSpawnGroupMgr, &g_CS2Fixes, &CS2Fixes::Hook_SetGameSpawnGroupMgr, nullptr); -KHook::Virtual createWorkshopMapGroupHook(&g_CS2Fixes, &CS2Fixes::Hook_CreateWorkshopMapGroup, nullptr); -KHook::Virtual getTouchingListHook(&g_CS2Fixes, nullptr, &CS2Fixes::Hook_GetTouchingList_Post); -KHook::Virtual checkMovingGroundHook(&g_CS2Fixes, &CS2Fixes::Hook_CheckMovingGround, nullptr); -KHook::Virtual dropWeaponHook(&g_CS2Fixes, nullptr, &CS2Fixes::Hook_DropWeapon_Post); -KHook::Virtual playerEquipUseHook(&g_CS2Fixes, &CS2Fixes::Hook_PlayerEquipUse, nullptr); -KHook::Virtual playerEquipPrecacheHook(&g_CS2Fixes, nullptr, &CS2Fixes::Hook_PlayerEquipPrecache_Post); -KHook::Virtual triggerGravityPrecacheHook(&g_CS2Fixes, nullptr, &CS2Fixes::Hook_TriggerGravityPrecache_Post); -KHook::Virtual triggerGravityEndTouchHook(&g_CS2Fixes, nullptr, &CS2Fixes::Hook_TriggerGravityEndTouch_Post); -KHook::Virtual onTakeDamageAliveHook(&g_CS2Fixes, &CS2Fixes::Hook_OnTakeDamage_Alive, nullptr); -KHook::Virtual playerPawnTeleportHook(&g_CS2Fixes, &CS2Fixes::Hook_CCSPlayerPawn_Teleport, nullptr); - CS2Fixes g_CS2Fixes; IGameEventSystem* g_gameEventSystem = nullptr; IGameEventManager2* g_gameEventManager = nullptr; @@ -103,18 +72,6 @@ IVEngineServer2* g_pEngineServer2 = nullptr; CCSGameRules* g_pGameRules = nullptr; // Will be null between map end & new map startup, null check if necessary! CSpawnGroupMgrGameSystem* g_pSpawnGroupMgr = nullptr; // Will be null between map end & new map startup, null check if necessary! -IGameEventManager2* g_pCGameEventManagerVTable = nullptr; -CEntitySystem* g_pCEntitySystemVTable = nullptr; -CVPhys2World* g_pCVPhys2WorldVTable = nullptr; -CCSPlayer_MovementServices* g_pCCSPlayer_MovementServicesVTable = nullptr; -CCSPlayer_WeaponServices* g_pCCSPlayer_WeaponServicesVTable = nullptr; -CGamePlayerEquip* g_pCGamePlayerEquipVTable = nullptr; -CTriggerGravity* g_pTriggerGravityVTable = nullptr; -CCSPlayerPawn* g_pCCSPlayerPawnVTable = nullptr; - -double g_flUniversalTime = 0.0; -float g_flLastTickedTime = 0.0f; -bool g_bHasTicked = false; bool g_bRequiredInitLoaded = true; CGameEntitySystem* GameEntitySystem() @@ -170,185 +127,17 @@ bool CS2Fixes::Load(PluginId id, ISmmAPI* ismm, char* error, size_t maxlen, bool return false; } - gameFrameHook.Add(g_pSource2Server); - gameServerSteamAPIActivatedHook.Add(g_pSource2Server); - applyGameSettingsHook.Add(g_pSource2Server); - clientActiveHook.Add(g_pSource2GameClients); - clientDisconnectHook.Add(g_pSource2GameClients); - clientPutInServerHook.Add(g_pSource2GameClients); - clientSettingsChangedHook.Add(g_pSource2GameClients); - onClientConnectedHook.Add(g_pSource2GameClients); - clientConnectHook.Add(g_pSource2GameClients); - clientCommandHook.Add(g_pSource2GameClients); - postEventAbstractHook.Add(g_gameEventSystem); - startupServerHook.Add(g_pNetworkServerService); - checkTransmitHook.Add(g_pSource2GameEntities); - dispatchConCommandHook.Add(g_pCVar); - if (!addresses::Initialize(g_GameConfig)) g_bRequiredInitLoaded = false; if (!InitPatches(g_GameConfig)) g_bRequiredInitLoaded = false; - InitDetours(g_GameConfig); + g_pHookManager = new CHookManager(g_GameConfig); if (!InitGameSystems()) g_bRequiredInitLoaded = false; - g_pCGameEventManagerVTable = (IGameEventManager2*)modules::server->FindVirtualTable("CGameEventManager"); - if (!g_pCGameEventManagerVTable) - { - Panic("Failed to find CGameEventManager vtable\n"); - g_bRequiredInitLoaded = false; - } - - loadEventsFromFileHook.AddGlobal((IGameEventManager2*)&g_pCGameEventManagerVTable); - - g_pCEntitySystemVTable = (CEntitySystem*)modules::server->FindVirtualTable("CGameEntitySystem"); - if (!g_pCEntitySystemVTable) - { - Panic("Failed to find CGameEntitySystem vtable\n"); - g_bRequiredInitLoaded = false; - } - - spawnHook.AddGlobal((CEntitySystem*)&g_pCEntitySystemVTable); - - int offset = g_GameConfig->GetOffset("IGameTypes_CreateWorkshopMapGroup"); - if (offset == -1) - { - Panic("Failed to find IGameTypes_CreateWorkshopMapGroup\n"); - g_bRequiredInitLoaded = false; - } - - createWorkshopMapGroupHook.Configure(offset); - createWorkshopMapGroupHook.Add(g_pGameTypes); - - g_pCVPhys2WorldVTable = (CVPhys2World*)modules::vphysics2->FindVirtualTable("CVPhys2World"); - if (!g_pCVPhys2WorldVTable) - { - Panic("Failed to find CVPhys2World vtable\n"); - g_bRequiredInitLoaded = false; - } - - offset = g_GameConfig->GetOffset("CVPhys2World::GetTouchingList"); - if (offset == -1) - { - Panic("Failed to find offset for CVPhys2World::GetTouchingList\n"); - g_bRequiredInitLoaded = false; - } - - getTouchingListHook.Configure(offset); - getTouchingListHook.AddGlobal((CVPhys2World*)&g_pCVPhys2WorldVTable); - - g_pCCSPlayer_MovementServicesVTable = (CCSPlayer_MovementServices*)modules::server->FindVirtualTable("CCSPlayer_MovementServices"); - if (!g_pCCSPlayer_MovementServicesVTable) - { - Panic("Failed to find CCSPlayer_MovementServices vtable\n"); - g_bRequiredInitLoaded = false; - } - - offset = g_GameConfig->GetOffset("CCSPlayer_MovementServices::CheckMovingGround"); - if (offset == -1) - { - Panic("Failed to find offset for CCSPlayer_MovementServices::CheckMovingGround\n"); - g_bRequiredInitLoaded = false; - } - - checkMovingGroundHook.Configure(offset); - checkMovingGroundHook.AddGlobal((CCSPlayer_MovementServices*)&g_pCCSPlayer_MovementServicesVTable); - - g_pCCSPlayer_WeaponServicesVTable = (CCSPlayer_WeaponServices*)modules::server->FindVirtualTable("CCSPlayer_WeaponServices"); - if (!g_pCCSPlayer_WeaponServicesVTable) - { - Panic("Failed to find CCSPlayer_WeaponServices vtable\n"); - g_bRequiredInitLoaded = false; - } - - offset = g_GameConfig->GetOffset("CCSPlayer_WeaponServices::DropWeapon"); - if (offset == -1) - { - Panic("Failed to find offset for CCSPlayer_WeaponServices::DropWeapon\n"); - g_bRequiredInitLoaded = false; - } - - dropWeaponHook.Configure(offset); - dropWeaponHook.AddGlobal((CCSPlayer_WeaponServices*)&g_pCCSPlayer_WeaponServicesVTable); - - g_pCGamePlayerEquipVTable = (CGamePlayerEquip*)modules::server->FindVirtualTable("CGamePlayerEquip"); - if (!g_pCGamePlayerEquipVTable) - { - Panic("Failed to find CGamePlayerEquip vtable\n"); - g_bRequiredInitLoaded = false; - } - - offset = g_GameConfig->GetOffset("CBaseEntity::Use"); - if (offset == -1) - { - Panic("Failed to find offset for CBaseEntity::Use\n"); - g_bRequiredInitLoaded = false; - } - - playerEquipUseHook.Configure(offset); - playerEquipUseHook.AddGlobal((CGamePlayerEquip*)&g_pCGamePlayerEquipVTable); - - offset = g_GameConfig->GetOffset("CBaseEntity::Precache"); - if (offset == -1) - { - Panic("Failed to find offset for CBaseEntity::Precache\n"); - g_bRequiredInitLoaded = false; - } - - playerEquipPrecacheHook.Configure(offset); - playerEquipPrecacheHook.AddGlobal((CGamePlayerEquip*)&g_pCGamePlayerEquipVTable); - - g_pTriggerGravityVTable = (CTriggerGravity*)modules::server->FindVirtualTable("CTriggerGravity"); - if (!g_pTriggerGravityVTable) - { - Panic("Failed to find CTriggerGravity vtable\n"); - g_bRequiredInitLoaded = false; - } - - triggerGravityPrecacheHook.Configure(offset); - triggerGravityPrecacheHook.AddGlobal((CTriggerGravity*)&g_pTriggerGravityVTable); - - offset = g_GameConfig->GetOffset("CBaseEntity::EndTouch"); - if (offset == -1) - { - Panic("Failed to find offset for CBaseEntity::EndTouch\n"); - g_bRequiredInitLoaded = false; - } - - triggerGravityEndTouchHook.Configure(offset); - triggerGravityEndTouchHook.AddGlobal((CTriggerGravity*)&g_pTriggerGravityVTable); - - g_pCCSPlayerPawnVTable = (CCSPlayerPawn*)modules::server->FindVirtualTable("CCSPlayerPawn"); - if (!g_pCCSPlayerPawnVTable) - { - Panic("Failed to find CCSPlayerPawn vtable\n"); - g_bRequiredInitLoaded = false; - } - - offset = g_GameConfig->GetOffset("CCSPlayerPawn::OnTakeDamage_Alive"); - if (offset == -1) - { - Panic("Failed to find offset for CCSPlayerPawn::OnTakeDamage_Alive\n"); - g_bRequiredInitLoaded = false; - } - - onTakeDamageAliveHook.Configure(offset); - onTakeDamageAliveHook.AddGlobal((CCSPlayerPawn*)&g_pCCSPlayerPawnVTable); - - offset = g_GameConfig->GetOffset("Teleport"); - if (offset == -1) - { - Panic("Failed to find offset for Teleport\n"); - g_bRequiredInitLoaded = false; - } - - playerPawnTeleportHook.Configure(offset); - playerPawnTeleportHook.AddGlobal((CCSPlayerPawn*)&g_pCCSPlayerPawnVTable); - if (!g_bRequiredInitLoaded) { snprintf(error, maxlen, "One or more address lookups, patches or detours failed, please refer to startup logs for more information"); @@ -429,33 +218,6 @@ bool CS2Fixes::Load(PluginId id, ISmmAPI* ismm, char* error, size_t maxlen, bool bool CS2Fixes::Unload(char* error, size_t maxlen) { - gameFrameHook.Remove(g_pSource2Server); - gameServerSteamAPIActivatedHook.Remove(g_pSource2Server); - applyGameSettingsHook.Remove(g_pSource2Server); - clientActiveHook.Remove(g_pSource2GameClients); - clientDisconnectHook.Remove(g_pSource2GameClients); - clientPutInServerHook.Remove(g_pSource2GameClients); - clientSettingsChangedHook.Remove(g_pSource2GameClients); - onClientConnectedHook.Remove(g_pSource2GameClients); - clientConnectHook.Remove(g_pSource2GameClients); - clientCommandHook.Remove(g_pSource2GameClients); - postEventAbstractHook.Remove(g_gameEventSystem); - startupServerHook.Remove(g_pNetworkServerService); - checkTransmitHook.Remove(g_pSource2GameEntities); - dispatchConCommandHook.Remove(g_pCVar); - loadEventsFromFileHook.RemoveGlobal((IGameEventManager2*)&g_pCGameEventManagerVTable); - spawnHook.RemoveGlobal((CEntitySystem*)&g_pCEntitySystemVTable); - setGameSpawnGroupMgrHook.Remove(GetNetworkGameServer()); - createWorkshopMapGroupHook.Remove(g_pGameTypes); - getTouchingListHook.RemoveGlobal((CVPhys2World*)&g_pCVPhys2WorldVTable); - checkMovingGroundHook.RemoveGlobal((CCSPlayer_MovementServices*)&g_pCCSPlayer_MovementServicesVTable); - dropWeaponHook.RemoveGlobal((CCSPlayer_WeaponServices*)&g_pCCSPlayer_WeaponServicesVTable); - playerEquipUseHook.RemoveGlobal((CGamePlayerEquip*)&g_pCGamePlayerEquipVTable); - playerEquipPrecacheHook.RemoveGlobal((CGamePlayerEquip*)&g_pCGamePlayerEquipVTable); - triggerGravityPrecacheHook.RemoveGlobal((CTriggerGravity*)&g_pTriggerGravityVTable); - triggerGravityEndTouchHook.RemoveGlobal((CTriggerGravity*)&g_pTriggerGravityVTable); - onTakeDamageAliveHook.RemoveGlobal((CCSPlayerPawn*)&g_pCCSPlayerPawnVTable); - playerPawnTeleportHook.RemoveGlobal((CCSPlayerPawn*)&g_pCCSPlayerPawnVTable); ConVar_Unregister(); @@ -468,6 +230,9 @@ bool CS2Fixes::Unload(char* error, size_t maxlen) if (g_GameConfig) delete g_GameConfig; + if (g_pHookManager) + delete g_pHookManager; + if (g_pAdminSystem) delete g_pAdminSystem; @@ -519,306 +284,6 @@ bool CS2Fixes::Unload(char* error, size_t maxlen) return true; } -KHook::Return CS2Fixes::Hook_DispatchConCommand(ICvar* pThis, ConCommandRef cmdHandle, const CCommandContext& ctx, const CCommand& args) -{ - VPROF_BUDGET("CS2Fixes::Hook_DispatchConCommand", "ConCommands"); - - if (!g_pEntitySystem) - return {KHook::Action::Ignore}; - - auto iCommandPlayerSlot = ctx.GetPlayerSlot(); - - if (!g_cvarEnableCommands.Get()) - return {KHook::Action::Ignore}; - - bool bSay = !V_strcmp(args.Arg(0), "say"); - bool bTeamSay = !V_strcmp(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); - bool bAdminChat = bTeamSay && *args[1] == '@'; - bool bSilent = *args[1] == '/' || bAdminChat; - bool bCommand = *args[1] == '!' || *args[1] == '/'; - - // Chat messages should generate events regardless - if (pController) - { - IGameEvent* pEvent = g_gameEventManager->CreateEvent("player_chat"); - - if (pEvent) - { - pEvent->SetBool("teamonly", bTeamSay); - pEvent->SetInt("userid", pController->GetPlayerSlot()); - pEvent->SetString("text", args[1]); - - g_gameEventManager->FireEvent(pEvent, true); - } - } - - if (!bGagged && !bSilent && !bFlooding) - { - dispatchConCommandHook.CallOriginal(pThis, cmdHandle, ctx, args); - } - else if (bFlooding) - { - if (pController) - ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "You are flooding the server!"); - } - else if (bAdminChat && GetGlobals()) // Admin chat can be sent by anyone but only seen by admins, use flood protection here too - { - // HACK: At this point, we can safely modify the arg buffer as it won't be passed anywhere else - // The string here is originally ("@foo bar"), trim it to be (foo bar) - char* pszMessage = (char*)(args.ArgS() + 2); - pszMessage[V_strlen(pszMessage) - 1] = 0; - - for (int i = 0; i < GetGlobals()->maxClients; i++) - { - ZEPlayer* pPlayer = g_playerManager->GetPlayer(i); - - if (!pPlayer) - continue; - - if (i == iCommandPlayerSlot.Get() || pPlayer->IsAdminFlagSet(ADMFLAG_GENERIC)) - ClientPrint(CCSPlayerController::FromSlot(i), HUD_PRINTTALK, " \4(%sADMINS) %s:\6 %s", bIsAdmin ? "" : "TO ", pController->GetPlayerName().c_str(), pszMessage); - } - } - - // Finally, run the chat command if it is one, so anything will print after the player's message - if (bCommand) - { - char* pszMessage = (char*)(args.ArgS() + 1); - - if (pszMessage[0] == '"' || pszMessage[0] == '!' || pszMessage[0] == '/') - pszMessage += 1; - - // Host_Say at some point removes the trailing " for whatever reason, so we only remove if it was never called - if ((bGagged || bSilent || bFlooding) && pszMessage[V_strlen(pszMessage) - 1] == '"') - pszMessage[V_strlen(pszMessage) - 1] = '\0'; - - ParseChatCommand(pszMessage, pController); - } - - return {KHook::Action::Supersede}; - } - - return {KHook::Action::Ignore}; -} - -CConVar g_cvarMotdUrl("cs2f_motd_url", FCVAR_NONE, "Server MOTD URL, shows up as a \"Server Website\" button in scoreboard", ""); - -KHook::Return CS2Fixes::Hook_StartupServer_Post(INetworkServerService* pThis, const GameSessionConfiguration_t& config, ISource2WorldSession* pSession, const char* pszMapName) -{ - g_pEntitySystem = GameEntitySystem(); - g_pEntitySystem->AddListenerEntity(g_pEntityListener); - - if (GetNetworkGameServer()) - setGameSpawnGroupMgrHook.Add(GetNetworkGameServer()); - - Message("Hook_StartupServer: %s\n", pszMapName); - - RegisterEventListeners(); - - if (g_bHasTicked) - RemoveTimers(TIMERFLAG_MAP); - - g_bHasTicked = false; - - g_pPanoramaVoteHandler->Reset(); - g_pVoteManager->VoteManager_Init(); - g_pIdleSystem->Reset(); - - INetworkStringTable* pInfoPanelTable = g_pNetworkStringTableServer->FindTable("InfoPanel"); - - if (pInfoPanelTable && V_strcmp(g_cvarMotdUrl.Get(), "")) - { - SetStringUserDataRequest_t pUserData; - pUserData.m_pRawData = (void*)g_cvarMotdUrl.Get().Get(); - pUserData.m_cbDataSize = g_cvarMotdUrl.Get().Length() + 1; - - pInfoPanelTable->AddString(true, "motd", &pUserData); - } - - return {KHook::Action::Ignore}; -} - -KHook::Return CS2Fixes::Hook_PlayerEquipUse(CGamePlayerEquip* pThis, InputData_t* pInput) -{ - CGamePlayerEquipHandler::Use(pThis, pInput); - - return {KHook::Action::Ignore}; -} -KHook::Return CS2Fixes::Hook_PlayerEquipPrecache_Post(CGamePlayerEquip* pThis, CEntityPrecacheContext* param) -{ - const auto kv = param->m_pKeyValues; - CGamePlayerEquipHandler::OnPrecache(pThis, kv); - - return {KHook::Action::Ignore}; -} - -KHook::Return CS2Fixes::Hook_TriggerGravityPrecache_Post(CTriggerGravity* pThis, CEntityPrecacheContext* param) -{ - const auto kv = param->m_pKeyValues; - CTriggerGravityHandler::OnPrecache(pThis, kv); - - return {KHook::Action::Ignore}; -} -KHook::Return CS2Fixes::Hook_TriggerGravityEndTouch_Post(CTriggerGravity* pThis, CBaseEntity* pOther) -{ - CTriggerGravityHandler::OnEndTouch(pThis, pOther); - - return {KHook::Action::Ignore}; -} - -KHook::Return CS2Fixes::Hook_GameServerSteamAPIActivated(IServerGameDLL* pThis) -{ - g_playerManager->OnSteamAPIActivated(); - - if (g_cvarVoteManagerEnable.Get() && !g_pMapVoteSystem->IsMapListLoaded()) - g_pMapVoteSystem->LoadMapList(); - - return {KHook::Action::Ignore}; -} - -CConVar g_cvarBlockParticleMsgs("cs2f_block_particle_msgs", FCVAR_NONE, "Whether to block CUserMsg_ParticleManager messages to fix lag/crashes, experimental", false); - -KHook::Return CS2Fixes::Hook_PostEventAbstract(IGameEventSystem* pThis, CSplitScreenSlot nSlot, bool bLocalOnly, int nClientCount, const uint64* clients, - INetworkMessageInternal* pEvent, const CNetMessage* pData, unsigned long nSize, NetChannelBufType_t bufType) -{ - // Message( "Hook_PostEvent(%d, %d, %d, %lli)\n", nSlot, bLocalOnly, nClientCount, clients ); - NetMessageInfo_t* info = pEvent->GetNetMessageInfo(); - - if (g_cvarEnableStopSound.Get() && info->m_MessageId == GE_FireBulletsId) - { - if (g_playerManager->GetSilenceSoundMask()) - { - // Post the silenced sound to those who use silencesound - // Creating a new event object requires us to include the protobuf c files which I didn't feel like doing yet - // So instead just edit the event in place and reset later - auto msg = const_cast(pData)->ToPB(); - - int32_t weapon_id = msg->weapon_id(); - int32_t sound_type = msg->sound_type(); - int32_t item_def_index = msg->item_def_index(); - - // original weapon_id will override new settings if not removed - msg->set_weapon_id(0); - msg->set_sound_type(9); - msg->set_item_def_index(61); // weapon_usp_silencer - - uint64 clientMask = *(uint64*)clients & g_playerManager->GetSilenceSoundMask(); - - postEventAbstractHook.CallOriginal(pThis, nSlot, bLocalOnly, nClientCount, &clientMask, pEvent, msg, nSize, bufType); - - msg->set_weapon_id(weapon_id); - msg->set_sound_type(sound_type); - msg->set_item_def_index(item_def_index); - } - - // Filter out people using stop/silence sound from the original event - *(uint64*)clients &= ~g_playerManager->GetStopSoundMask(); - *(uint64*)clients &= ~g_playerManager->GetSilenceSoundMask(); - } - else if (info->m_MessageId == GE_PlaceDecalEvent) - { - *(uint64*)clients &= ~g_playerManager->GetStopDecalsMask(); - } - else if (info->m_MessageId == GE_Source1LegacyGameEvent) - { - if (g_cvarEnableLeader.Get()) - Leader_PostEventAbstract_Source1LegacyGameEvent(clients, pData); - } - else if (info->m_MessageId == UM_Shake) - { - auto pPBData = const_cast(pData)->ToPB(); - if (g_cvarMaxShakeAmp.Get() >= 0 && pPBData->amplitude() > g_cvarMaxShakeAmp.Get()) - pPBData->set_amplitude(g_cvarMaxShakeAmp.Get()); - - // remove client with noshake from the event - if (g_cvarEnableNoShake.Get()) - *(uint64*)clients &= ~g_playerManager->GetNoShakeMask(); - } - else if (info->m_MessageId == GE_SosStartSoundEvent) - { - auto msg = const_cast(pData)->ToPB(); - - if (g_cvarEnableZR.Get()) - ZR_PostEventAbstract_SosStartSoundEvent(clients, msg); - - if (g_cvarEnableStopSound.Get()) - { - static std::set soundEventHashes; - - ExecuteOnce( - soundEventHashes.insert(GetSoundEventHash("Weapon_sg556.ZoomIn")); - soundEventHashes.insert(GetSoundEventHash("Weapon_sg556.ZoomOut")); - soundEventHashes.insert(GetSoundEventHash("Weapon_AUG.ZoomIn")); - soundEventHashes.insert(GetSoundEventHash("Weapon_AUG.ZoomOut")); - soundEventHashes.insert(GetSoundEventHash("Weapon_SSG08.Zoom")); - soundEventHashes.insert(GetSoundEventHash("Weapon_SSG08.ZoomOut")); - soundEventHashes.insert(GetSoundEventHash("Weapon_SCAR20.Zoom")); - soundEventHashes.insert(GetSoundEventHash("Weapon_SCAR20.ZoomOut")); - soundEventHashes.insert(GetSoundEventHash("Weapon_G3SG1.Zoom")); - soundEventHashes.insert(GetSoundEventHash("Weapon_G3SG1.ZoomOut")); - soundEventHashes.insert(GetSoundEventHash("Weapon_AWP.Zoom")); - soundEventHashes.insert(GetSoundEventHash("Weapon_AWP.ZoomOut")); - soundEventHashes.insert(GetSoundEventHash("Weapon_Revolver.Prepare")); - soundEventHashes.insert(GetSoundEventHash("Weapon.AutoSemiAutoSwitch"));); - - if (!soundEventHashes.contains(msg->soundevent_hash())) - return {KHook::Action::Ignore}; - - uint64 stopSoundMask = g_playerManager->GetStopSoundMask(); - uint64 silenceSoundMask = g_playerManager->GetSilenceSoundMask(); - - if (!msg->has_source_entity_index()) - return {KHook::Action::Ignore}; - - CBaseEntity* pSourceEntity = (CBaseEntity*)g_pEntitySystem->GetEntityInstance(CEntityIndex(msg->source_entity_index())); - int playerSlot = -1; - - if (!pSourceEntity) - return {KHook::Action::Ignore}; - - if (pSourceEntity->IsPawn() && ((CCSPlayerPawn*)pSourceEntity)->GetController()) - { - playerSlot = ((CCSPlayerPawn*)pSourceEntity)->GetController()->GetPlayerSlot(); - } - else if (!V_strncasecmp(pSourceEntity->GetClassname(), "weapon_", 7)) - { - CCSPlayerPawn* pPawn = (CCSPlayerPawn*)pSourceEntity->m_hOwnerEntity().Get(); - - if (pPawn && pPawn->IsPawn() && pPawn->GetController()) - playerSlot = pPawn->GetController()->GetPlayerSlot(); - } - - // Remove player who triggered this sound from masks - // Because some of these sounds never get played locally (Zoom's, Knife Hit/Stab) - if (playerSlot != -1 && g_playerManager->IsPlayerUsingStopSound(playerSlot)) - stopSoundMask &= ~((uint64)1 << playerSlot); - - if (playerSlot != -1 && g_playerManager->IsPlayerUsingSilenceSound(playerSlot)) - silenceSoundMask &= ~((uint64)1 << playerSlot); - - // Filter out people using stop/silence sound from hearing this sound from other players - *(uint64*)clients &= ~stopSoundMask; - *(uint64*)clients &= ~silenceSoundMask; - } - } - else if (info->m_MessageId == UM_ParticleManager) - { - // These messages were previously unused, but recently started being used for weapon particles in the AG2 update - // Unfortunately, this new system seems extremely unoptimized for 64 players, and was causing severe performance issues & vector overflow client crashes - if (g_cvarBlockParticleMsgs.Get()) - *(uint64*)clients = 0; - } - - return {KHook::Action::Ignore}; -} - void CS2Fixes::AllPluginsLoaded() { /* This is where we'd do stuff that relies on the mod or other plugins @@ -828,425 +293,6 @@ void CS2Fixes::AllPluginsLoaded() Message("AllPluginsLoaded\n"); } -KHook::Return CS2Fixes::Hook_ClientActive_Post(IServerGameClients* pThis, CPlayerSlot slot, bool bLoadGame, const char* pszName, uint64 xuid) -{ - Message("Hook_ClientActive(%d, %d, \"%s\", %lli)\n", slot, bLoadGame, pszName, xuid); - - return {KHook::Action::Ignore}; -} - -KHook::Return CS2Fixes::Hook_ClientCommand(IServerGameClients* pThis, CPlayerSlot slot, const CCommand& args) -{ -#ifdef _DEBUG - Message("Hook_ClientCommand(%d, \"%s\")\n", slot, args.GetCommandString()); -#endif - - if (g_cvarIdleKickTime.Get() > 0.0f) - { - ZEPlayer* pPlayer = g_playerManager->GetPlayer(slot); - - if (pPlayer) - pPlayer->UpdateLastInputTime(); - } - - if (g_cvarVoteManagerEnable.Get() && V_stricmp(args[0], "endmatch_votenextmap") == 0 && args.ArgC() == 2) - { - if (g_pMapVoteSystem->RegisterPlayerVote(slot, atoi(args[1]))) - return {KHook::Action::Ignore}; - else - return {KHook::Action::Supersede}; - } - - if (g_cvarEnableZR.Get() && slot != -1 && !V_strncmp(args.Arg(0), "jointeam", 8)) - { - ZR_Hook_ClientCommand_JoinTeam(slot, args); - return {KHook::Action::Supersede}; - } - - return {KHook::Action::Ignore}; -} - -KHook::Return CS2Fixes::Hook_ClientSettingsChanged(IServerGameClients* pThis, CPlayerSlot slot) -{ -#ifdef _DEBUG - Message("Hook_ClientSettingsChanged(%d)\n", slot); -#endif - - return {KHook::Action::Ignore}; -} - -KHook::Return CS2Fixes::Hook_OnClientConnected(IServerGameClients* pThis, CPlayerSlot slot, const char* pszName, uint64 xuid, const char* pszNetworkID, const char* pszAddress, bool bFakePlayer) -{ - Message("Hook_OnClientConnected(%d, \"%s\", %lli, \"%s\", \"%s\", %d)\n", slot, pszName, xuid, pszNetworkID, pszAddress, bFakePlayer); - - static ConVarRefAbstract tv_name("tv_name"); - const char* pszTvName = tv_name.GetString().Get(); - - // Ideally we would use CServerSideClient::IsHLTV().. but it doesn't work :( - if (bFakePlayer && V_strcmp(pszName, pszTvName)) - g_playerManager->OnBotConnected(slot); - - return {KHook::Action::Ignore}; -} - -KHook::Return CS2Fixes::Hook_ClientConnect(IServerGameClients* pThis, CPlayerSlot slot, const char* pszName, uint64 xuid, const char* pszNetworkID, bool unk1, CBufferString* pRejectReason) -{ - Message("Hook_ClientConnect(%d, \"%s\", %lli, \"%s\", %d, \"%s\")\n", slot, pszName, xuid, pszNetworkID, unk1, pRejectReason->Get()); - - // Player is banned - if (!g_playerManager->OnClientConnected(slot, xuid, pszNetworkID)) - return {KHook::Action::Supersede, false}; - - return {KHook::Action::Ignore}; -} - -KHook::Return CS2Fixes::Hook_ClientPutInServer_Post(IServerGameClients* pThis, CPlayerSlot slot, char const* pszName, int type, uint64 xuid) -{ - Message("Hook_ClientPutInServer(%d, \"%s\", %d, %d, %lli)\n", slot, pszName, type, xuid); - - if (!g_playerManager->GetPlayer(slot)) - return {KHook::Action::Ignore}; - - g_playerManager->OnClientPutInServer(slot); - - if (g_cvarEnableZR.Get()) - ZR_Hook_ClientPutInServer(slot, pszName, type, xuid); - - return {KHook::Action::Ignore}; -} - -KHook::Return CS2Fixes::Hook_ClientDisconnect_Post(IServerGameClients* pThis, CPlayerSlot slot, ENetworkDisconnectionReason reason, const char* pszName, uint64 xuid, const char* pszNetworkID) -{ - Message("Hook_ClientDisconnect(%d, %d, \"%s\", %lli)\n", slot, reason, pszName, xuid); - - CCSPlayerController* player = CCSPlayerController::FromSlot(slot); - - if (g_cvarEnableZR.Get()) - { - // Controller team num is not valid post-disconnect, so just check both teams - if (!ZR_CheckTeamWinConditions(CS_TEAM_T)) - ZR_CheckTeamWinConditions(CS_TEAM_CT); - } - - ZEPlayer* pPlayer = g_playerManager->GetPlayer(slot); - - if (!pPlayer) - return {KHook::Action::Ignore}; - - // Dont add to c_listdc clients that are downloading MultiAddonManager stuff or were present during a map change - if (reason != NETWORK_DISCONNECT_LOOPSHUTDOWN && reason != NETWORK_DISCONNECT_SHUTDOWN) - g_pAdminSystem->AddDisconnectedPlayer(pszName, xuid, pPlayer ? pPlayer->GetIpAddress() : ""); - - g_playerManager->OnClientDisconnect(slot); - - return {KHook::Action::Ignore}; -} - -KHook::Return CS2Fixes::Hook_GameFrame_Post(IServerGameDLL* pThis, bool simulating, bool bFirstTick, bool bLastTick) -{ - /** - * simulating: - * *********** - * true | game is ticking - * false | game is not ticking - */ - - VPROF_BUDGET("CS2Fixes::Hook_GameFramePost", "CS2FixesPerFrame"); - - if (!GetGlobals()) - return {KHook::Action::Ignore}; - - if (simulating && g_bHasTicked) - g_flUniversalTime += GetGlobals()->curtime - g_flLastTickedTime; - - g_flLastTickedTime = GetGlobals()->curtime; - g_bHasTicked = true; - - RunTimers(); - EntityHandler_OnGameFramePost(simulating, GetGlobals()->tickcount); - - return {KHook::Action::Ignore}; -} - -KHook::Return CS2Fixes::Hook_CheckTransmit_Post(ISource2GameEntities* pThis, CCheckTransmitInfo** ppInfoList, int infoCount, CBitVec<16384>& unionTransmitEdicts, - CBitVec<16384>&, const Entity2Networkable_t** pNetworkables, const uint16* pEntityIndicies, int nEntities) -{ - if (!g_pEntitySystem || !GetGlobals()) - return {KHook::Action::Ignore}; - - VPROF("CS2Fixes::Hook_CheckTransmit"); - - 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); - - if (!pSelfController || !pSelfController->IsConnected()) - continue; - - auto pSelfZEPlayer = g_playerManager->GetPlayer(iPlayerSlot); - - if (!pSelfZEPlayer) - continue; - - for (int j = 0; j < GetGlobals()->maxClients; j++) - { - CCSPlayerController* pController = CCSPlayerController::FromSlot(j); - // Always transmit to themselves - if (!pController || pController->m_bIsHLTV || j == iPlayerSlot) - continue; - - // Don't transmit other players' flashlights - CBarnLight* pFlashLight = pController->IsConnected() ? g_playerManager->GetPlayer(j)->GetFlashLight() : nullptr; - - if (!g_cvarFlashLightTransmitOthers.Get() && pFlashLight) - pInfo->m_pTransmitEntity->Clear(pFlashLight->entindex()); - - if (g_cvarEnableEntWatch.Get() && g_pEWHandler->IsConfigLoaded()) - { - // Don't transmit other players' entwatch hud - CPointWorldText* pHud = pController->IsConnected() ? g_playerManager->GetPlayer(j)->GetEntwatchHud() : nullptr; - if (pHud) - pInfo->m_pTransmitEntity->Clear(pHud->entindex()); - } - - // Always transmit other players if spectating - if (!g_cvarEnableHide.Get() || pSelfController->GetPawnState() == STATE_OBSERVER_MODE) - continue; - - // Get the actual pawn as the player could be currently spectating - CCSPlayerPawn* pPawn = pController->GetPlayerPawn(); - - if (!pPawn) - continue; - - // Do not hide leaders or item holders to other players - ZEPlayer* pOtherZEPlayer = g_playerManager->GetPlayer(j); - if (pSelfZEPlayer->ShouldBlockTransmit(j) && pOtherZEPlayer && !pOtherZEPlayer->IsLeader() && g_pEWHandler->FindItemInstanceByOwner(j, false, 0) == -1) - { - pInfo->m_pTransmitEntity->Clear(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()); - } - } - } - } - - // Don't transmit glow model to it's owner - CBaseModelEntity* pGlowModel = pSelfZEPlayer->GetGlowModel(); - - if (pGlowModel) - pInfo->m_pTransmitEntity->Clear(pGlowModel->entindex()); - } - - return {KHook::Action::Ignore}; -} - -KHook::Return CS2Fixes::Hook_ApplyGameSettings(IServerGameDLL* pThis, KeyValues* pKV) -{ - g_pMapVoteSystem->ApplyGameSettings(pKV); - g_pMapMigrations->ApplyGameSettings(pKV); - - return {KHook::Action::Ignore}; -} - -KHook::Return CS2Fixes::Hook_CreateWorkshopMapGroup(IGameTypes* pThis, const char* name, const CUtlStringList& mapList) -{ - if (g_cvarVoteManagerEnable.Get() && g_pMapVoteSystem->IsMapListLoaded()) - return KHook::Recall(nullptr, {KHook::Action::Ignore}, pThis, name, g_pMapVoteSystem->CreateWorkshopMapGroup()); - - return {KHook::Action::Ignore}; -} - -CConVar g_cvarDropMapWeapons("cs2f_drop_map_weapons", FCVAR_NONE, "Whether to force drop map-spawned weapons on death", false); - -KHook::Return CS2Fixes::Hook_OnTakeDamage_Alive(CCSPlayerPawn* pPawn, CTakeDamageResult* pDamageResult) -{ - if (g_cvarEnableZR.Get() && ZR_Hook_OnTakeDamage_Alive(pDamageResult->m_pOriginatingInfo, pPawn)) - { - pDamageResult->m_bWasDamageSuppressed = true; - pDamageResult->m_flDamageDealt = 0.0f; - return {KHook::Action::Supersede, false}; - } - - // This is a shit place to be doing this, but player_death event is too late and there is no pre-hook alternative - // Check if this is going to kill the player - if (g_cvarDropMapWeapons.Get() && pPawn && pPawn->m_iHealth() <= 0) - { - if (g_cvarEnableEntWatch.Get()) - { - CCSPlayerController* pController = pPawn->GetOriginalController(); - if (pController) - EW_PlayerDeathPre(pController); - } - - pPawn->DropMapWeapons(); - } - - return {KHook::Action::Ignore}; -} - -CConVar g_cvarFixPhysicsPlayerShuffle("cs2f_shuffle_player_physics_sim", FCVAR_NONE, "Whether to enable shuffle player list in physics simulate", false); - -struct TouchLinked_t -{ - uint32_t TouchFlags; - -private: - uint8_t padding_0[20]; - -public: - CBaseHandle SourceHandle; - CBaseHandle TargetHandle; - -private: - uint8_t padding_1[224]; - -public: - [[nodiscard]] bool IsUnTouching() const - { - return !!(TouchFlags & 0x10); - } - - [[nodiscard]] bool IsTouching() const - { - return (!!(TouchFlags & 4)) || (!!(TouchFlags & 8)); - } -}; -static_assert(sizeof(TouchLinked_t) == 256, "Touch_t size mismatch"); -KHook::Return CS2Fixes::Hook_GetTouchingList_Post(CVPhys2World* pThis, CUtlVector* pList, bool unknown) -{ - if (!g_cvarFixPhysicsPlayerShuffle.Get() || pList->Count() <= 1) - return {KHook::Action::Ignore}; - - // [Kxnrl] - // seems it sorted by flags? - - if (GetGlobals()) - std::srand(GetGlobals()->tickcount); - - // Fisher-Yates shuffle - - std::vector touchingLinks; - std::vector unTouchLinks; - - FOR_EACH_VEC(*pList, i) - { - const auto& link = pList->Element(i); - if (link.IsUnTouching()) - unTouchLinks.push_back(link); - else - touchingLinks.push_back(link); - } - - if (touchingLinks.size() <= 1) - return {KHook::Action::Ignore}; - - for (size_t i = touchingLinks.size() - 1; i > 0; --i) - { - const auto j = std::rand() % (i + 1); - std::swap(touchingLinks[i], touchingLinks[j]); - } - - pList->Purge(); - - for (const auto& link : touchingLinks) - pList->AddToTail(link); - for (const auto& link : unTouchLinks) - pList->AddToTail(link); - - return {KHook::Action::Ignore}; -} - -KHook::Return CS2Fixes::Hook_CheckMovingGround(CCSPlayer_MovementServices* pThis, double frametime) -{ - CCSPlayerPawn* pPawn = pThis->GetPawn(); - - if (!pPawn || !GetGlobals()) - return {KHook::Action::Ignore}; - - CCSPlayerController* pController = pPawn->GetOriginalController(); - - if (!pController) - return {KHook::Action::Ignore}; - - int iSlot = pController->GetPlayerSlot(); - - static int aPlayerTicks[MAXPLAYERS] = {0}; - - // The point of doing this is to avoid running the function (and applying/resetting basevelocity) multiple times per tick - // This can happen when the client or server lags - if (aPlayerTicks[iSlot] == GetGlobals()->tickcount) - return {KHook::Action::Supersede}; - - aPlayerTicks[iSlot] = GetGlobals()->tickcount; - - return {KHook::Action::Ignore}; -} - -KHook::Return CS2Fixes::Hook_DropWeapon_Post(CCSPlayer_WeaponServices* pThis, CBasePlayerWeapon* pWeapon, Vector* pVecTarget, Vector* pVelocity) -{ - if (g_cvarEnableEntWatch.Get()) - EW_DropWeapon(pThis, pWeapon); - - return {KHook::Action::Ignore}; -} - -KHook::Return CS2Fixes::Hook_LoadEventsFromFile(IGameEventManager2* pThis, const char* filename, bool bSearchAll) -{ - ExecuteOnce(g_gameEventManager = pThis); - - return {KHook::Action::Ignore}; -} - -KHook::Return CS2Fixes::Hook_SetGameSpawnGroupMgr(INetworkGameServer* pThis, IGameSpawnGroupMgr* pSpawnGroupMgr) -{ - // This also resets our stored pointer on deletion, since null gets passed into this function, nice! - g_pSpawnGroupMgr = (CSpawnGroupMgrGameSystem*)pSpawnGroupMgr; - - return {KHook::Action::Ignore}; -} - -KHook::Return CS2Fixes::Hook_Spawn_Post(CEntitySystem* pThis, int nCount, const EntitySpawnInfo_t* pInfo) -{ - for (int i = 0; i < nCount; i++) - g_pMapMigrations->OnEntitySpawned(pInfo[i].m_pEntity->m_pInstance, pInfo[i].m_pKeyValues); - - return {KHook::Action::Ignore}; -} - -KHook::Return CS2Fixes::Hook_CCSPlayerPawn_Teleport(CCSPlayerPawn* pPawn, const Vector* pPosition, const QAngle* pAngles, const Vector* pVelocity) -{ - if (!pAngles) - return {KHook::Action::Ignore}; - - QAngle* pCastAngles = const_cast(pAngles); - - // Post-AG2, changing x or z angles on a playermodel will bug out, and never did anything pre-AG2 anyways - if (pCastAngles->x != 0.0f) - pCastAngles->x = 0.0f; - - if (pCastAngles->z != 0.0f) - pCastAngles->z = 0.0f; - - return {KHook::Action::Ignore}; -} - void* CS2Fixes::OnMetamodQuery(const char* iface, int* ret) { if (V_strcmp(iface, CS2FIXES_INTERFACE)) diff --git a/src/cs2fixes.h b/src/cs2fixes.h index 643abc2c..68de77d8 100644 --- a/src/cs2fixes.h +++ b/src/cs2fixes.h @@ -37,18 +37,6 @@ #include "version_gen_placeholder.h" #endif -class CCSPlayer_MovementServices; -class CServerSideClient; -struct TouchLinked_t; -class CCSPlayer_WeaponServices; -class CBasePlayerWeapon; -class IGameEventSystem; -class CGamePlayerEquip; -class CCSGameRules; -class CCSPlayerPawn; -class CVPhys2World; -class CTriggerGravity; - extern IGameEventSystem* g_gameEventSystem; extern IGameEventManager2* g_gameEventManager; extern CGameEntitySystem* g_pEntitySystem; @@ -77,37 +65,6 @@ class CS2Fixes : public ISmmPlugin, public IMetamodListener, public ICS2Fixes bool background); void OnLevelShutdown(); -public: // hooks - KHook::Return Hook_GameFrame_Post(IServerGameDLL* pThis, bool simulating, bool bFirstTick, bool bLastTick); - KHook::Return Hook_GameServerSteamAPIActivated(IServerGameDLL* pThis); - KHook::Return Hook_ApplyGameSettings(IServerGameDLL* pThis, KeyValues* pKV); - KHook::Return Hook_ClientActive_Post(IServerGameClients* pThis, CPlayerSlot slot, bool bLoadGame, const char* pszName, uint64 xuid); - KHook::Return Hook_ClientDisconnect_Post(IServerGameClients* pThis, CPlayerSlot slot, ENetworkDisconnectionReason reason, const char* pszName, uint64 xuid, const char* pszNetworkID); - KHook::Return Hook_ClientPutInServer_Post(IServerGameClients* pThis, CPlayerSlot slot, char const* pszName, int type, uint64 xuid); - KHook::Return Hook_ClientSettingsChanged(IServerGameClients* pThis, CPlayerSlot slot); - KHook::Return Hook_OnClientConnected(IServerGameClients* pThis, CPlayerSlot slot, const char* pszName, uint64 xuid, const char* pszNetworkID, const char* pszAddress, bool bFakePlayer); - KHook::Return Hook_ClientConnect(IServerGameClients* pThis, CPlayerSlot slot, const char* pszName, uint64 xuid, const char* pszNetworkID, bool unk1, CBufferString* pRejectReason); - KHook::Return Hook_ClientCommand(IServerGameClients* pThis, CPlayerSlot nSlot, const CCommand& _cmd); - KHook::Return Hook_PostEventAbstract(IGameEventSystem* pThis, CSplitScreenSlot nSlot, bool bLocalOnly, int nClientCount, const uint64* clients, - INetworkMessageInternal* pEvent, const CNetMessage* pData, unsigned long nSize, NetChannelBufType_t bufType); - KHook::Return Hook_StartupServer_Post(INetworkServerService* pThis, const GameSessionConfiguration_t& config, ISource2WorldSession*, const char*); - KHook::Return Hook_CheckTransmit_Post(ISource2GameEntities* pThis, CCheckTransmitInfo** ppInfoList, int infoCount, CBitVec<16384>& unionTransmitEdicts, - CBitVec<16384>&, const Entity2Networkable_t** pNetworkables, const uint16* pEntityIndicies, int nEntities); - KHook::Return Hook_DispatchConCommand(ICvar* pThis, ConCommandRef cmd, const CCommandContext& ctx, const CCommand& args); - KHook::Return Hook_LoadEventsFromFile(IGameEventManager2* pThis, const char* filename, bool bSearchAll); - KHook::Return Hook_Spawn_Post(CEntitySystem* pThis, int nCount, const EntitySpawnInfo_t* pInfo); - KHook::Return Hook_SetGameSpawnGroupMgr(INetworkGameServer* pThis, IGameSpawnGroupMgr* pSpawnGroupMgr); - KHook::Return Hook_CreateWorkshopMapGroup(IGameTypes* pThis, const char* name, const CUtlStringList& mapList); - KHook::Return Hook_GetTouchingList_Post(CVPhys2World* pThis, CUtlVector* pList, bool unknown); - KHook::Return Hook_CheckMovingGround(CCSPlayer_MovementServices* pThis, double frametime); - KHook::Return Hook_DropWeapon_Post(CCSPlayer_WeaponServices* pThis, CBasePlayerWeapon* pWeapon, Vector* pVecTarget, Vector* pVelocity); - KHook::Return Hook_PlayerEquipUse(CGamePlayerEquip* pThis, class InputData_t*); - KHook::Return Hook_PlayerEquipPrecache_Post(CGamePlayerEquip* pThis, CEntityPrecacheContext*); - KHook::Return Hook_TriggerGravityPrecache_Post(CTriggerGravity* pThis, CEntityPrecacheContext* param); - KHook::Return Hook_TriggerGravityEndTouch_Post(CTriggerGravity* pThis, CBaseEntity* pOther); - KHook::Return Hook_OnTakeDamage_Alive(CCSPlayerPawn* pPawn, CTakeDamageResult* pDamageResult); - KHook::Return Hook_CCSPlayerPawn_Teleport(CCSPlayerPawn* pPawn, const Vector* pPosition, const QAngle* pAngles, const Vector* pVelocity); - public: // MetaMod API void* OnMetamodQuery(const char* iface, int* ret); std::uint64_t GetAdminFlags(std::uint64_t iSteam64ID) const override; diff --git a/src/detours.cpp b/src/detours.cpp deleted file mode 100644 index a93beac3..00000000 --- a/src/detours.cpp +++ /dev/null @@ -1,923 +0,0 @@ -/** - * ============================================================================= - * 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 "cs_usercmd.pb.h" -#include "networkbasetypes.pb.h" -#include "usercmd.pb.h" - -#include "addresses.h" -#include "buttonwatch.h" -#include "commands.h" -#include "common.h" -#include "ctimer.h" -#include "customio.h" -#include "detours.h" -#include "entities.h" -#include "entity/cbasemodelentity.h" -#include "entity/ccsplayercontroller.h" -#include "entity/ccsplayerpawn.h" -#include "entity/ccsweaponbase.h" -#include "entity/cenvhudhint.h" -#include "entity/cgamerules.h" -#include "entity/cpointviewcontrol.h" -#include "entity/ctakedamageinfo.h" -#include "entity/ctriggerpush.h" -#include "entity/services.h" -#include "entwatch.h" -#include "gameconfig.h" -#include "igameevents.h" -#include "irecipientfilter.h" -#include "map_votes.h" -#include "mapmigrations.h" -#include "module.h" -#include "networksystem/inetworkserializer.h" -#include "playermanager.h" -#include "serversideclient.h" -#include "tier0/vprof.h" -#include "votemanager.h" -#include "zombiereborn.h" - -#include "tier0/memdbgon.h" - -KHook::Member takeDamageOldHook(Detour_CBaseEntity_TakeDamageOld, Detour_CBaseEntity_TakeDamageOld_Post); -KHook::Member triggerPushTouchHook(Detour_TriggerPush_Touch, nullptr); -KHook::Function isHearingClientHook(Detour_IsHearingClient, nullptr); -KHook::Function sayTextFilterHook(Detour_UTIL_SayTextFilter, nullptr); -KHook::Function sayText2FilterHook(Detour_UTIL_SayText2Filter, nullptr); -KHook::Member canUseHook(Detour_CCSPlayer_WeaponServices_CanUse, nullptr); -KHook::Member equipWeaponHook(Detour_CCSPlayer_WeaponServices_EquipWeapon, nullptr); -KHook::Member acceptInputHook(Detour_CEntityIdentity_AcceptInput, nullptr); -KHook::Member getNearestNavAreaHook(Detour_CNavMesh_GetNearestNavArea, nullptr); -KHook::Member processMovementHook(Detour_ProcessMovement, Detour_ProcessMovement_Post); -KHook::Member processUsercmdsHook(Detour_ProcessUsercmds, nullptr); -KHook::Member inputTriggerForAllPlayersHook(Detour_CGamePlayerEquip_InputTriggerForAllPlayers, nullptr); -KHook::Member inputTriggerForActivatedPlayerHook(Detour_CGamePlayerEquip_InputTriggerForActivatedPlayer, nullptr); -KHook::Member gravityTouchHook(Detour_CTriggerGravity_GravityTouch, nullptr); -KHook::Function getFreeClientHook(Detour_GetFreeClient, nullptr); -KHook::Member getMaxSpeedHook(Detour_CCSPlayerPawn_GetMaxSpeed, nullptr); -KHook::Member findUseEntityHook(Detour_FindUseEntity, Detour_FindUseEntity_Post); -KHook::Function traceFuncHook(Detour_TraceFunc, nullptr); -KHook::Function traceShapeHook(Detour_TraceShape, nullptr); -KHook::Member fireOutputInternalHook(Detour_CEntityIOOutput_FireOutputInternal, nullptr); -#ifdef PLATFORM_WINDOWS -KHook::Member getEyePositionHook(Detour_CBasePlayerPawn_GetEyePosition, nullptr); -KHook::Member getEyeAnglesHook(Detour_CBasePlayerPawn_GetEyeAngles, nullptr); -#else -KHook::Member getEyePositionHook(Detour_CBasePlayerPawn_GetEyePosition, nullptr); -KHook::Member getEyeAnglesHook(Detour_CBasePlayerPawn_GetEyeAngles, nullptr); -#endif -KHook::Member inputTestActivatorHook(Detour_CBaseFilter_InputTestActivator, nullptr); -KHook::Function checkSteamBanHook(nullptr, Detour_GameSystem_Think_CheckSteamBan_Post); -KHook::Member canAcquireHook(Detour_CCSPlayer_ItemServices_CanAcquire, nullptr); -KHook::Function scriptSetModelHook(Detour_CS_Script_SetModel, Detour_CS_Script_SetModel_Post); -KHook::Member setModelHook(Detour_CBaseModelEntity_SetModel, nullptr); -KHook::Member goToIntermissionHook(Detour_CCSGameRules_GoToIntermission, nullptr); - -template -void SetupDetour(CGameConfig* gameConfig, KHook::Function& hook, const char* name) -{ - auto pfnFunc = reinterpret_cast(gameConfig->ResolveSignature(name)); - - if (!pfnFunc) - { - g_bRequiredInitLoaded = false; - return; - } - - hook.Configure(pfnFunc); - Message("Detoured %s at 0x%p\n", name, pfnFunc); -} - -template -void SetupDetour(CGameConfig* gameConfig, KHook::Member& hook, const char* name) -{ - void* pfnFunc = gameConfig->ResolveSignature(name); - - if (!pfnFunc) - { - g_bRequiredInitLoaded = false; - return; - } - - hook.Configure(pfnFunc); - Message("Detoured %s at 0x%p\n", name, pfnFunc); -} - -void InitDetours(CGameConfig* gameConfig) -{ - SetupDetour(gameConfig, takeDamageOldHook, "CBaseEntity_TakeDamageOld"); - SetupDetour(gameConfig, triggerPushTouchHook, "TriggerPush_Touch"); - SetupDetour(gameConfig, isHearingClientHook, "IsHearingClient"); - SetupDetour(gameConfig, sayTextFilterHook, "UTIL_SayTextFilter"); - SetupDetour(gameConfig, sayText2FilterHook, "UTIL_SayText2Filter"); - SetupDetour(gameConfig, canUseHook, "CCSPlayer_WeaponServices_CanUse"); - SetupDetour(gameConfig, equipWeaponHook, "CCSPlayer_WeaponServices_EquipWeapon"); - SetupDetour(gameConfig, acceptInputHook, "CEntityIdentity_AcceptInput"); - SetupDetour(gameConfig, getNearestNavAreaHook, "CNavMesh_GetNearestNavArea"); - SetupDetour(gameConfig, processMovementHook, "ProcessMovement"); - SetupDetour(gameConfig, processUsercmdsHook, "ProcessUsercmds"); - SetupDetour(gameConfig, inputTriggerForAllPlayersHook, "CGamePlayerEquip_InputTriggerForAllPlayers"); - SetupDetour(gameConfig, inputTriggerForActivatedPlayerHook, "CGamePlayerEquip_InputTriggerForActivatedPlayer"); - SetupDetour(gameConfig, gravityTouchHook, "CTriggerGravity_GravityTouch"); - SetupDetour(gameConfig, getFreeClientHook, "GetFreeClient"); -#ifdef __linux__ - // Inlined by MSVC as of 2025-07-28 CS2 update - // TODO: Find some alternative that supports Windows - SetupDetour(gameConfig, getMaxSpeedHook, "CCSPlayerPawn_GetMaxSpeed"); -#endif - SetupDetour(gameConfig, findUseEntityHook, "FindUseEntity"); - SetupDetour(gameConfig, traceFuncHook, "TraceFunc"); - SetupDetour(gameConfig, traceShapeHook, "TraceShape"); - SetupDetour(gameConfig, fireOutputInternalHook, "CEntityIOOutput_FireOutputInternal"); - SetupDetour(gameConfig, getEyePositionHook, "CBasePlayerPawn_GetEyePosition"); - SetupDetour(gameConfig, getEyeAnglesHook, "CBasePlayerPawn_GetEyeAngles"); - SetupDetour(gameConfig, inputTestActivatorHook, "CBaseFilter_InputTestActivator"); - SetupDetour(gameConfig, checkSteamBanHook, "GameSystem_Think_CheckSteamBan"); - SetupDetour(gameConfig, canAcquireHook, "CCSPlayer_ItemServices_CanAcquire"); - SetupDetour(gameConfig, scriptSetModelHook, "CS_Script_SetModel"); - SetupDetour(gameConfig, setModelHook, "CBaseModelEntity_SetModel"); - SetupDetour(gameConfig, goToIntermissionHook, "CCSGameRules_GoToIntermission"); -} - -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); -CConVar g_cvarFixBlockDamage("cs2f_fix_block_dmg", FCVAR_NONE, "Whether to fix block-damage on players", false); - -KHook::Return Detour_CBaseEntity_TakeDamageOld(CBaseEntity* pThis, CTakeDamageInfo* pInfo, CTakeDamageResult* pResult) -{ - // NOTE valve always return 1 here, since 2025/10/15 update. - -#ifdef _DEBUG - Message("\n--------------------------------\n" - "TakeDamage on %s\n" - "Attacker: %s\n" - "Inflictor: %s\n" - "Ability: %s\n" - "Damage: %.2f\n" - "Damage Type: %i\n" - "--------------------------------\n", - pThis->GetClassname(), - pInfo->m_hAttacker.Get() ? pInfo->m_hAttacker.Get()->GetClassname() : "NULL", - pInfo->m_hInflictor.Get() ? pInfo->m_hInflictor.Get()->GetClassname() : "NULL", - pInfo->m_hAbility.Get() ? pInfo->m_hAbility.Get()->GetClassname() : "NULL", - pInfo->m_flDamage, - pInfo->m_bitsDamageType); -#endif - - // Block all player damage if desired - if (g_cvarBlockAllDamage.Get() && pThis->IsPawn()) - return {KHook::Action::Supersede, 1}; - - CEntityInstance* pInflictor = pInfo->m_hInflictor.Get(); - const char* pszInflictorClass = pInflictor ? pInflictor->GetClassname() : ""; - - // After Armory update, activator became attacker on block damage, which broke it.. - if (g_cvarFixBlockDamage.Get() && pInfo->m_AttackerInfo.m_bIsPawn && pInfo->m_bitsDamageType ^ DMG_BULLET && pInfo->m_hAttacker != pThis->GetHandle()) - { - if (V_strcasecmp(pszInflictorClass, "func_movelinear") == 0 - || V_strcasecmp(pszInflictorClass, "func_mover") == 0 - || V_strcasecmp(pszInflictorClass, "func_door") == 0 - || V_strcasecmp(pszInflictorClass, "func_door_rotating") == 0 - || V_strcasecmp(pszInflictorClass, "func_rotating") == 0 - || V_strcasecmp(pszInflictorClass, "point_hurt") == 0) - { - pInfo->m_AttackerInfo.m_bIsPawn = false; - pInfo->m_AttackerInfo.m_bIsWorld = true; - pInfo->m_hAttacker = pInfo->m_hInflictor; - - pInfo->m_AttackerInfo.m_hAttackerPawn = CHandle(~0u); - pInfo->m_AttackerInfo.m_nAttackerPlayerSlot = ~0; - } - } - - // Prevent molly on self - if (g_cvarBlockMolotovSelfDmg.Get() && pInfo->m_hAttacker == pThis && !V_strncmp(pszInflictorClass, "inferno", 7)) - return {KHook::Action::Supersede, 1}; - - // Fix disconnected players grenades being able to damage teammates - if (!V_strcasecmp(pszInflictorClass, "hegrenade_projectile") && pInfo->m_AttackerInfo.m_bIsPawn && pInfo->m_AttackerInfo.m_nTeam == 0) - return {KHook::Action::Supersede, 1}; - - // maybe call in flow - CTakeDamageResult damageResult(0); - - if (pResult == nullptr) - { - damageResult.CopyFrom(pInfo); - return KHook::Recall(nullptr, {KHook::Action::Ignore}, pThis, pInfo, &damageResult); - } - - return {KHook::Action::Ignore}; -} - -KHook::Return Detour_CBaseEntity_TakeDamageOld_Post(CBaseEntity* pThis, CTakeDamageInfo* pInfo, CTakeDamageResult* pResult) -{ - if (pResult->m_flDamageDealt > 0.0f && !pResult->m_bWasDamageSuppressed && g_cvarEnableZR.Get() && pThis->IsPawn()) - ZR_OnPlayerTakeDamage(reinterpret_cast(pThis), pInfo, pResult->m_flDamageDealt); - - return {KHook::Action::Ignore}; -} - -CConVar g_cvarUseOldPush("cs2f_use_old_push", FCVAR_NONE, "Whether to use the old CSGO trigger_push behavior", false); -CConVar g_cvarLogPushes("cs2f_log_pushes", FCVAR_NONE, "Whether to log pushes (cs2f_use_old_push must be enabled)", false); - -KHook::Return Detour_TriggerPush_Touch(CTriggerPush* pPush, CBaseEntity* pOther) -{ - // This trigger pushes only once (and kills itself) or pushes only on StartTouch, both of which are fine already - if (!g_cvarUseOldPush.Get() || pPush->m_spawnflags() & SF_TRIG_PUSH_ONCE || pPush->m_bTriggerOnStartTouch()) - return {KHook::Action::Ignore}; - - MoveType_t movetype = pOther->m_nActualMoveType(); - - // VPhysics handling doesn't need any changes - if (movetype == MOVETYPE_VPHYSICS) - return {KHook::Action::Ignore}; - - if (movetype == MOVETYPE_NONE || movetype == MOVETYPE_PUSH || movetype == MOVETYPE_NOCLIP) - return {KHook::Action::Supersede}; - - CCollisionProperty* collisionProp = pOther->m_pCollision(); - if (!IsSolid(collisionProp->m_nSolidType(), collisionProp->m_usSolidFlags())) - return {KHook::Action::Supersede}; - - if (!pPush->PassesTriggerFilters(pOther)) - return {KHook::Action::Supersede}; - - if (pOther->m_CBodyComponent()->m_pSceneNode()->m_pParent()) - return {KHook::Action::Supersede}; - - Vector vecAbsDir; - matrix3x4_t matTransform = pPush->m_CBodyComponent()->m_pSceneNode()->EntityToWorldTransform(); - - Vector vecPushDir = pPush->m_vecPushDirEntitySpace(); - VectorRotate(vecPushDir, matTransform, vecAbsDir); - - Vector vecPush = vecAbsDir * pPush->m_flSpeed(); - - uint32 flags = pOther->m_fFlags(); - - if (flags & (1 << 23)) // TODO: is FL_BASEVELOCITY really gone? - vecPush = vecPush + pOther->m_vecBaseVelocity(); - - if (vecPush.z > 0 && (flags & FL_ONGROUND)) - { - pOther->SetGroundEntity(nullptr); - Vector origin = pOther->GetAbsOrigin(); - origin.z += 1.0f; - - pOther->Teleport(&origin, nullptr, nullptr); - } - - if (g_cvarLogPushes.Get() && GetGlobals()) - { - Vector vecEntBaseVelocity = pOther->m_vecBaseVelocity; - Vector vecOrigPush = vecAbsDir * pPush->m_flSpeed(); - - Message("Pushing entity %i | frame = %i | tick = %i | entity basevelocity %s = %.2f %.2f %.2f | original push velocity = %.2f %.2f %.2f | final push velocity = %.2f %.2f %.2f\n", - pOther->GetEntityIndex(), - GetGlobals()->framecount, - GetGlobals()->tickcount, - (flags & (1 << 23)) ? "WITH FLAG" : "", - vecEntBaseVelocity.x, vecEntBaseVelocity.y, vecEntBaseVelocity.z, - vecOrigPush.x, vecOrigPush.y, vecOrigPush.z, - vecPush.x, vecPush.y, vecPush.z); - } - - pOther->m_vecBaseVelocity(vecPush); - - flags |= (1 << 23); // TODO: is FL_BASEVELOCITY really gone? - pOther->m_fFlags(flags); - - return {KHook::Action::Supersede}; -} - -KHook::Return Detour_IsHearingClient(void* serverClient, int index) -{ - ZEPlayer* player = g_playerManager->GetPlayer(index); - if (player && player->IsMuted()) - return {KHook::Action::Supersede, false}; - - return {KHook::Action::Ignore}; -} - -KHook::Return SayChatMessageWithTimer(IRecipientFilter& filter, const char* pText, CCSPlayerController* pPlayer, uint64 eMessageType) -{ - VPROF("SayChatMessageWithTimer"); - - char buf[256]; - - // Filter console message - remove non-alphanumeric chars and convert to lowercase - uint32 uiTextLength = strlen(pText); - uint32 uiFilteredTextLength = 0; - char filteredText[256]; - - for (uint32 i = 0; i < uiTextLength; i++) - { - if (pText[i] >= 'A' && pText[i] <= 'Z') - filteredText[uiFilteredTextLength++] = pText[i] + 32; - if (pText[i] == ' ' || (pText[i] >= '0' && pText[i] <= '9') || (pText[i] >= 'a' && pText[i] <= 'z')) - filteredText[uiFilteredTextLength++] = pText[i]; - } - filteredText[uiFilteredTextLength] = '\0'; - - // Split console message into words seperated by the space character - CSplitString words(filteredText, " "); - - // Word count includes the first word "Console:" at index 0, first relevant word is at index 1 - int iWordCount = words.Count(); - uint32 uiTriggerTimerLength = 0; - - if (iWordCount == 2) - uiTriggerTimerLength = V_StringToUint32(words.Element(1), 0, NULL, NULL, PARSING_FLAG_SKIP_WARNING); - - for (int i = 1; i < iWordCount && uiTriggerTimerLength == 0; i++) - { - uint32 uiCurrentValue = V_StringToUint32(words.Element(i), 0, NULL, NULL, PARSING_FLAG_SKIP_WARNING); - uint32 uiNextWordLength = 0; - char* pNextWord = NULL; - - if (i + 1 < iWordCount) - { - pNextWord = words.Element(i + 1); - uiNextWordLength = strlen(pNextWord); - } - - // Case: ... X sec(onds) ... or ... X s ... or ... X min(utes) ... - if (pNextWord != NULL && uiCurrentValue > 0) - { - if (uiNextWordLength == 1) - { - if (pNextWord[0] == 's') - uiTriggerTimerLength = uiCurrentValue; - } - else if (uiNextWordLength > 2) - { - if (pNextWord[0] == 's' && pNextWord[1] == 'e' && pNextWord[2] == 'c') - uiTriggerTimerLength = uiCurrentValue; - if (pNextWord[0] == 'm' && pNextWord[1] == 'i' && pNextWord[2] == 'n') - uiTriggerTimerLength = uiCurrentValue * 60; - } - } - - // Case: ... Xs - only support up to 3 digit numbers (in seconds) for this timer parse method - if (uiCurrentValue == 0) - { - char* pCurrentWord = words.Element(i); - uint32 uiCurrentScanLength = MIN(strlen(pCurrentWord), 4); - - for (uint32 j = 0; j < uiCurrentScanLength; j++) - { - if (pCurrentWord[j] >= '0' && pCurrentWord[j] <= '9') - continue; - - if (pCurrentWord[j] == 's') - { - pCurrentWord[j] = '\0'; - uiTriggerTimerLength = V_StringToUint32(pCurrentWord, 0, NULL, NULL, PARSING_FLAG_SKIP_WARNING); - } - break; - } - } - } - - float fCurrentRoundClock = g_pGameRules->m_iRoundTime - (GetGlobals()->curtime - g_pGameRules->m_fRoundStartTime.Get().GetTime()); - - // Only display trigger time if the timer is greater than 4 seconds, and time expires within the round - if ((uiTriggerTimerLength > 4) && (fCurrentRoundClock > uiTriggerTimerLength)) - { - int iTriggerTime = fCurrentRoundClock - uiTriggerTimerLength; - - // Round timer to nearest whole second - if ((int)(fCurrentRoundClock - 0.5f) == (int)fCurrentRoundClock) - iTriggerTime++; - - int mins = iTriggerTime / 60; - int secs = iTriggerTime % 60; - - V_snprintf(buf, sizeof(buf), "%s %s %s %2d:%02d", " \7CONSOLE:\4", pText + sizeof("Console:"), "\x10- @", mins, secs); - } - else - V_snprintf(buf, sizeof(buf), "%s %s", " \7CONSOLE:\4", pText + sizeof("Console:")); - - return KHook::Recall(nullptr, {KHook::Action::Ignore}, filter, buf, pPlayer, eMessageType); -} - -CConVar g_cvarEnableTriggerTimer("cs2f_trigger_timer_enable", FCVAR_NONE, "Whether to process countdown messages said by Console (e.g. Hold for 10 seconds) and append the round time where the countdown resolves", false); - -KHook::Return Detour_UTIL_SayTextFilter(IRecipientFilter& filter, const char* pText, CCSPlayerController* pPlayer, uint64 eMessageType) -{ - if (pPlayer) - return {KHook::Action::Ignore}; - - if (g_cvarEnableTriggerTimer.Get() && GetGlobals() && g_pGameRules) - return SayChatMessageWithTimer(filter, pText, pPlayer, eMessageType); - - char buf[256]; - V_snprintf(buf, sizeof(buf), "%s %s", " \7CONSOLE:\4", pText + sizeof("Console:")); - - return KHook::Recall(nullptr, {KHook::Action::Ignore}, filter, buf, pPlayer, eMessageType); -} - -KHook::Return Detour_UTIL_SayText2Filter( - IRecipientFilter& filter, - CCSPlayerController* pEntity, - uint64 eMessageType, - const char* msg_name, - const char* param1, - const char* param2, - const char* param3, - const char* param4) -{ -#ifdef _DEBUG - CPlayerSlot slot = filter.GetRecipientIndex(0); - CCSPlayerController* target = CCSPlayerController::FromSlot(slot); - - if (target) - Message("Chat from %s to %s: %s\n", param1, target->GetPlayerName().c_str(), param2); -#endif - - return KHook::Recall(nullptr, {KHook::Action::Ignore}, filter, pEntity, eMessageType, msg_name, pEntity->GetPlayerName().c_str(), param2, param3, param4); -} - -KHook::Return Detour_CCSPlayer_WeaponServices_CanUse(CCSPlayer_WeaponServices* pWeaponServices, CBasePlayerWeapon* pPlayerWeapon) -{ - if (g_cvarEnableEntWatch.Get() && !EW_Detour_CCSPlayer_WeaponServices_CanUse(pWeaponServices, pPlayerWeapon)) - return {KHook::Action::Supersede, false}; - - return {KHook::Action::Ignore}; -} - -KHook::Return Detour_CCSPlayer_WeaponServices_EquipWeapon(CCSPlayer_WeaponServices* pWeaponServices, CBasePlayerWeapon* pPlayerWeapon) -{ - if (g_cvarEnableEntWatch.Get()) - EW_Detour_CCSPlayer_WeaponServices_EquipWeapon(pWeaponServices, pPlayerWeapon); - - g_pMapMigrations->OnEquipWeapon(pPlayerWeapon); - - return {KHook::Action::Ignore}; -} - -CConVar g_cvarDisableSetModel("cs2f_disable_setmodel", FCVAR_NONE, "Whether to disable SetModel usage from maps (custom input, cs_script function)", false); - -bool PrepareMapSetModel(CBaseModelEntity* pModel) -{ - if (!pModel->IsPawn()) - return true; - - if (g_cvarDisableSetModel.Get() || g_pMapMigrations->Migrations20260420Enabled()) - return false; - - // Player color may have been changed by zclass/server customization, so reset it first - // This also means if maps want to change player color, it needs to be done after the SetModel call - int originalAlpha = pModel->m_clrRender().a(); - pModel->m_clrRender = Color(255, 255, 255, originalAlpha); - - return true; -} - -KHook::Return Detour_CEntityIdentity_AcceptInput(CEntityIdentity* pThis, CUtlSymbolLarge* pInputName, CEntityInstance* pActivator, CEntityInstance* pCaller, variant_t* value, int nOutputID, void* a7, void* a8) -{ - VPROF_SCOPE_BEGIN("Detour_CEntityIdentity_AcceptInput"); - - if (g_cvarEnableZR.Get()) - { - bool result = ZR_Detour_CEntityIdentity_AcceptInput(pThis, pInputName, pActivator, pCaller, value, nOutputID); - - if (!result) - return {KHook::Action::Supersede, result}; - } - - // Handle KeyValue(s) - if (!V_strnicmp(pInputName->String(), "KeyValue", 8)) - { - if ((value->m_type == FIELD_CSTRING || value->m_type == FIELD_STRING) && value->m_pszString) - { - // always const char*, even if it's FIELD_STRING (that is bug string from lua 'EntFire') - return {KHook::Action::Supersede, CustomIO_HandleInput(pThis->m_pInstance, value->m_pszString, pActivator, pCaller)}; - } - Message("Invalid value type for input %s\n", pInputName->String()); - return {KHook::Action::Supersede, false}; - } - - if (!V_strnicmp(pInputName->String(), "IgniteL", 7)) // Override IgniteLifetime - { - float flDuration = 0.f; - - if ((value->m_type == FIELD_CSTRING || value->m_type == FIELD_STRING) && value->m_pszString) - flDuration = V_StringToFloat32(value->m_pszString, 0.f); - else - flDuration = value->m_float32; - - CCSPlayerPawn* pPawn = reinterpret_cast(pThis->m_pInstance); - - if (pPawn->IsPawn() && IgnitePawn(pPawn, flDuration, pPawn, pPawn)) - return {KHook::Action::Supersede, true}; - } - else if (!V_strnicmp(pInputName->String(), "AddScore", 8)) - { - int iScore = 0; - - if ((value->m_type == FIELD_CSTRING || value->m_type == FIELD_STRING) && value->m_pszString) - iScore = V_StringToInt32(value->m_pszString, 0); - else - iScore = value->m_int32; - - CCSPlayerPawn* pPawn = reinterpret_cast(pThis->m_pInstance); - - if (pPawn->IsPawn() && pPawn->GetOriginalController()) - { - pPawn->GetOriginalController()->AddScore(iScore); - return {KHook::Action::Supersede, true}; - } - } - else if (!V_strcasecmp(pInputName->String(), "SetMessage")) - { - if (const auto pHudHint = reinterpret_cast(pThis->m_pInstance)->AsHudHint()) - { - if ((value->m_type == FIELD_CSTRING || value->m_type == FIELD_STRING) && value->m_pszString) - pHudHint->m_iszMessage(GameEntitySystem()->AllocPooledString(value->m_pszString)); - return {KHook::Action::Supersede, true}; - } - } - else if (!V_strcasecmp(pInputName->String(), "SetModel")) - { - if (const auto pModelEntity = reinterpret_cast(pThis->m_pInstance)->AsBaseModelEntity()) - { - if ((value->m_type == FIELD_CSTRING || value->m_type == FIELD_STRING) && value->m_pszString && PrepareMapSetModel(pModelEntity)) - pModelEntity->SetModel(value->m_pszString); - - return {KHook::Action::Supersede, true}; - } - } - else if (const auto pGameUI = reinterpret_cast(pThis->m_pInstance)->AsGameUI()) - { - if (!V_strcasecmp(pInputName->String(), "Activate")) - return {KHook::Action::Supersede, CGameUIHandler::OnActivate(pGameUI, reinterpret_cast(pActivator))}; - if (!V_strcasecmp(pInputName->String(), "Deactivate")) - return {KHook::Action::Supersede, CGameUIHandler::OnDeactivate(pGameUI, reinterpret_cast(pActivator))}; - } - else if (const auto pViewControl = reinterpret_cast(pThis->m_pInstance)->AsPointViewControl()) - { - if (!V_strcasecmp(pInputName->String(), "EnableCamera")) - return {KHook::Action::Supersede, CPointViewControlHandler::OnEnable(pViewControl, reinterpret_cast(pActivator))}; - if (!V_strcasecmp(pInputName->String(), "DisableCamera")) - return {KHook::Action::Supersede, CPointViewControlHandler::OnDisable(pViewControl, reinterpret_cast(pActivator))}; - if (!V_strcasecmp(pInputName->String(), "EnableCameraAll")) - return {KHook::Action::Supersede, CPointViewControlHandler::OnEnableAll(pViewControl)}; - if (!V_strcasecmp(pInputName->String(), "DisableCameraAll")) - return {KHook::Action::Supersede, CPointViewControlHandler::OnDisableAll(pViewControl)}; - } - - VPROF_SCOPE_END(); - - return {KHook::Action::Ignore}; -} - -CConVar g_cvarBlockNavLookup("cs2f_block_nav_lookup", FCVAR_NONE, "Whether to block navigation mesh lookup, improves server performance but breaks bot navigation", false); - -KHook::Return Detour_CNavMesh_GetNearestNavArea(CNavMesh* pNavMesh, float* unk2, unsigned int* unk3, unsigned int unk4, int64_t unk5, float unk6, int64_t unk7) -{ - if (g_cvarBlockNavLookup.Get()) - return {KHook::Action::Supersede, nullptr}; - - return {KHook::Action::Ignore}; -} - -float g_flStoreFrametime = 0.0f; - -KHook::Return Detour_ProcessMovement(CCSPlayer_MovementServices* pThis, void* pMove) -{ - CCSPlayerPawn* pPawn = pThis->GetPawn(); - - if (!pPawn->IsAlive() || !GetGlobals()) - return {KHook::Action::Ignore}; - - CCSPlayerController* pController = pPawn->GetOriginalController(); - - if (!pController || !pController->IsConnected()) - return {KHook::Action::Ignore}; - - float flSpeedMod = pController->GetZEPlayer()->GetSpeedMod(); - - if (flSpeedMod == 1.f) - return {KHook::Action::Ignore}; - - // Yes, this is what source1 does to scale player speed - // Scale frametime during the entire movement processing step and revert right after - g_flStoreFrametime = GetGlobals()->frametime; - GetGlobals()->frametime *= flSpeedMod; - - return {KHook::Action::Ignore}; -} - -KHook::Return Detour_ProcessMovement_Post(CCSPlayer_MovementServices* pThis, void* pMove) -{ - GetGlobals()->frametime = g_flStoreFrametime; - return {KHook::Action::Ignore}; -} - -CConVar g_cvarDisableSubtickMovement("cs2f_disable_subtick_move", FCVAR_NONE, "Whether to disable subtick movement", false); -CConVar g_cvarDisableSubtickShooting("cs2f_disable_subtick_shooting", FCVAR_NONE, "Whether to disable subtick shooting, experimental (WARNING: add \"log_flags Shooting + DoNotEcho\" to your cfg to prevent console spam on every shot fired)", false); - -class CUserCmd -{ -public: - [[maybe_unused]] char pad0[0x10]; - CSGOUserCmdPB cmd; - [[maybe_unused]] char pad1[0x38]; -#ifdef PLATFORM_WINDOWS - [[maybe_unused]] char pad2[0x8]; -#endif -}; - -KHook::Return Detour_ProcessUsercmds(CCSPlayerController* pController, CUserCmd* cmds, int numcmds, bool paused, float margin) -{ - VPROF_SCOPE_BEGIN("Detour_ProcessUsercmds"); - - for (int i = 0; i < numcmds; i++) - { - // Push fix only works properly if subtick movement is also disabled - if (g_cvarDisableSubtickMovement.Get() || g_cvarUseOldPush.Get()) - { - auto subtickMoves = cmds[i].cmd.mutable_base()->mutable_subtick_moves(); - auto iterator = subtickMoves->begin(); - - while (iterator != subtickMoves->end()) - { - uint64 button = iterator->button(); - - // Remove normal subtick movement inputs by button - // Unfortunately, we also need to ignore IN_JUMP, because de-subticking jumps somehow conflicts with other subtick inputs pressed at the same time - if (button >= IN_DUCK && button <= IN_MOVERIGHT && button != IN_USE) - { - subtickMoves->erase(iterator); - } - else - { - // Remove subtick movement viewangles by pitch/yaw - if (iterator->pitch_delta() != 0.0f) - iterator->set_pitch_delta(0.0f); - - if (iterator->yaw_delta() != 0.0f) - iterator->set_yaw_delta(0.0f); - - iterator++; - } - } - } - - if (g_cvarDisableSubtickShooting.Get()) - { - cmds[i].cmd.set_attack1_start_history_index(-1); - cmds[i].cmd.set_attack2_start_history_index(-1); - cmds[i].cmd.mutable_input_history()->Clear(); - } - } - - VPROF_SCOPE_END(); - - return {KHook::Action::Ignore}; -} - -KHook::Return Detour_CGamePlayerEquip_InputTriggerForAllPlayers(CGamePlayerEquip* pEntity, InputData_t* pInput) -{ - CGamePlayerEquipHandler::TriggerForAllPlayers(pEntity, pInput); - return {KHook::Action::Ignore}; -} -KHook::Return Detour_CGamePlayerEquip_InputTriggerForActivatedPlayer(CGamePlayerEquip* pEntity, InputData_t* pInput) -{ - if (CGamePlayerEquipHandler::TriggerForActivatedPlayer(pEntity, pInput)) - return {KHook::Action::Ignore}; - - return {KHook::Action::Supersede}; -} - -KHook::Return Detour_CTriggerGravity_GravityTouch(CTriggerGravity* pEntity, CBaseEntity* pOther) -{ - // no need to call original function here - // because original function calls CBaseEntity::SetGravityScale internal - // but passes the wrong gravity scale value - if (CTriggerGravityHandler::GravityTouching(pEntity, pOther)) - return {KHook::Action::Supersede}; - - return {KHook::Action::Ignore}; -} - -KHook::Return Detour_GetFreeClient(int64_t unk1, const __m128i* unk2, unsigned int unk3, int64_t unk4, char unk5, void* unk6) -{ - // Not sure if this function can even be called in this state, but if it is, we can't do shit anyways - if (!GetClientList() || !GetGlobals()) - return {KHook::Action::Supersede, nullptr}; - - // Check if there is still unused slots, this should never break so just fall back to original behaviour for ease (we don't have a CServerSideClient constructor) - if (GetGlobals()->maxClients != GetClientList()->Count()) - return {KHook::Action::Ignore}; - - // Phantom client fix - for (int i = 0; i < GetClientList()->Count(); i++) - { - CServerSideClient* pClient = (*GetClientList())[i]; - - if (pClient && pClient->GetSignonState() < SIGNONSTATE_CONNECTED) - return {KHook::Action::Supersede, pClient}; - } - - // Server is actually full for real - return {KHook::Action::Supersede, nullptr}; -} - -KHook::Return Detour_CCSPlayerPawn_GetMaxSpeed(CCSPlayerPawn* pPawn) -{ - auto flMaxSpeed = getMaxSpeedHook.CallOriginal(pPawn); - - const auto pController = reinterpret_cast(pPawn->GetController()); - if (const auto pPlayer = pController != nullptr ? pController->GetZEPlayer() : nullptr) - flMaxSpeed *= pPlayer->GetMaxSpeed(); - - return {KHook::Action::Supersede, flMaxSpeed}; -} - -CConVar g_cvarPreventUsingPlayers("cs2f_prevent_using_players", FCVAR_NONE, "Whether to prevent +use from hitting players (0=can use players, 1=cannot use players)", false); -bool g_bFindingUseEntity = false; - -KHook::Return Detour_FindUseEntity(CCSPlayer_UseServices* pThis, float a2) -{ - g_bFindingUseEntity = true; - return {KHook::Action::Ignore}; -} - -KHook::Return Detour_FindUseEntity_Post(CCSPlayer_UseServices* pThis, float a2) -{ - g_bFindingUseEntity = false; - return {KHook::Action::Ignore}; -} - -KHook::Return Detour_TraceFunc(int64* a1, int* a2, float* a3, uint64 traceMask) -{ - if (g_cvarPreventUsingPlayers.Get() && g_bFindingUseEntity) - { - uint64 newMask = traceMask & (~(CONTENTS_PLAYER & CONTENTS_NPC)); - KHook::Recall(nullptr, {KHook::Action::Ignore}, a1, a2, a3, newMask); - } - - return {KHook::Action::Ignore}; -} - -KHook::Return Detour_TraceShape(int64* a1, int64 a2, int64 a3, int64 a4, CTraceFilter* filter, int64 a6) -{ - if (g_cvarPreventUsingPlayers.Get() && g_bFindingUseEntity) - { - filter->DisableInteractsWithLayer(LAYER_INDEX_CONTENTS_PLAYER); - filter->DisableInteractsWithLayer(LAYER_INDEX_CONTENTS_NPC); - } - - return {KHook::Action::Ignore}; -} - -KHook::Return Detour_CEntityIOOutput_FireOutputInternal(const CEntityIOOutput* pThis, CEntityInstance* pActivator, CEntityInstance* pCaller, const CVariant* value, float flDelay, void* a6, void* a7) -{ - if (g_cvarEnableButtonWatch.Get()) - ButtonWatch(pThis, pActivator, pCaller, value, flDelay); - - if (g_cvarEnableEntWatch.Get()) - EW_FireOutput(pThis, pActivator, pCaller, value, flDelay); - - return {KHook::Action::Ignore}; -} - -#ifdef PLATFORM_WINDOWS -KHook::Return Detour_CBasePlayerPawn_GetEyePosition(CBasePlayerPawn* pPawn, Vector* pRet) -{ - if (pPawn->IsAlive() && CPointViewControlHandler::IsViewControl(reinterpret_cast(pPawn))) - { - const auto& origin = pPawn->GetEyePosition(); - pRet->Init(origin.x, origin.y, origin.z); - return {KHook::Action::Supersede, pRet}; - } - - return {KHook::Action::Ignore}; -} -KHook::Return Detour_CBasePlayerPawn_GetEyeAngles(CBasePlayerPawn* pPawn, QAngle* pRet) -{ - if (pPawn->IsAlive() && CPointViewControlHandler::IsViewControl(reinterpret_cast(pPawn))) - { - const auto& angles = pPawn->v_angle(); - pRet->Init(angles.x, angles.y, angles.z); - return {KHook::Action::Supersede, pRet}; - } - - return {KHook::Action::Ignore}; -} -#else -KHook::Return Detour_CBasePlayerPawn_GetEyePosition(CBasePlayerPawn* pPawn) -{ - if (pPawn->IsAlive() && CPointViewControlHandler::IsViewControl(reinterpret_cast(pPawn))) - { - const auto& origin = pPawn->GetEyePosition(); - return {KHook::Action::Supersede, origin}; - } - - return {KHook::Action::Ignore}; -} -KHook::Return Detour_CBasePlayerPawn_GetEyeAngles(CBasePlayerPawn* pPawn) -{ - if (pPawn->IsAlive() && CPointViewControlHandler::IsViewControl(reinterpret_cast(pPawn))) - { - const auto& angles = pPawn->v_angle(); - return {KHook::Action::Supersede, angles}; - } - - return {KHook::Action::Ignore}; -} -#endif - -KHook::Return Detour_CBaseFilter_InputTestActivator(CBaseFilter* pThis, InputData_t& inputdata) -{ - // If null activator (player disconnected & pawn removed), block the real function from executing and crashing the server - if (!inputdata.pActivator) - return {KHook::Action::Supersede}; - - return {KHook::Action::Ignore}; -} - -CConVar g_cvarFixGameBans("cs2f_fix_game_bans", FCVAR_NONE, "Whether to fix CS2 game bans spreading to all new joining players", false); - -KHook::Return Detour_GameSystem_Think_CheckSteamBan_Post() -{ - // Implementation shared by @aiolos1045 - if (!g_cvarFixGameBans.Get()) - return {KHook::Action::Ignore}; - - auto pMap = addresses::sm_mapGcBanInformation; - - // After player has been kicked, remove any ban entries, to prevent spreading to all new joining players - if (pMap->Count() > 0) - pMap->RemoveAll(); - - return {KHook::Action::Ignore}; -} - -KHook::Return Detour_CCSPlayer_ItemServices_CanAcquire(CCSPlayer_ItemServices* pItemServices, CEconItemView* pEconItem, AcquireMethod iAcquireMethod, uint64_t unk4) -{ - if (g_cvarEnableZR.Get()) - { - AcquireResult zrResult = ZR_Detour_CCSPlayer_ItemServices_CanAcquire(pItemServices, pEconItem); - - if (zrResult != AcquireResult::Allowed) - return {KHook::Action::Supersede, zrResult}; - } - - return {KHook::Action::Ignore}; -} - -bool g_bInScriptSetModel = false; - -KHook::Return Detour_CS_Script_SetModel(uint64_t unk1) -{ - g_bInScriptSetModel = true; - return {KHook::Action::Ignore}; -} - -KHook::Return Detour_CS_Script_SetModel_Post(uint64_t unk1) -{ - g_bInScriptSetModel = false; - return {KHook::Action::Ignore}; -} - -KHook::Return Detour_CBaseModelEntity_SetModel(CBaseModelEntity* pModel, const char* pszModel) -{ - if (!g_bInScriptSetModel) - return {KHook::Action::Ignore}; - - if (PrepareMapSetModel(pModel)) - return {KHook::Action::Ignore}; - - return {KHook::Action::Supersede}; -} - -KHook::Return Detour_CCSGameRules_GoToIntermission(CCSGameRules* pThis, bool bAbortedMatch) -{ - if (!g_pMapVoteSystem->IsIntermissionAllowed(false) && g_cvarVoteManagerEnable.Get()) - return {KHook::Action::Supersede}; - - if (g_cvarVoteManagerEnable.Get()) - g_pVoteManager->OnIntermission(); - - return {KHook::Action::Ignore}; -} diff --git a/src/detours.h b/src/detours.h deleted file mode 100644 index a21ecbd6..00000000 --- a/src/detours.h +++ /dev/null @@ -1,120 +0,0 @@ -/** - * ============================================================================= - * 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 "cs2_sdk/entityio.h" -#include "khook.hpp" -#include - -class CCheckTransmitInfo; -class IRecipientFilter; -class ISoundEmitterSystemBase; -class CBaseEntity; -class CBaseFilter; -class CCSPlayerController; -class CEntityIndex; -class CCommand; -class CTriggerPush; -class CTriggerGravity; -class CGameConfig; -class CGameRules; -class CTakeDamageInfo; -class CCSPlayer_WeaponServices; -class CCSPlayer_MovementServices; -class CCSPlayer_ItemServices; -class CBasePlayerWeapon; -class INetworkMessageInternal; -class IEngineServiceMgr; -class CServerSideClient; -class INetChannel; -class CBasePlayerPawn; -class CUserCmd; -class CGamePlayerEquip; -class InputData_t; -class CCSPlayerPawn; -class CCSPlayer_UseServices; -class CTraceFilter; -class CNavMesh; -class CBaseModelEntity; -class Vector; -class QAngle; -class CEconItemView; -class CCSGameRules; -struct CTakeDamageResult; - -enum class AcquireMethod -{ - PickUp, - Buy, -}; - -enum class AcquireResult -{ - Allowed, - InvalidItem, - AlreadyOwned, - AlreadyPurchased, - ReachedGrenadeTypeLimit, - ReachedGrenadeTotalLimit, - NotAllowedByTeam, - NotAllowedByMap, - NotAllowedByMode, - NotAllowedForPurchase, - NotAllowedByProhibition, -}; - -void InitDetours(CGameConfig* gameConfig); - -KHook::Return Detour_CBaseEntity_TakeDamageOld(CBaseEntity* pThis, CTakeDamageInfo* pInfo, CTakeDamageResult* pResult); -KHook::Return Detour_CBaseEntity_TakeDamageOld_Post(CBaseEntity* pThis, CTakeDamageInfo* pInfo, CTakeDamageResult* pResult); -KHook::Return Detour_TriggerPush_Touch(CTriggerPush* pPush, CBaseEntity* pOther); -KHook::Return Detour_IsHearingClient(void*, int); -KHook::Return Detour_UTIL_SayTextFilter(IRecipientFilter&, const char*, CCSPlayerController*, uint64); -KHook::Return Detour_UTIL_SayText2Filter(IRecipientFilter&, CCSPlayerController*, uint64, const char*, const char*, const char*, const char*, const char*); -KHook::Return Detour_CCSPlayer_WeaponServices_CanUse(CCSPlayer_WeaponServices*, CBasePlayerWeapon*); -KHook::Return Detour_CCSPlayer_WeaponServices_EquipWeapon(CCSPlayer_WeaponServices*, CBasePlayerWeapon*); -KHook::Return Detour_CEntityIdentity_AcceptInput(CEntityIdentity* pThis, CUtlSymbolLarge* pInputName, CEntityInstance* pActivator, CEntityInstance* pCaller, variant_t* value, int nOutputID, void*, void*); -KHook::Return Detour_CNavMesh_GetNearestNavArea(CNavMesh* pNavMesh, float* unk2, unsigned int* unk3, unsigned int unk4, int64_t unk5, float unk6, int64_t unk7); -KHook::Return Detour_ProcessMovement(CCSPlayer_MovementServices* pThis, void* pMove); -KHook::Return Detour_ProcessMovement_Post(CCSPlayer_MovementServices* pThis, void* pMove); -KHook::Return Detour_ProcessUsercmds(CCSPlayerController* pController, CUserCmd* cmds, int numcmds, bool paused, float margin); -KHook::Return Detour_CGamePlayerEquip_InputTriggerForAllPlayers(CGamePlayerEquip*, InputData_t*); -KHook::Return Detour_CGamePlayerEquip_InputTriggerForActivatedPlayer(CGamePlayerEquip*, InputData_t*); -KHook::Return Detour_CTriggerGravity_GravityTouch(CTriggerGravity* pEntity, CBaseEntity* pOther); -KHook::Return Detour_GetFreeClient(int64_t unk1, const __m128i* unk2, unsigned int unk3, int64_t unk4, char unk5, void* unk6); -KHook::Return Detour_CCSPlayerPawn_GetMaxSpeed(CCSPlayerPawn*); -KHook::Return Detour_FindUseEntity(CCSPlayer_UseServices* pThis, float a2); -KHook::Return Detour_FindUseEntity_Post(CCSPlayer_UseServices* pThis, float a2); -KHook::Return Detour_TraceFunc(int64*, int*, float*, uint64); -KHook::Return Detour_TraceShape(int64*, int64, int64, int64, CTraceFilter*, int64); -KHook::Return Detour_CEntityIOOutput_FireOutputInternal(const CEntityIOOutput* pThis, CEntityInstance* pActivator, CEntityInstance* pCaller, const CVariant* value, float flDelay, void*, void*); -#ifdef PLATFORM_WINDOWS -KHook::Return Detour_CBasePlayerPawn_GetEyePosition(CBasePlayerPawn*, Vector*); -KHook::Return Detour_CBasePlayerPawn_GetEyeAngles(CBasePlayerPawn*, QAngle*); -#else -KHook::Return Detour_CBasePlayerPawn_GetEyePosition(CBasePlayerPawn*); -KHook::Return Detour_CBasePlayerPawn_GetEyeAngles(CBasePlayerPawn*); -#endif -KHook::Return Detour_CBaseFilter_InputTestActivator(CBaseFilter* pThis, InputData_t& inputdata); -KHook::Return Detour_GameSystem_Think_CheckSteamBan_Post(); -KHook::Return Detour_CCSPlayer_ItemServices_CanAcquire(CCSPlayer_ItemServices* pItemServices, CEconItemView* pEconItem, AcquireMethod iAcquireMethod, uint64_t unk4); -KHook::Return Detour_CS_Script_SetModel(uint64_t unk1); -KHook::Return Detour_CS_Script_SetModel_Post(uint64_t unk1); -KHook::Return Detour_CBaseModelEntity_SetModel(CBaseModelEntity* pModel, const char* pszModel); -KHook::Return Detour_CCSGameRules_GoToIntermission(CCSGameRules* pThis, bool bAbortedMatch); diff --git a/src/entwatch.cpp b/src/entwatch.cpp index cc199c27..0e47fafc 100644 --- a/src/entwatch.cpp +++ b/src/entwatch.cpp @@ -22,7 +22,6 @@ #include "cs2fixes.h" #include "ctimer.h" #include "customio.h" -#include "detours.h" #include "engine/igameeventsystem.h" #include "entity/cbasebutton.h" #include "entity/ccsplayercontroller.h" @@ -33,6 +32,7 @@ #include "entity/cteam.h" #include "entity/services.h" #include "eventlistener.h" +#include "hookmanager.h" #include "gameevents.pb.h" #include "leader.h" #include "map_votes.h" diff --git a/src/entwatch.h b/src/entwatch.h index 0132c240..25ed8c8a 100644 --- a/src/entwatch.h +++ b/src/entwatch.h @@ -23,6 +23,7 @@ #include "ctimer.h" #include "eventlistener.h" #include "gamesystem.h" +#include "cs2_sdk/entityio.h" #include "khook.hpp" #include "vendor/nlohmann/json_fwd.hpp" @@ -218,20 +219,20 @@ class CEWHandler public: CEWHandler() : m_bConfigLoaded(false), - m_hBaseButtonUse(this, &CEWHandler::Hook_Use, nullptr), - m_hPhysBoxUse(this, &CEWHandler::Hook_Use, nullptr), - m_hRotButtonUse(this, &CEWHandler::Hook_Use, nullptr), - m_hMomentaryRotButtonUse(this, &CEWHandler::Hook_Use, nullptr), - m_hPhysicalButtonUse(this, &CEWHandler::Hook_Use, nullptr), - m_hTriggerTeleportStartTouch(this, &CEWHandler::Hook_Touch, nullptr), - m_hTriggerOnceStartTouch(this, &CEWHandler::Hook_Touch, nullptr), - m_hTriggerMultipleStartTouch(this, &CEWHandler::Hook_Touch, nullptr), - m_hTriggerTeleportTouch(this, &CEWHandler::Hook_Touch, nullptr), - m_hTriggerOnceTouch(this, &CEWHandler::Hook_Touch, nullptr), - m_hTriggerMultipleTouch(this, &CEWHandler::Hook_Touch, nullptr), - m_hTriggerTeleportEndTouch(this, &CEWHandler::Hook_Touch, nullptr), - m_hTriggerOnceEndTouch(this, &CEWHandler::Hook_Touch, nullptr), - m_hTriggerMultipleEndTouch(this, &CEWHandler::Hook_Touch, nullptr) + m_hBaseButtonUse(new KHook::Virtual(nullptr, this, &CEWHandler::Hook_Use, nullptr)), + m_hPhysBoxUse(new KHook::Virtual(nullptr, this, &CEWHandler::Hook_Use, nullptr)), + m_hRotButtonUse(new KHook::Virtual(nullptr, this, &CEWHandler::Hook_Use, nullptr)), + m_hMomentaryRotButtonUse(new KHook::Virtual(nullptr, this, &CEWHandler::Hook_Use, nullptr)), + m_hPhysicalButtonUse(new KHook::Virtual(nullptr, this, &CEWHandler::Hook_Use, nullptr)), + m_hTriggerTeleportStartTouch(new KHook::Virtual(nullptr, this, &CEWHandler::Hook_Touch, nullptr)), + m_hTriggerOnceStartTouch(new KHook::Virtual(nullptr, this, &CEWHandler::Hook_Touch, nullptr)), + m_hTriggerMultipleStartTouch(new KHook::Virtual(nullptr, this, &CEWHandler::Hook_Touch, nullptr)), + m_hTriggerTeleportTouch(new KHook::Virtual(nullptr, this, &CEWHandler::Hook_Touch, nullptr)), + m_hTriggerOnceTouch(new KHook::Virtual(nullptr, this, &CEWHandler::Hook_Touch, nullptr)), + m_hTriggerMultipleTouch(new KHook::Virtual(nullptr, this, &CEWHandler::Hook_Touch, nullptr)), + m_hTriggerTeleportEndTouch(new KHook::Virtual(nullptr, this, &CEWHandler::Hook_Touch, nullptr)), + m_hTriggerOnceEndTouch(new KHook::Virtual(nullptr, this, &CEWHandler::Hook_Touch, nullptr)), + m_hTriggerMultipleEndTouch(new KHook::Virtual(nullptr, this, &CEWHandler::Hook_Touch, nullptr)) { CreateHooks(); } @@ -260,7 +261,6 @@ class CEWHandler void RegisterHandler(CBaseEntity* pEnt); bool RegisterTrigger(CBaseEntity* pEnt); - KHook::Return Hook_Touch(CBaseEntity* pThis, CBaseEntity* pOther); bool RemoveTrigger(CBaseEntity* pEnt); void RemoveHandler(CBaseEntity* pEnt); void ResetAllClantags(); @@ -272,7 +272,6 @@ class CEWHandler void Transfer(CCSPlayerController* pCaller, int iItemInstance, CHandle hReceiver); void RemoveUseEntity(CBaseEntity* pEnt); - KHook::Return Hook_Use(CBaseEntity* pThis, InputData_t* pInput); std::map> mapItemConfig; /* items defined in the config */ std::vector> vecItems; /* all items found spawned */ @@ -285,21 +284,25 @@ class CEWHandler std::map> mapTransfers; // Any etransfers that target multiple items std::vector> vecActiveTransfers; // Active transfers where only the receiver can pickup the weapon +public: + KHook::Return Hook_Touch(CBaseEntity* pThis, CBaseEntity* pOther); + KHook::Return Hook_Use(CBaseEntity* pThis, InputData_t* pInput); - KHook::Virtual m_hBaseButtonUse; - KHook::Virtual m_hPhysBoxUse; - KHook::Virtual m_hRotButtonUse; - KHook::Virtual m_hMomentaryRotButtonUse; - KHook::Virtual m_hPhysicalButtonUse; - KHook::Virtual m_hTriggerTeleportStartTouch; - KHook::Virtual m_hTriggerOnceStartTouch; - KHook::Virtual m_hTriggerMultipleStartTouch; - KHook::Virtual m_hTriggerTeleportTouch; - KHook::Virtual m_hTriggerOnceTouch; - KHook::Virtual m_hTriggerMultipleTouch; - KHook::Virtual m_hTriggerTeleportEndTouch; - KHook::Virtual m_hTriggerOnceEndTouch; - KHook::Virtual m_hTriggerMultipleEndTouch; +protected: + KHook::Virtual* m_hBaseButtonUse; + KHook::Virtual* m_hPhysBoxUse; + KHook::Virtual* m_hRotButtonUse; + KHook::Virtual* m_hMomentaryRotButtonUse; + KHook::Virtual* m_hPhysicalButtonUse; + KHook::Virtual* m_hTriggerTeleportStartTouch; + KHook::Virtual* m_hTriggerOnceStartTouch; + KHook::Virtual* m_hTriggerMultipleStartTouch; + KHook::Virtual* m_hTriggerTeleportTouch; + KHook::Virtual* m_hTriggerOnceTouch; + KHook::Virtual* m_hTriggerMultipleTouch; + KHook::Virtual* m_hTriggerTeleportEndTouch; + KHook::Virtual* m_hTriggerOnceEndTouch; + KHook::Virtual* m_hTriggerMultipleEndTouch; }; extern CEWHandler* g_pEWHandler; diff --git a/src/hookmanager.cpp b/src/hookmanager.cpp new file mode 100644 index 00000000..f850ec57 --- /dev/null +++ b/src/hookmanager.cpp @@ -0,0 +1,1839 @@ +/** + * ============================================================================= + * 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 "cs_usercmd.pb.h" +#include "networkbasetypes.pb.h" +#include "usercmd.pb.h" + +#include "addresses.h" +#include "adminsystem.h" +#include "buttonwatch.h" +#include "commands.h" +#include "common.h" +#include "cs2fixes.h" +#include "cs_gameevents.pb.h" +#include "ctimer.h" +#include "customio.h" +#include "entities.h" +#include "entity/cbasemodelentity.h" +#include "entity/ccsplayercontroller.h" +#include "entity/ccsplayerpawn.h" +#include "entity/ccsweaponbase.h" +#include "entity/cenvhudhint.h" +#include "entity/cgamerules.h" +#include "entity/cpointviewcontrol.h" +#include "entity/ctakedamageinfo.h" +#include "entity/ctriggerpush.h" +#include "entity/services.h" +#include "entitylistener.h" +#include "entwatch.h" +#include "eventlistener.h" +#include "gameconfig.h" +#include "gameevents.pb.h" +#include "hookmanager.h" +#include "idlemanager.h" +#include "igameevents.h" +#include "irecipientfilter.h" +#include "leader.h" +#include "map_votes.h" +#include "mapmigrations.h" +#include "module.h" +#include "networksystem/inetworkserializer.h" +#include "networkstringtabledefs.h" +#include "panoramavote.h" +#include "patches.h" +#include "playermanager.h" +#include "serversideclient.h" +#include "te.pb.h" +#include "tier0/vprof.h" +#include "usermessages.pb.h" +#include "votemanager.h" +#include "zombiereborn.h" +#include + +#include "tier0/memdbgon.h" + +CHookManager* g_pHookManager = nullptr; + +double g_flUniversalTime = 0.0; +float g_flLastTickedTime = 0.0f; +bool g_bHasTicked = false; + +CConVar g_cvarMotdUrl("cs2f_motd_url", FCVAR_NONE, "Server MOTD URL, shows up as a \"Server Website\" button in scoreboard", ""); +CConVar g_cvarBlockParticleMsgs("cs2f_block_particle_msgs", FCVAR_NONE, "Whether to block CUserMsg_ParticleManager messages to fix lag/crashes, experimental", false); +CConVar g_cvarDropMapWeapons("cs2f_drop_map_weapons", FCVAR_NONE, "Whether to force drop map-spawned weapons on death", false); +CConVar g_cvarFixPhysicsPlayerShuffle("cs2f_shuffle_player_physics_sim", FCVAR_NONE, "Whether to enable shuffle player list in physics simulate", false); + +class CUserCmd +{ +public: + [[maybe_unused]] char pad0[0x10]; + CSGOUserCmdPB cmd; + [[maybe_unused]] char pad1[0x38]; +#ifdef PLATFORM_WINDOWS + [[maybe_unused]] char pad2[0x8]; +#endif +}; + +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); +CConVar g_cvarFixBlockDamage("cs2f_fix_block_dmg", FCVAR_NONE, "Whether to fix block-damage on players", false); +CConVar g_cvarUseOldPush("cs2f_use_old_push", FCVAR_NONE, "Whether to use the old CSGO trigger_push behavior", false); +CConVar g_cvarLogPushes("cs2f_log_pushes", FCVAR_NONE, "Whether to log pushes (cs2f_use_old_push must be enabled)", false); +CConVar g_cvarEnableTriggerTimer("cs2f_trigger_timer_enable", FCVAR_NONE, "Whether to process countdown messages said by Console (e.g. Hold for 10 seconds) and append the round time where the countdown resolves", false); +CConVar g_cvarDisableSetModel("cs2f_disable_setmodel", FCVAR_NONE, "Whether to disable SetModel usage from maps (custom input, cs_script function)", false); +CConVar g_cvarBlockNavLookup("cs2f_block_nav_lookup", FCVAR_NONE, "Whether to block navigation mesh lookup, improves server performance but breaks bot navigation", false); +CConVar g_cvarDisableSubtickMovement("cs2f_disable_subtick_move", FCVAR_NONE, "Whether to disable subtick movement", false); +CConVar g_cvarDisableSubtickShooting("cs2f_disable_subtick_shooting", FCVAR_NONE, "Whether to disable subtick shooting, experimental (WARNING: add \"log_flags Shooting + DoNotEcho\" to your cfg to prevent console spam on every shot fired)", false); +CConVar g_cvarPreventUsingPlayers("cs2f_prevent_using_players", FCVAR_NONE, "Whether to prevent +use from hitting players (0=can use players, 1=cannot use players)", false); +CConVar g_cvarFixGameBans("cs2f_fix_game_bans", FCVAR_NONE, "Whether to fix CS2 game bans spreading to all new joining players", false); + +template +static void SetupDetour(CGameConfig* gameConfig, KHook::Function* hook, const char* name) +{ + auto pfnFunc = reinterpret_cast(gameConfig->ResolveSignature(name)); + + if (!pfnFunc || !hook) + { + g_bRequiredInitLoaded = false; + return; + } + + hook->Configure(pfnFunc); + Message("Detoured %s at 0x%p\n", name, pfnFunc); +} + +template +static void SetupDetour(CGameConfig* gameConfig, KHook::Member* hook, const char* name) +{ + void* pfnFunc = gameConfig->ResolveSignature(name); + + if (!pfnFunc || !hook) + { + g_bRequiredInitLoaded = false; + return; + } + + hook->Configure(pfnFunc); + Message("Detoured %s at 0x%p\n", name, pfnFunc); +} + +// makeVirtual — mirrors Kenzzer's declareHook() but returns a heap-allocated +// pointer bound to CHookManager as context. Deduces CLASS/RETURN/ARGS from the +// callback signature so callers never need explicit template params. + +// With function MFP (vtable index known at compile time): pre + post +template +static KHook::Virtual* makeVirtual( + CHookManager* ctx, RETURN (CLASS::*fn)(ARGS...), + KHook::Return (CHookManager::*pre)(CLASS*, ARGS...), + KHook::Return (CHookManager::*post)(CLASS*, ARGS...)) +{ return new KHook::Virtual(fn, ctx, pre, post); } + +// With function MFP: nullptr pre +template +static KHook::Virtual* makeVirtual( + CHookManager* ctx, RETURN (CLASS::*fn)(ARGS...), + std::nullptr_t, + KHook::Return (CHookManager::*post)(CLASS*, ARGS...)) +{ return new KHook::Virtual(fn, ctx, nullptr, post); } + +// With function MFP: nullptr post +template +static KHook::Virtual* makeVirtual( + CHookManager* ctx, RETURN (CLASS::*fn)(ARGS...), + KHook::Return (CHookManager::*pre)(CLASS*, ARGS...), + std::nullptr_t) +{ return new KHook::Virtual(fn, ctx, pre, nullptr); } + +// No-function hooks (vtable index set later via Configure()): pre + post +template +static KHook::Virtual* makeVirtual( + CHookManager* ctx, + KHook::Return (CHookManager::*pre)(CLASS*, ARGS...), + KHook::Return (CHookManager::*post)(CLASS*, ARGS...)) +{ return new KHook::Virtual(nullptr, ctx, pre, post); } + +// No-function: nullptr pre +template +static KHook::Virtual* makeVirtual( + CHookManager* ctx, + std::nullptr_t, + KHook::Return (CHookManager::*post)(CLASS*, ARGS...)) +{ return new KHook::Virtual(nullptr, ctx, nullptr, post); } + +// No-function: nullptr post +template +static KHook::Virtual* makeVirtual( + CHookManager* ctx, + KHook::Return (CHookManager::*pre)(CLASS*, ARGS...), + std::nullptr_t) +{ return new KHook::Virtual(nullptr, ctx, pre, nullptr); } + +CHookManager::CHookManager(CGameConfig* pGameConfig) : + m_hTakeDamageOld(new KHook::Member(this, &CHookManager::Hook_TakeDamageOld, &CHookManager::Hook_TakeDamageOld_Post)), + m_hTriggerPushTouch(new KHook::Member(this, &CHookManager::Hook_TriggerPushTouch, nullptr)), + m_hIsHearingClient(new KHook::Function(this, &CHookManager::Hook_IsHearingClient, nullptr)), + m_hSayTextFilter(new KHook::Function(this, &CHookManager::Hook_SayTextFilter, nullptr)), + m_hSayText2Filter(new KHook::Function(this, &CHookManager::Hook_SayText2Filter, nullptr)), + m_hCanUse(new KHook::Member(this, &CHookManager::Hook_CanUse, nullptr)), + m_hEquipWeapon(new KHook::Member(this, &CHookManager::Hook_EquipWeapon, nullptr)), + m_hAcceptInput(new KHook::Member(this, &CHookManager::Hook_AcceptInput, nullptr)), + m_hGetNearestNavArea(new KHook::Member(this, &CHookManager::Hook_GetNearestNavArea, nullptr)), + m_hProcessMovement(new KHook::Member(this, &CHookManager::Hook_ProcessMovement, &CHookManager::Hook_ProcessMovement_Post)), + m_hProcessUsercmds(new KHook::Member(this, &CHookManager::Hook_ProcessUsercmds, nullptr)), + m_hInputTriggerForAllPlayers(new KHook::Member(this, &CHookManager::Hook_InputTriggerForAllPlayers, nullptr)), + m_hInputTriggerForActivatedPlayer(new KHook::Member(this, &CHookManager::Hook_InputTriggerForActivatedPlayer, nullptr)), + m_hGravityTouch(new KHook::Member(this, &CHookManager::Hook_GravityTouch, nullptr)), + m_hGetFreeClient(new KHook::Function(this, &CHookManager::Hook_GetFreeClient, nullptr)), + m_hGetMaxSpeed(new KHook::Member(this, &CHookManager::Hook_GetMaxSpeed, nullptr)), + m_hFindUseEntity(new KHook::Member(this, &CHookManager::Hook_FindUseEntity, &CHookManager::Hook_FindUseEntity_Post)), + m_hTraceFunc(new KHook::Function(this, &CHookManager::Hook_TraceFunc, nullptr)), + m_hTraceShape(new KHook::Function(this, &CHookManager::Hook_TraceShape, nullptr)), + m_hFireOutputInternal(new KHook::Member(this, &CHookManager::Hook_FireOutputInternal, nullptr)), + m_hGetEyePosition(new KHook::Member(this, &CHookManager::Hook_GetEyePosition, nullptr)), + m_hGetEyeAngles(new KHook::Member(this, &CHookManager::Hook_GetEyeAngles, nullptr)), + m_hInputTestActivator(new KHook::Member(this, &CHookManager::Hook_InputTestActivator, nullptr)), + m_hCheckSteamBan(new KHook::Function(this, nullptr, &CHookManager::Hook_CheckSteamBan_Post)), + m_hCanAcquire(new KHook::Member(this, &CHookManager::Hook_CanAcquire, nullptr)), + m_hScriptSetModel(new KHook::Function(this, &CHookManager::Hook_ScriptSetModel, &CHookManager::Hook_ScriptSetModel_Post)), + m_hSetModel(new KHook::Member(this, &CHookManager::Hook_SetModel, nullptr)), + m_hGoToIntermission(new KHook::Member(this, &CHookManager::Hook_GoToIntermission, nullptr)), + m_hGameFrame(makeVirtual(this, &IServerGameDLL::GameFrame, nullptr, &CHookManager::Hook_GameFrame_Post)), + m_hGameServerSteamAPIActivated(makeVirtual(this, &IServerGameDLL::GameServerSteamAPIActivated, &CHookManager::Hook_GameServerSteamAPIActivated, nullptr)), + m_hApplyGameSettings(makeVirtual(this, &IServerGameDLL::ApplyGameSettings, &CHookManager::Hook_ApplyGameSettings, nullptr)), + m_hClientActive(makeVirtual(this, &IServerGameClients::ClientActive, nullptr, &CHookManager::Hook_ClientActive_Post)), + m_hClientDisconnect(makeVirtual(this, &IServerGameClients::ClientDisconnect, nullptr, &CHookManager::Hook_ClientDisconnect_Post)), + m_hClientPutInServer(makeVirtual(this, &IServerGameClients::ClientPutInServer, nullptr, &CHookManager::Hook_ClientPutInServer_Post)), + m_hClientSettingsChanged(makeVirtual(this, &IServerGameClients::ClientSettingsChanged, &CHookManager::Hook_ClientSettingsChanged, nullptr)), + m_hOnClientConnected(makeVirtual(this, &IServerGameClients::OnClientConnected, &CHookManager::Hook_OnClientConnected, nullptr)), + m_hClientConnect(makeVirtual(this, &IServerGameClients::ClientConnect, &CHookManager::Hook_ClientConnect, nullptr)), + m_hClientCommand(makeVirtual(this, &IServerGameClients::ClientCommand, &CHookManager::Hook_ClientCommand, nullptr)), + m_hPostEventAbstract(makeVirtual(this, &IGameEventSystem::PostEventAbstract, &CHookManager::Hook_PostEventAbstract, nullptr)), + m_hStartupServer(makeVirtual(this, &INetworkServerService::StartupServer, nullptr, &CHookManager::Hook_StartupServer_Post)), + m_hCheckTransmit(makeVirtual(this, &ISource2GameEntities::CheckTransmit, nullptr, &CHookManager::Hook_CheckTransmit_Post)), + m_hDispatchConCommand(makeVirtual(this, &ICvar::DispatchConCommand, &CHookManager::Hook_DispatchConCommand, nullptr)), + m_hLoadEventsFromFile(makeVirtual(this, &IGameEventManager2::LoadEventsFromFile, &CHookManager::Hook_LoadEventsFromFile, nullptr)), + m_hSpawn(makeVirtual(this, &CEntitySystem::Spawn, nullptr, &CHookManager::Hook_Spawn_Post)), + m_hSetGameSpawnGroupMgr(makeVirtual(this, &INetworkGameServer::SetGameSpawnGroupMgr, &CHookManager::Hook_SetGameSpawnGroupMgr, nullptr)), + m_hCreateWorkshopMapGroup(makeVirtual(this, &CHookManager::Hook_CreateWorkshopMapGroup, nullptr)), + m_hGetTouchingList(makeVirtual(this, nullptr, &CHookManager::Hook_GetTouchingList_Post)), + m_hCheckMovingGround(makeVirtual(this, &CHookManager::Hook_CheckMovingGround, nullptr)), + m_hDropWeapon(makeVirtual(this, nullptr, &CHookManager::Hook_DropWeapon_Post)), + m_hPlayerEquipUse(makeVirtual(this, &CHookManager::Hook_PlayerEquipUse, nullptr)), + m_hPlayerEquipPrecache(makeVirtual(this, nullptr, &CHookManager::Hook_PlayerEquipPrecache_Post)), + m_hTriggerGravityPrecache(makeVirtual(this, nullptr, &CHookManager::Hook_TriggerGravityPrecache_Post)), + m_hTriggerGravityEndTouch(makeVirtual(this, nullptr, &CHookManager::Hook_TriggerGravityEndTouch_Post)), + m_hOnTakeDamageAlive(makeVirtual(this, &CHookManager::Hook_OnTakeDamage_Alive, nullptr)), + m_hPlayerPawnTeleport(makeVirtual(this, &CHookManager::Hook_CCSPlayerPawn_Teleport, nullptr)) +{ + CreateHooks(pGameConfig); +} + +CHookManager::~CHookManager() +{ + RemoveHooks(); +} + +void CHookManager::CreateHooks(CGameConfig* gameConfig) +{ + SetupDetour(gameConfig, m_hTakeDamageOld, "CBaseEntity_TakeDamageOld"); + SetupDetour(gameConfig, m_hTriggerPushTouch, "TriggerPush_Touch"); + SetupDetour(gameConfig, m_hIsHearingClient, "IsHearingClient"); + SetupDetour(gameConfig, m_hSayTextFilter, "UTIL_SayTextFilter"); + SetupDetour(gameConfig, m_hSayText2Filter, "UTIL_SayText2Filter"); + SetupDetour(gameConfig, m_hCanUse, "CCSPlayer_WeaponServices_CanUse"); + SetupDetour(gameConfig, m_hEquipWeapon, "CCSPlayer_WeaponServices_EquipWeapon"); + SetupDetour(gameConfig, m_hAcceptInput, "CEntityIdentity_AcceptInput"); + SetupDetour(gameConfig, m_hGetNearestNavArea, "CNavMesh_GetNearestNavArea"); + SetupDetour(gameConfig, m_hProcessMovement, "ProcessMovement"); + SetupDetour(gameConfig, m_hProcessUsercmds, "ProcessUsercmds"); + SetupDetour(gameConfig, m_hInputTriggerForAllPlayers, "CGamePlayerEquip_InputTriggerForAllPlayers"); + SetupDetour(gameConfig, m_hInputTriggerForActivatedPlayer, "CGamePlayerEquip_InputTriggerForActivatedPlayer"); + SetupDetour(gameConfig, m_hGravityTouch, "CTriggerGravity_GravityTouch"); + SetupDetour(gameConfig, m_hGetFreeClient, "GetFreeClient"); +#ifdef __linux__ + // Inlined by MSVC as of 2025-07-28 CS2 update + // TODO: Find some alternative that supports Windows + SetupDetour(gameConfig, m_hGetMaxSpeed, "CCSPlayerPawn_GetMaxSpeed"); +#endif + SetupDetour(gameConfig, m_hFindUseEntity, "FindUseEntity"); + SetupDetour(gameConfig, m_hTraceFunc, "TraceFunc"); + SetupDetour(gameConfig, m_hTraceShape, "TraceShape"); + SetupDetour(gameConfig, m_hFireOutputInternal, "CEntityIOOutput_FireOutputInternal"); + SetupDetour(gameConfig, m_hGetEyePosition, "CBasePlayerPawn_GetEyePosition"); + SetupDetour(gameConfig, m_hGetEyeAngles, "CBasePlayerPawn_GetEyeAngles"); + SetupDetour(gameConfig, m_hInputTestActivator, "CBaseFilter_InputTestActivator"); + SetupDetour(gameConfig, m_hCheckSteamBan, "GameSystem_Think_CheckSteamBan"); + SetupDetour(gameConfig, m_hCanAcquire, "CCSPlayer_ItemServices_CanAcquire"); + SetupDetour(gameConfig, m_hScriptSetModel, "CS_Script_SetModel"); + SetupDetour(gameConfig, m_hSetModel, "CBaseModelEntity_SetModel"); + SetupDetour(gameConfig, m_hGoToIntermission, "CCSGameRules_GoToIntermission"); + + m_hGameFrame->Add(g_pSource2Server); + m_hGameServerSteamAPIActivated->Add(g_pSource2Server); + m_hApplyGameSettings->Add(g_pSource2Server); + m_hClientActive->Add(g_pSource2GameClients); + m_hClientDisconnect->Add(g_pSource2GameClients); + m_hClientPutInServer->Add(g_pSource2GameClients); + m_hClientSettingsChanged->Add(g_pSource2GameClients); + m_hOnClientConnected->Add(g_pSource2GameClients); + m_hClientConnect->Add(g_pSource2GameClients); + m_hClientCommand->Add(g_pSource2GameClients); + m_hPostEventAbstract->Add(g_gameEventSystem); + m_hStartupServer->Add(g_pNetworkServerService); + m_hCheckTransmit->Add(g_pSource2GameEntities); + m_hDispatchConCommand->Add(g_pCVar); + + m_pCGameEventManagerVTable = (IGameEventManager2*)modules::server->FindVirtualTable("CGameEventManager"); + if (!m_pCGameEventManagerVTable) + { + Panic("Failed to find CGameEventManager vtable\n"); + g_bRequiredInitLoaded = false; + } + else + { + m_hLoadEventsFromFile->AddGlobal((IGameEventManager2*)&m_pCGameEventManagerVTable); + } + + m_pCEntitySystemVTable = (CEntitySystem*)modules::server->FindVirtualTable("CGameEntitySystem"); + if (!m_pCEntitySystemVTable) + { + Panic("Failed to find CGameEntitySystem vtable\n"); + g_bRequiredInitLoaded = false; + } + else + { + m_hSpawn->AddGlobal((CEntitySystem*)&m_pCEntitySystemVTable); + } + + int offset = gameConfig->GetOffset("IGameTypes_CreateWorkshopMapGroup"); + if (offset == -1) + { + Panic("Failed to find IGameTypes_CreateWorkshopMapGroup\n"); + g_bRequiredInitLoaded = false; + } + else + { + m_hCreateWorkshopMapGroup->Configure(offset); + m_hCreateWorkshopMapGroup->Add(g_pGameTypes); + } + + m_pCVPhys2WorldVTable = (CVPhys2World*)modules::vphysics2->FindVirtualTable("CVPhys2World"); + if (!m_pCVPhys2WorldVTable) + { + Panic("Failed to find CVPhys2World vtable\n"); + g_bRequiredInitLoaded = false; + } + else + { + offset = gameConfig->GetOffset("CVPhys2World::GetTouchingList"); + if (offset == -1) + { + Panic("Failed to find offset for CVPhys2World::GetTouchingList\n"); + g_bRequiredInitLoaded = false; + } + else + { + m_hGetTouchingList->Configure(offset); + m_hGetTouchingList->AddGlobal((CVPhys2World*)&m_pCVPhys2WorldVTable); + } + } + + m_pCCSPlayer_MovementServicesVTable = (CCSPlayer_MovementServices*)modules::server->FindVirtualTable("CCSPlayer_MovementServices"); + if (!m_pCCSPlayer_MovementServicesVTable) + { + Panic("Failed to find CCSPlayer_MovementServices vtable\n"); + g_bRequiredInitLoaded = false; + } + else + { + offset = gameConfig->GetOffset("CCSPlayer_MovementServices::CheckMovingGround"); + if (offset == -1) + { + Panic("Failed to find offset for CCSPlayer_MovementServices::CheckMovingGround\n"); + g_bRequiredInitLoaded = false; + } + else + { + m_hCheckMovingGround->Configure(offset); + m_hCheckMovingGround->AddGlobal((CCSPlayer_MovementServices*)&m_pCCSPlayer_MovementServicesVTable); + } + } + + m_pCCSPlayer_WeaponServicesVTable = (CCSPlayer_WeaponServices*)modules::server->FindVirtualTable("CCSPlayer_WeaponServices"); + if (!m_pCCSPlayer_WeaponServicesVTable) + { + Panic("Failed to find CCSPlayer_WeaponServices vtable\n"); + g_bRequiredInitLoaded = false; + } + else + { + offset = gameConfig->GetOffset("CCSPlayer_WeaponServices::DropWeapon"); + if (offset == -1) + { + Panic("Failed to find offset for CCSPlayer_WeaponServices::DropWeapon\n"); + g_bRequiredInitLoaded = false; + } + else + { + m_hDropWeapon->Configure(offset); + m_hDropWeapon->AddGlobal((CCSPlayer_WeaponServices*)&m_pCCSPlayer_WeaponServicesVTable); + } + } + + m_pCGamePlayerEquipVTable = (CGamePlayerEquip*)modules::server->FindVirtualTable("CGamePlayerEquip"); + if (!m_pCGamePlayerEquipVTable) + { + Panic("Failed to find CGamePlayerEquip vtable\n"); + g_bRequiredInitLoaded = false; + } + else + { + offset = gameConfig->GetOffset("CBaseEntity::Use"); + if (offset == -1) + { + Panic("Failed to find offset for CBaseEntity::Use\n"); + g_bRequiredInitLoaded = false; + } + else + { + m_hPlayerEquipUse->Configure(offset); + m_hPlayerEquipUse->AddGlobal((CGamePlayerEquip*)&m_pCGamePlayerEquipVTable); + } + + offset = gameConfig->GetOffset("CBaseEntity::Precache"); + if (offset == -1) + { + Panic("Failed to find offset for CBaseEntity::Precache\n"); + g_bRequiredInitLoaded = false; + } + else + { + m_hPlayerEquipPrecache->Configure(offset); + m_hPlayerEquipPrecache->AddGlobal((CGamePlayerEquip*)&m_pCGamePlayerEquipVTable); + } + } + + m_pTriggerGravityVTable = (CTriggerGravity*)modules::server->FindVirtualTable("CTriggerGravity"); + if (!m_pTriggerGravityVTable) + { + Panic("Failed to find CTriggerGravity vtable\n"); + g_bRequiredInitLoaded = false; + } + else + { + // Reuse CBaseEntity::Precache offset found above + offset = gameConfig->GetOffset("CBaseEntity::Precache"); + if (offset != -1) + { + m_hTriggerGravityPrecache->Configure(offset); + m_hTriggerGravityPrecache->AddGlobal((CTriggerGravity*)&m_pTriggerGravityVTable); + } + + offset = gameConfig->GetOffset("CBaseEntity::EndTouch"); + if (offset == -1) + { + Panic("Failed to find offset for CBaseEntity::EndTouch\n"); + g_bRequiredInitLoaded = false; + } + else + { + m_hTriggerGravityEndTouch->Configure(offset); + m_hTriggerGravityEndTouch->AddGlobal((CTriggerGravity*)&m_pTriggerGravityVTable); + } + } + + m_pCCSPlayerPawnVTable = (CCSPlayerPawn*)modules::server->FindVirtualTable("CCSPlayerPawn"); + if (!m_pCCSPlayerPawnVTable) + { + Panic("Failed to find CCSPlayerPawn vtable\n"); + g_bRequiredInitLoaded = false; + } + else + { + offset = gameConfig->GetOffset("CCSPlayerPawn::OnTakeDamage_Alive"); + if (offset == -1) + { + Panic("Failed to find offset for CCSPlayerPawn::OnTakeDamage_Alive\n"); + g_bRequiredInitLoaded = false; + } + else + { + m_hOnTakeDamageAlive->Configure(offset); + m_hOnTakeDamageAlive->AddGlobal((CCSPlayerPawn*)&m_pCCSPlayerPawnVTable); + } + + offset = gameConfig->GetOffset("Teleport"); + if (offset == -1) + { + Panic("Failed to find offset for Teleport\n"); + g_bRequiredInitLoaded = false; + } + else + { + m_hPlayerPawnTeleport->Configure(offset); + m_hPlayerPawnTeleport->AddGlobal((CCSPlayerPawn*)&m_pCCSPlayerPawnVTable); + } + } +} + +void CHookManager::RemoveHooks() +{ + // Remove virtual hooks + m_hGameFrame->Remove(g_pSource2Server); + m_hGameServerSteamAPIActivated->Remove(g_pSource2Server); + m_hApplyGameSettings->Remove(g_pSource2Server); + m_hClientActive->Remove(g_pSource2GameClients); + m_hClientDisconnect->Remove(g_pSource2GameClients); + m_hClientPutInServer->Remove(g_pSource2GameClients); + m_hClientSettingsChanged->Remove(g_pSource2GameClients); + m_hOnClientConnected->Remove(g_pSource2GameClients); + m_hClientConnect->Remove(g_pSource2GameClients); + m_hClientCommand->Remove(g_pSource2GameClients); + m_hPostEventAbstract->Remove(g_gameEventSystem); + m_hStartupServer->Remove(g_pNetworkServerService); + m_hCheckTransmit->Remove(g_pSource2GameEntities); + m_hDispatchConCommand->Remove(g_pCVar); + m_hSetGameSpawnGroupMgr->Remove(GetNetworkGameServer()); + + if (m_pCGameEventManagerVTable) + m_hLoadEventsFromFile->RemoveGlobal((IGameEventManager2*)&m_pCGameEventManagerVTable); + if (m_pCEntitySystemVTable) + m_hSpawn->RemoveGlobal((CEntitySystem*)&m_pCEntitySystemVTable); + m_hCreateWorkshopMapGroup->Remove(g_pGameTypes); + if (m_pCVPhys2WorldVTable) + m_hGetTouchingList->RemoveGlobal((CVPhys2World*)&m_pCVPhys2WorldVTable); + if (m_pCCSPlayer_MovementServicesVTable) + m_hCheckMovingGround->RemoveGlobal((CCSPlayer_MovementServices*)&m_pCCSPlayer_MovementServicesVTable); + if (m_pCCSPlayer_WeaponServicesVTable) + m_hDropWeapon->RemoveGlobal((CCSPlayer_WeaponServices*)&m_pCCSPlayer_WeaponServicesVTable); + if (m_pCGamePlayerEquipVTable) + { + m_hPlayerEquipUse->RemoveGlobal((CGamePlayerEquip*)&m_pCGamePlayerEquipVTable); + m_hPlayerEquipPrecache->RemoveGlobal((CGamePlayerEquip*)&m_pCGamePlayerEquipVTable); + } + if (m_pTriggerGravityVTable) + { + m_hTriggerGravityPrecache->RemoveGlobal((CTriggerGravity*)&m_pTriggerGravityVTable); + m_hTriggerGravityEndTouch->RemoveGlobal((CTriggerGravity*)&m_pTriggerGravityVTable); + } + if (m_pCCSPlayerPawnVTable) + { + m_hOnTakeDamageAlive->RemoveGlobal((CCSPlayerPawn*)&m_pCCSPlayerPawnVTable); + m_hPlayerPawnTeleport->RemoveGlobal((CCSPlayerPawn*)&m_pCCSPlayerPawnVTable); + } + + delete m_hGameFrame; + delete m_hGameServerSteamAPIActivated; + delete m_hApplyGameSettings; + delete m_hClientActive; + delete m_hClientDisconnect; + delete m_hClientPutInServer; + delete m_hClientSettingsChanged; + delete m_hOnClientConnected; + delete m_hClientConnect; + delete m_hClientCommand; + delete m_hPostEventAbstract; + delete m_hStartupServer; + delete m_hCheckTransmit; + delete m_hDispatchConCommand; + delete m_hLoadEventsFromFile; + delete m_hSpawn; + delete m_hSetGameSpawnGroupMgr; + delete m_hCreateWorkshopMapGroup; + delete m_hGetTouchingList; + delete m_hCheckMovingGround; + delete m_hDropWeapon; + delete m_hPlayerEquipUse; + delete m_hPlayerEquipPrecache; + delete m_hTriggerGravityPrecache; + delete m_hTriggerGravityEndTouch; + delete m_hOnTakeDamageAlive; + delete m_hPlayerPawnTeleport; + + delete m_hTakeDamageOld; + delete m_hTriggerPushTouch; + delete m_hIsHearingClient; + delete m_hSayTextFilter; + delete m_hSayText2Filter; + delete m_hCanUse; + delete m_hEquipWeapon; + delete m_hAcceptInput; + delete m_hGetNearestNavArea; + delete m_hProcessMovement; + delete m_hProcessUsercmds; + delete m_hInputTriggerForAllPlayers; + delete m_hInputTriggerForActivatedPlayer; + delete m_hGravityTouch; + delete m_hGetFreeClient; + delete m_hGetMaxSpeed; + delete m_hFindUseEntity; + delete m_hTraceFunc; + delete m_hTraceShape; + delete m_hFireOutputInternal; + delete m_hGetEyePosition; + delete m_hGetEyeAngles; + delete m_hInputTestActivator; + delete m_hCheckSteamBan; + delete m_hCanAcquire; + delete m_hScriptSetModel; + delete m_hSetModel; + delete m_hGoToIntermission; +} + +KHook::Return CHookManager::Hook_GameFrame_Post(IServerGameDLL* pThis, bool simulating, bool bFirstTick, bool bLastTick) +{ + VPROF_BUDGET("CS2Fixes::Hook_GameFramePost", "CS2FixesPerFrame"); + + if (!GetGlobals()) + return {KHook::Action::Ignore}; + + if (simulating && g_bHasTicked) + g_flUniversalTime += GetGlobals()->curtime - g_flLastTickedTime; + + g_flLastTickedTime = GetGlobals()->curtime; + g_bHasTicked = true; + + RunTimers(); + EntityHandler_OnGameFramePost(simulating, GetGlobals()->tickcount); + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_GameServerSteamAPIActivated(IServerGameDLL* pThis) +{ + g_playerManager->OnSteamAPIActivated(); + + if (g_cvarVoteManagerEnable.Get() && !g_pMapVoteSystem->IsMapListLoaded()) + g_pMapVoteSystem->LoadMapList(); + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_ApplyGameSettings(IServerGameDLL* pThis, KeyValues* pKV) +{ + g_pMapVoteSystem->ApplyGameSettings(pKV); + g_pMapMigrations->ApplyGameSettings(pKV); + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_ClientActive_Post(IServerGameClients* pThis, CPlayerSlot slot, bool bLoadGame, const char* pszName, uint64 xuid) +{ + Message("Hook_ClientActive(%d, %d, \"%s\", %lli)\n", slot, bLoadGame, pszName, xuid); + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_ClientDisconnect_Post(IServerGameClients* pThis, CPlayerSlot slot, ENetworkDisconnectionReason reason, const char* pszName, uint64 xuid, const char* pszNetworkID) +{ + Message("Hook_ClientDisconnect(%d, %d, \"%s\", %lli)\n", slot, reason, pszName, xuid); + + if (g_cvarEnableZR.Get()) + { + if (!ZR_CheckTeamWinConditions(CS_TEAM_T)) + ZR_CheckTeamWinConditions(CS_TEAM_CT); + } + + ZEPlayer* pPlayer = g_playerManager->GetPlayer(slot); + + if (!pPlayer) + return {KHook::Action::Ignore}; + + if (reason != NETWORK_DISCONNECT_LOOPSHUTDOWN && reason != NETWORK_DISCONNECT_SHUTDOWN) + g_pAdminSystem->AddDisconnectedPlayer(pszName, xuid, pPlayer ? pPlayer->GetIpAddress() : ""); + + g_playerManager->OnClientDisconnect(slot); + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_ClientPutInServer_Post(IServerGameClients* pThis, CPlayerSlot slot, const char* pszName, int type, uint64 xuid) +{ + Message("Hook_ClientPutInServer(%d, \"%s\", %d, %d, %lli)\n", slot, pszName, type, xuid); + + if (!g_playerManager->GetPlayer(slot)) + return {KHook::Action::Ignore}; + + g_playerManager->OnClientPutInServer(slot); + + if (g_cvarEnableZR.Get()) + ZR_Hook_ClientPutInServer(slot, pszName, type, xuid); + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_ClientSettingsChanged(IServerGameClients* pThis, CPlayerSlot slot) +{ +#ifdef _DEBUG + Message("Hook_ClientSettingsChanged(%d)\n", slot); +#endif + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_OnClientConnected(IServerGameClients* pThis, CPlayerSlot slot, const char* pszName, uint64 xuid, const char* pszNetworkID, const char* pszAddress, bool bFakePlayer) +{ + Message("Hook_OnClientConnected(%d, \"%s\", %lli, \"%s\", \"%s\", %d)\n", slot, pszName, xuid, pszNetworkID, pszAddress, bFakePlayer); + + static ConVarRefAbstract tv_name("tv_name"); + const char* pszTvName = tv_name.GetString().Get(); + + if (bFakePlayer && V_strcmp(pszName, pszTvName)) + g_playerManager->OnBotConnected(slot); + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_ClientConnect(IServerGameClients* pThis, CPlayerSlot slot, const char* pszName, uint64 xuid, const char* pszNetworkID, bool unk1, CBufferString* pRejectReason) +{ + Message("Hook_ClientConnect(%d, \"%s\", %lli, \"%s\", %d, \"%s\")\n", slot, pszName, xuid, pszNetworkID, unk1, pRejectReason->Get()); + + if (!g_playerManager->OnClientConnected(slot, xuid, pszNetworkID)) + return {KHook::Action::Supersede, false}; + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_ClientCommand(IServerGameClients* pThis, CPlayerSlot slot, const CCommand& args) +{ +#ifdef _DEBUG + Message("Hook_ClientCommand(%d, \"%s\")\n", slot, args.GetCommandString()); +#endif + + if (g_cvarIdleKickTime.Get() > 0.0f) + { + ZEPlayer* pPlayer = g_playerManager->GetPlayer(slot); + + if (pPlayer) + pPlayer->UpdateLastInputTime(); + } + + if (g_cvarVoteManagerEnable.Get() && V_stricmp(args[0], "endmatch_votenextmap") == 0 && args.ArgC() == 2) + { + if (g_pMapVoteSystem->RegisterPlayerVote(slot, atoi(args[1]))) + return {KHook::Action::Ignore}; + else + return {KHook::Action::Supersede}; + } + + if (g_cvarEnableZR.Get() && slot != -1 && !V_strncmp(args.Arg(0), "jointeam", 8)) + { + ZR_Hook_ClientCommand_JoinTeam(slot, args); + return {KHook::Action::Supersede}; + } + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_PostEventAbstract(IGameEventSystem* pThis, CSplitScreenSlot nSlot, bool bLocalOnly, int nClientCount, const uint64* clients, INetworkMessageInternal* pEvent, const CNetMessage* pData, unsigned long nSize, NetChannelBufType_t bufType) +{ + NetMessageInfo_t* info = pEvent->GetNetMessageInfo(); + + if (g_cvarEnableStopSound.Get() && info->m_MessageId == GE_FireBulletsId) + { + if (g_playerManager->GetSilenceSoundMask()) + { + auto msg = const_cast(pData)->ToPB(); + + int32_t weapon_id = msg->weapon_id(); + int32_t sound_type = msg->sound_type(); + int32_t item_def_index = msg->item_def_index(); + + msg->set_weapon_id(0); + msg->set_sound_type(9); + msg->set_item_def_index(61); // weapon_usp_silencer + + uint64 clientMask = *(uint64*)clients & g_playerManager->GetSilenceSoundMask(); + + m_hPostEventAbstract->CallOriginal(pThis, nSlot, bLocalOnly, nClientCount, &clientMask, pEvent, msg, nSize, bufType); + + msg->set_weapon_id(weapon_id); + msg->set_sound_type(sound_type); + msg->set_item_def_index(item_def_index); + } + + *(uint64*)clients &= ~g_playerManager->GetStopSoundMask(); + *(uint64*)clients &= ~g_playerManager->GetSilenceSoundMask(); + } + else if (info->m_MessageId == GE_PlaceDecalEvent) + { + *(uint64*)clients &= ~g_playerManager->GetStopDecalsMask(); + } + else if (info->m_MessageId == GE_Source1LegacyGameEvent) + { + if (g_cvarEnableLeader.Get()) + Leader_PostEventAbstract_Source1LegacyGameEvent(clients, pData); + } + else if (info->m_MessageId == UM_Shake) + { + auto pPBData = const_cast(pData)->ToPB(); + if (g_cvarMaxShakeAmp.Get() >= 0 && pPBData->amplitude() > g_cvarMaxShakeAmp.Get()) + pPBData->set_amplitude(g_cvarMaxShakeAmp.Get()); + + if (g_cvarEnableNoShake.Get()) + *(uint64*)clients &= ~g_playerManager->GetNoShakeMask(); + } + else if (info->m_MessageId == GE_SosStartSoundEvent) + { + auto msg = const_cast(pData)->ToPB(); + + if (g_cvarEnableZR.Get()) + ZR_PostEventAbstract_SosStartSoundEvent(clients, msg); + + if (g_cvarEnableStopSound.Get()) + { + static std::set soundEventHashes; + + ExecuteOnce( + soundEventHashes.insert(GetSoundEventHash("Weapon_sg556.ZoomIn")); + soundEventHashes.insert(GetSoundEventHash("Weapon_sg556.ZoomOut")); + soundEventHashes.insert(GetSoundEventHash("Weapon_AUG.ZoomIn")); + soundEventHashes.insert(GetSoundEventHash("Weapon_AUG.ZoomOut")); + soundEventHashes.insert(GetSoundEventHash("Weapon_SSG08.Zoom")); + soundEventHashes.insert(GetSoundEventHash("Weapon_SSG08.ZoomOut")); + soundEventHashes.insert(GetSoundEventHash("Weapon_SCAR20.Zoom")); + soundEventHashes.insert(GetSoundEventHash("Weapon_SCAR20.ZoomOut")); + soundEventHashes.insert(GetSoundEventHash("Weapon_G3SG1.Zoom")); + soundEventHashes.insert(GetSoundEventHash("Weapon_G3SG1.ZoomOut")); + soundEventHashes.insert(GetSoundEventHash("Weapon_AWP.Zoom")); + soundEventHashes.insert(GetSoundEventHash("Weapon_AWP.ZoomOut")); + soundEventHashes.insert(GetSoundEventHash("Weapon_Revolver.Prepare")); + soundEventHashes.insert(GetSoundEventHash("Weapon.AutoSemiAutoSwitch"));); + + if (!soundEventHashes.contains(msg->soundevent_hash())) + return {KHook::Action::Ignore}; + + uint64 stopSoundMask = g_playerManager->GetStopSoundMask(); + uint64 silenceSoundMask = g_playerManager->GetSilenceSoundMask(); + + if (!msg->has_source_entity_index()) + return {KHook::Action::Ignore}; + + CBaseEntity* pSourceEntity = (CBaseEntity*)g_pEntitySystem->GetEntityInstance(CEntityIndex(msg->source_entity_index())); + int playerSlot = -1; + + if (!pSourceEntity) + return {KHook::Action::Ignore}; + + if (pSourceEntity->IsPawn() && ((CCSPlayerPawn*)pSourceEntity)->GetController()) + { + playerSlot = ((CCSPlayerPawn*)pSourceEntity)->GetController()->GetPlayerSlot(); + } + else if (!V_strncasecmp(pSourceEntity->GetClassname(), "weapon_", 7)) + { + CCSPlayerPawn* pPawn = (CCSPlayerPawn*)pSourceEntity->m_hOwnerEntity().Get(); + + if (pPawn && pPawn->IsPawn() && pPawn->GetController()) + playerSlot = pPawn->GetController()->GetPlayerSlot(); + } + + if (playerSlot != -1 && g_playerManager->IsPlayerUsingStopSound(playerSlot)) + stopSoundMask &= ~((uint64)1 << playerSlot); + + if (playerSlot != -1 && g_playerManager->IsPlayerUsingSilenceSound(playerSlot)) + silenceSoundMask &= ~((uint64)1 << playerSlot); + + *(uint64*)clients &= ~stopSoundMask; + *(uint64*)clients &= ~silenceSoundMask; + } + } + else if (info->m_MessageId == UM_ParticleManager) + { + if (g_cvarBlockParticleMsgs.Get()) + *(uint64*)clients = 0; + } + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_StartupServer_Post(INetworkServerService* pThis, const GameSessionConfiguration_t& config, ISource2WorldSession* pSession, const char* pszMapName) +{ + g_pEntitySystem = GameEntitySystem(); + g_pEntitySystem->AddListenerEntity(g_pEntityListener); + + if (GetNetworkGameServer()) + m_hSetGameSpawnGroupMgr->Add(GetNetworkGameServer()); + + Message("Hook_StartupServer: %s\n", pszMapName); + + RegisterEventListeners(); + + if (g_bHasTicked) + RemoveTimers(TIMERFLAG_MAP); + + g_bHasTicked = false; + + g_pPanoramaVoteHandler->Reset(); + g_pVoteManager->VoteManager_Init(); + g_pIdleSystem->Reset(); + + INetworkStringTable* pInfoPanelTable = g_pNetworkStringTableServer->FindTable("InfoPanel"); + + if (pInfoPanelTable && V_strcmp(g_cvarMotdUrl.Get(), "")) + { + SetStringUserDataRequest_t pUserData; + pUserData.m_pRawData = (void*)g_cvarMotdUrl.Get().Get(); + pUserData.m_cbDataSize = g_cvarMotdUrl.Get().Length() + 1; + + pInfoPanelTable->AddString(true, "motd", &pUserData); + } + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_CheckTransmit_Post(ISource2GameEntities* pThis, CCheckTransmitInfo** ppInfoList, int infoCount, CBitVec<16384>& unionTransmitEdicts, CBitVec<16384>&, const Entity2Networkable_t** pNetworkables, const uint16* pEntityIndicies, int nEntities) +{ + if (!g_pEntitySystem || !GetGlobals()) + return {KHook::Action::Ignore}; + + VPROF("CS2Fixes::Hook_CheckTransmit"); + + for (int i = 0; i < infoCount; i++) + { + auto& pInfo = ppInfoList[i]; + + static int offset = g_GameConfig->GetOffset("CheckTransmitPlayerSlot"); + int iPlayerSlot = (int)*((uint8*)pInfo + offset); + + CCSPlayerController* pSelfController = CCSPlayerController::FromSlot(iPlayerSlot); + + if (!pSelfController || !pSelfController->IsConnected()) + continue; + + auto pSelfZEPlayer = g_playerManager->GetPlayer(iPlayerSlot); + + if (!pSelfZEPlayer) + continue; + + for (int j = 0; j < GetGlobals()->maxClients; j++) + { + CCSPlayerController* pController = CCSPlayerController::FromSlot(j); + if (!pController || pController->m_bIsHLTV || j == iPlayerSlot) + continue; + + CBarnLight* pFlashLight = pController->IsConnected() ? g_playerManager->GetPlayer(j)->GetFlashLight() : nullptr; + + if (!g_cvarFlashLightTransmitOthers.Get() && pFlashLight) + pInfo->m_pTransmitEntity->Clear(pFlashLight->entindex()); + + if (g_cvarEnableEntWatch.Get() && g_pEWHandler->IsConfigLoaded()) + { + CPointWorldText* pHud = pController->IsConnected() ? g_playerManager->GetPlayer(j)->GetEntwatchHud() : nullptr; + if (pHud) + pInfo->m_pTransmitEntity->Clear(pHud->entindex()); + } + + if (!g_cvarEnableHide.Get() || pSelfController->GetPawnState() == STATE_OBSERVER_MODE) + continue; + + CCSPlayerPawn* pPawn = pController->GetPlayerPawn(); + + if (!pPawn) + continue; + + ZEPlayer* pOtherZEPlayer = g_playerManager->GetPlayer(j); + if (pSelfZEPlayer->ShouldBlockTransmit(j) && pOtherZEPlayer && !pOtherZEPlayer->IsLeader() && g_pEWHandler->FindItemInstanceByOwner(j, false, 0) == -1) + { + pInfo->m_pTransmitEntity->Clear(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()); + } + } + } + } + + CBaseModelEntity* pGlowModel = pSelfZEPlayer->GetGlowModel(); + + if (pGlowModel) + pInfo->m_pTransmitEntity->Clear(pGlowModel->entindex()); + } + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_DispatchConCommand(ICvar* pThis, ConCommandRef cmdHandle, const CCommandContext& ctx, const CCommand& args) +{ + VPROF_BUDGET("CS2Fixes::Hook_DispatchConCommand", "ConCommands"); + + if (!g_pEntitySystem) + return {KHook::Action::Ignore}; + + auto iCommandPlayerSlot = ctx.GetPlayerSlot(); + + if (!g_cvarEnableCommands.Get()) + return {KHook::Action::Ignore}; + + bool bSay = !V_strcmp(args.Arg(0), "say"); + bool bTeamSay = !V_strcmp(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); + bool bAdminChat = bTeamSay && *args[1] == '@'; + bool bSilent = *args[1] == '/' || bAdminChat; + bool bCommand = *args[1] == '!' || *args[1] == '/'; + + if (pController) + { + IGameEvent* pEvent = g_gameEventManager->CreateEvent("player_chat"); + + if (pEvent) + { + pEvent->SetBool("teamonly", bTeamSay); + pEvent->SetInt("userid", pController->GetPlayerSlot()); + pEvent->SetString("text", args[1]); + + g_gameEventManager->FireEvent(pEvent, true); + } + } + + if (!bGagged && !bSilent && !bFlooding) + { + m_hDispatchConCommand->CallOriginal(pThis, cmdHandle, ctx, args); + } + else if (bFlooding) + { + if (pController) + ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "You are flooding the server!"); + } + else if (bAdminChat && GetGlobals()) + { + char* pszMessage = (char*)(args.ArgS() + 2); + pszMessage[V_strlen(pszMessage) - 1] = 0; + + for (int i = 0; i < GetGlobals()->maxClients; i++) + { + ZEPlayer* pPlayer = g_playerManager->GetPlayer(i); + + if (!pPlayer) + continue; + + if (i == iCommandPlayerSlot.Get() || pPlayer->IsAdminFlagSet(ADMFLAG_GENERIC)) + ClientPrint(CCSPlayerController::FromSlot(i), HUD_PRINTTALK, " \4(%sADMINS) %s:\6 %s", bIsAdmin ? "" : "TO ", pController->GetPlayerName().c_str(), pszMessage); + } + } + + if (bCommand) + { + char* pszMessage = (char*)(args.ArgS() + 1); + + if (pszMessage[0] == '"' || pszMessage[0] == '!' || pszMessage[0] == '/') + pszMessage += 1; + + if ((bGagged || bSilent || bFlooding) && pszMessage[V_strlen(pszMessage) - 1] == '"') + pszMessage[V_strlen(pszMessage) - 1] = '\0'; + + ParseChatCommand(pszMessage, pController); + } + + return {KHook::Action::Supersede}; + } + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_LoadEventsFromFile(IGameEventManager2* pThis, const char* filename, bool bSearchAll) +{ + ExecuteOnce(g_gameEventManager = pThis); + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_Spawn_Post(CEntitySystem* pThis, int nCount, const EntitySpawnInfo_t* pInfo) +{ + for (int i = 0; i < nCount; i++) + g_pMapMigrations->OnEntitySpawned(pInfo[i].m_pEntity->m_pInstance, pInfo[i].m_pKeyValues); + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_SetGameSpawnGroupMgr(INetworkGameServer* pThis, IGameSpawnGroupMgr* pSpawnGroupMgr) +{ + g_pSpawnGroupMgr = (CSpawnGroupMgrGameSystem*)pSpawnGroupMgr; + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_CreateWorkshopMapGroup(IGameTypes* pThis, const char* name, const CUtlStringList& mapList) +{ + if (g_cvarVoteManagerEnable.Get() && g_pMapVoteSystem->IsMapListLoaded()) + return KHook::Recall(nullptr, {KHook::Action::Ignore}, pThis, name, g_pMapVoteSystem->CreateWorkshopMapGroup()); + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_GetTouchingList_Post(CVPhys2World* pThis, CUtlVector* pList, bool unknown) +{ + if (!g_cvarFixPhysicsPlayerShuffle.Get() || pList->Count() <= 1) + return {KHook::Action::Ignore}; + + if (GetGlobals()) + std::srand(GetGlobals()->tickcount); + + std::vector touchingLinks; + std::vector unTouchLinks; + + FOR_EACH_VEC(*pList, i) + { + const auto& link = pList->Element(i); + if (link.IsUnTouching()) + unTouchLinks.push_back(link); + else + touchingLinks.push_back(link); + } + + if (touchingLinks.size() <= 1) + return {KHook::Action::Ignore}; + + for (size_t i = touchingLinks.size() - 1; i > 0; --i) + { + const auto j = std::rand() % (i + 1); + std::swap(touchingLinks[i], touchingLinks[j]); + } + + pList->Purge(); + + for (const auto& link : touchingLinks) + pList->AddToTail(link); + for (const auto& link : unTouchLinks) + pList->AddToTail(link); + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_CheckMovingGround(CCSPlayer_MovementServices* pThis, double frametime) +{ + CCSPlayerPawn* pPawn = pThis->GetPawn(); + + if (!pPawn || !GetGlobals()) + return {KHook::Action::Ignore}; + + CCSPlayerController* pController = pPawn->GetOriginalController(); + + if (!pController) + return {KHook::Action::Ignore}; + + int iSlot = pController->GetPlayerSlot(); + + static int aPlayerTicks[MAXPLAYERS] = {0}; + + if (aPlayerTicks[iSlot] == GetGlobals()->tickcount) + return {KHook::Action::Supersede}; + + aPlayerTicks[iSlot] = GetGlobals()->tickcount; + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_DropWeapon_Post(CCSPlayer_WeaponServices* pThis, CBasePlayerWeapon* pWeapon, Vector* pVecTarget, Vector* pVelocity) +{ + if (g_cvarEnableEntWatch.Get()) + EW_DropWeapon(pThis, pWeapon); + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_PlayerEquipUse(CGamePlayerEquip* pThis, InputData_t* pInput) +{ + CGamePlayerEquipHandler::Use(pThis, pInput); + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_PlayerEquipPrecache_Post(CGamePlayerEquip* pThis, CEntityPrecacheContext* param) +{ + const auto kv = param->m_pKeyValues; + CGamePlayerEquipHandler::OnPrecache(pThis, kv); + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_TriggerGravityPrecache_Post(CTriggerGravity* pThis, CEntityPrecacheContext* param) +{ + const auto kv = param->m_pKeyValues; + CTriggerGravityHandler::OnPrecache(pThis, kv); + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_TriggerGravityEndTouch_Post(CTriggerGravity* pThis, CBaseEntity* pOther) +{ + CTriggerGravityHandler::OnEndTouch(pThis, pOther); + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_OnTakeDamage_Alive(CCSPlayerPawn* pPawn, CTakeDamageResult* pDamageResult) +{ + if (g_cvarEnableZR.Get() && ZR_Hook_OnTakeDamage_Alive(pDamageResult->m_pOriginatingInfo, pPawn)) + { + pDamageResult->m_bWasDamageSuppressed = true; + pDamageResult->m_flDamageDealt = 0.0f; + return {KHook::Action::Supersede, false}; + } + + if (g_cvarDropMapWeapons.Get() && pPawn && pPawn->m_iHealth() <= 0) + { + if (g_cvarEnableEntWatch.Get()) + { + CCSPlayerController* pController = pPawn->GetOriginalController(); + if (pController) + EW_PlayerDeathPre(pController); + } + + pPawn->DropMapWeapons(); + } + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_CCSPlayerPawn_Teleport(CCSPlayerPawn* pPawn, const Vector* pPosition, const QAngle* pAngles, const Vector* pVelocity) +{ + if (!pAngles) + return {KHook::Action::Ignore}; + + QAngle* pCastAngles = const_cast(pAngles); + + if (pCastAngles->x != 0.0f) + pCastAngles->x = 0.0f; + + if (pCastAngles->z != 0.0f) + pCastAngles->z = 0.0f; + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_TakeDamageOld(CBaseEntity* pThis, CTakeDamageInfo* pInfo, CTakeDamageResult* pResult) +{ + // NOTE valve always return 1 here, since 2025/10/15 update + +#ifdef _DEBUG + Message("\n--------------------------------\nTakeDamage on %s\nAttacker: %s\nInflictor: %s\nAbility: %s\nDamage: %.2f\nDamage Type: %i\n--------------------------------\n", pThis->GetClassname(), pInfo->m_hAttacker.Get() ? pInfo->m_hAttacker.Get()->GetClassname() : "NULL", pInfo->m_hInflictor.Get() ? pInfo->m_hInflictor.Get()->GetClassname() : "NULL", pInfo->m_hAbility.Get() ? pInfo->m_hAbility.Get()->GetClassname() : "NULL", pInfo->m_flDamage, pInfo->m_bitsDamageType); +#endif + + if (g_cvarBlockAllDamage.Get() && pThis->IsPawn()) return {KHook::Action::Supersede, 1}; + + CEntityInstance* pInflictor = pInfo->m_hInflictor.Get(); + const char* pszInflictorClass = pInflictor ? pInflictor->GetClassname() : ""; + + if (g_cvarFixBlockDamage.Get() && pInfo->m_AttackerInfo.m_bIsPawn && pInfo->m_bitsDamageType ^ DMG_BULLET && pInfo->m_hAttacker != pThis->GetHandle()) + { + if (V_strcasecmp(pszInflictorClass, "func_movelinear") == 0 || V_strcasecmp(pszInflictorClass, "func_mover") == 0 || V_strcasecmp(pszInflictorClass, "func_door") == 0 || V_strcasecmp(pszInflictorClass, "func_door_rotating") == 0 || V_strcasecmp(pszInflictorClass, "func_rotating") == 0 || V_strcasecmp(pszInflictorClass, "point_hurt") == 0) + { + pInfo->m_AttackerInfo.m_bIsPawn = false; + pInfo->m_AttackerInfo.m_bIsWorld = true; + pInfo->m_hAttacker = pInfo->m_hInflictor; + + pInfo->m_AttackerInfo.m_hAttackerPawn = CHandle(~0u); + pInfo->m_AttackerInfo.m_nAttackerPlayerSlot = ~0; + } + } + + if (g_cvarBlockMolotovSelfDmg.Get() && pInfo->m_hAttacker == pThis && !V_strncmp(pszInflictorClass, "inferno", 7)) return {KHook::Action::Supersede, 1}; + + if (!V_strcasecmp(pszInflictorClass, "hegrenade_projectile") && pInfo->m_AttackerInfo.m_bIsPawn && pInfo->m_AttackerInfo.m_nTeam == 0) return {KHook::Action::Supersede, 1}; + + CTakeDamageResult damageResult(0); + + if (pResult == nullptr) + { + damageResult.CopyFrom(pInfo); + return KHook::Recall(nullptr, {KHook::Action::Ignore}, pThis, pInfo, &damageResult); + } + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_TakeDamageOld_Post(CBaseEntity* pThis, CTakeDamageInfo* pInfo, CTakeDamageResult* pResult) +{ + if (pResult->m_flDamageDealt > 0.0f && !pResult->m_bWasDamageSuppressed && g_cvarEnableZR.Get() && pThis->IsPawn()) ZR_OnPlayerTakeDamage(reinterpret_cast(pThis), pInfo, pResult->m_flDamageDealt); + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_TriggerPushTouch(CTriggerPush* pPush, CBaseEntity* pOther) +{ + if (!g_cvarUseOldPush.Get() || pPush->m_spawnflags() & SF_TRIG_PUSH_ONCE || pPush->m_bTriggerOnStartTouch()) return {KHook::Action::Ignore}; + + MoveType_t movetype = pOther->m_nActualMoveType(); + + if (movetype == MOVETYPE_VPHYSICS) return {KHook::Action::Ignore}; + + if (movetype == MOVETYPE_NONE || movetype == MOVETYPE_PUSH || movetype == MOVETYPE_NOCLIP) return {KHook::Action::Supersede}; + + CCollisionProperty* collisionProp = pOther->m_pCollision(); + if (!IsSolid(collisionProp->m_nSolidType(), collisionProp->m_usSolidFlags())) return {KHook::Action::Supersede}; + + if (!pPush->PassesTriggerFilters(pOther)) return {KHook::Action::Supersede}; + + if (pOther->m_CBodyComponent()->m_pSceneNode()->m_pParent()) return {KHook::Action::Supersede}; + + Vector vecAbsDir; + matrix3x4_t matTransform = pPush->m_CBodyComponent()->m_pSceneNode()->EntityToWorldTransform(); + + Vector vecPushDir = pPush->m_vecPushDirEntitySpace(); + VectorRotate(vecPushDir, matTransform, vecAbsDir); + + Vector vecPush = vecAbsDir * pPush->m_flSpeed(); + + uint32 flags = pOther->m_fFlags(); + + if (flags & FL_BASEVELOCITY) + vecPush = vecPush + pOther->m_vecBaseVelocity(); + + if (vecPush.z > 0 && (flags & FL_ONGROUND)) + { + pOther->SetGroundEntity(nullptr); + Vector origin = pOther->GetAbsOrigin(); + origin.z += 1.0f; + + pOther->Teleport(&origin, nullptr, nullptr); + } + + if (g_cvarLogPushes.Get() && GetGlobals()) + { + Vector vecEntBaseVelocity = pOther->m_vecBaseVelocity; + Vector vecOrigPush = vecAbsDir * pPush->m_flSpeed(); + + Message("Pushing entity %i | frame = %i | tick = %i | entity basevelocity %s = %.2f %.2f %.2f | original push velocity = %.2f %.2f %.2f | final push velocity = %.2f %.2f %.2f\n", pOther->GetEntityIndex(), GetGlobals()->framecount, GetGlobals()->tickcount, (flags & FL_BASEVELOCITY) ? "WITH FLAG" : "", vecEntBaseVelocity.x, vecEntBaseVelocity.y, vecEntBaseVelocity.z, vecOrigPush.x, vecOrigPush.y, vecOrigPush.z, vecPush.x, vecPush.y, vecPush.z); + } + + pOther->m_vecBaseVelocity(vecPush); + + flags |= FL_BASEVELOCITY; + pOther->m_fFlags(flags); + + return {KHook::Action::Supersede}; +} + +KHook::Return CHookManager::Hook_IsHearingClient(void* serverClient, int index) +{ + ZEPlayer* player = g_playerManager->GetPlayer(index); + if (player && player->IsMuted()) return {KHook::Action::Supersede, false}; + + return {KHook::Action::Ignore}; +} + +static KHook::Return SayChatMessageWithTimer(IRecipientFilter& filter, const char* pText, CCSPlayerController* pPlayer, uint64 eMessageType) +{ + VPROF("SayChatMessageWithTimer"); + + char buf[256]; + + uint32 uiTextLength = strlen(pText); + uint32 uiFilteredTextLength = 0; + char filteredText[256]; + + for (uint32 i = 0; i < uiTextLength; i++) + { + if (pText[i] >= 'A' && pText[i] <= 'Z') filteredText[uiFilteredTextLength++] = pText[i] + 32; + if (pText[i] == ' ' || (pText[i] >= '0' && pText[i] <= '9') || (pText[i] >= 'a' && pText[i] <= 'z')) filteredText[uiFilteredTextLength++] = pText[i]; + } + filteredText[uiFilteredTextLength] = '\0'; + + CSplitString words(filteredText, " "); + + int iWordCount = words.Count(); + uint32 uiTriggerTimerLength = 0; + + if (iWordCount == 2) uiTriggerTimerLength = V_StringToUint32(words.Element(1), 0, NULL, NULL, PARSING_FLAG_SKIP_WARNING); + + for (int i = 1; i < iWordCount && uiTriggerTimerLength == 0; i++) + { + uint32 uiCurrentValue = V_StringToUint32(words.Element(i), 0, NULL, NULL, PARSING_FLAG_SKIP_WARNING); + uint32 uiNextWordLength = 0; + char* pNextWord = NULL; + + if (i + 1 < iWordCount) + { + pNextWord = words.Element(i + 1); + uiNextWordLength = strlen(pNextWord); + } + + if (pNextWord != NULL && uiCurrentValue > 0) + { + if (uiNextWordLength == 1) + { + if (pNextWord[0] == 's') uiTriggerTimerLength = uiCurrentValue; + } + else if (uiNextWordLength > 2) + { + if (pNextWord[0] == 's' && pNextWord[1] == 'e' && pNextWord[2] == 'c') uiTriggerTimerLength = uiCurrentValue; + if (pNextWord[0] == 'm' && pNextWord[1] == 'i' && pNextWord[2] == 'n') uiTriggerTimerLength = uiCurrentValue * 60; + } + } + + if (uiCurrentValue == 0) + { + char* pCurrentWord = words.Element(i); + uint32 uiCurrentScanLength = MIN(strlen(pCurrentWord), 4); + + for (uint32 j = 0; j < uiCurrentScanLength; j++) + { + if (pCurrentWord[j] >= '0' && pCurrentWord[j] <= '9') continue; + + if (pCurrentWord[j] == 's') + { + pCurrentWord[j] = '\0'; + uiTriggerTimerLength = V_StringToUint32(pCurrentWord, 0, NULL, NULL, PARSING_FLAG_SKIP_WARNING); + } + break; + } + } + } + + float fCurrentRoundClock = g_pGameRules->m_iRoundTime - (GetGlobals()->curtime - g_pGameRules->m_fRoundStartTime.Get().GetTime()); + + if ((uiTriggerTimerLength > 4) && (fCurrentRoundClock > uiTriggerTimerLength)) + { + int iTriggerTime = fCurrentRoundClock - uiTriggerTimerLength; + + if ((int)(fCurrentRoundClock - 0.5f) == (int)fCurrentRoundClock) iTriggerTime++; + + int mins = iTriggerTime / 60; + int secs = iTriggerTime % 60; + + V_snprintf(buf, sizeof(buf), "%s %s %s %2d:%02d", " \7CONSOLE:\4", pText + sizeof("Console:"), "\x10- @", mins, secs); + } + else V_snprintf(buf, sizeof(buf), "%s %s", " \7CONSOLE:\4", pText + sizeof("Console:")); + + return KHook::Recall(nullptr, {KHook::Action::Ignore}, filter, buf, pPlayer, eMessageType); +} + +KHook::Return CHookManager::Hook_SayTextFilter(IRecipientFilter& filter, const char* pText, CCSPlayerController* pPlayer, uint64 eMessageType) +{ + if (pPlayer) return {KHook::Action::Ignore}; + + if (g_cvarEnableTriggerTimer.Get() && GetGlobals() && g_pGameRules) return SayChatMessageWithTimer(filter, pText, pPlayer, eMessageType); + + char buf[256]; + V_snprintf(buf, sizeof(buf), "%s %s", " \7CONSOLE:\4", pText + sizeof("Console:")); + + return KHook::Recall(nullptr, {KHook::Action::Ignore}, filter, buf, pPlayer, eMessageType); +} + +KHook::Return CHookManager::Hook_SayText2Filter(IRecipientFilter& filter, CCSPlayerController* pEntity, uint64 eMessageType, const char* msg_name, const char* param1, const char* param2, const char* param3, const char* param4) +{ +#ifdef _DEBUG + CPlayerSlot slot = filter.GetRecipientIndex(0); + CCSPlayerController* target = CCSPlayerController::FromSlot(slot); + + if (target) + Message("Chat from %s to %s: %s\n", param1, target->GetPlayerName().c_str(), param2); +#endif + + return KHook::Recall(nullptr, {KHook::Action::Ignore}, filter, pEntity, eMessageType, msg_name, pEntity->GetPlayerName().c_str(), param2, param3, param4); +} + +KHook::Return CHookManager::Hook_CanUse(CCSPlayer_WeaponServices* pWeaponServices, CBasePlayerWeapon* pPlayerWeapon) +{ + if (g_cvarEnableEntWatch.Get() && !EW_Detour_CCSPlayer_WeaponServices_CanUse(pWeaponServices, pPlayerWeapon)) return {KHook::Action::Supersede, false}; + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_EquipWeapon(CCSPlayer_WeaponServices* pWeaponServices, CBasePlayerWeapon* pPlayerWeapon) +{ + if (g_cvarEnableEntWatch.Get()) EW_Detour_CCSPlayer_WeaponServices_EquipWeapon(pWeaponServices, pPlayerWeapon); + + g_pMapMigrations->OnEquipWeapon(pPlayerWeapon); + + return {KHook::Action::Ignore}; +} + +static bool PrepareMapSetModel(CBaseModelEntity* pModel) +{ + if (!pModel->IsPawn()) return true; + + if (g_cvarDisableSetModel.Get() || g_pMapMigrations->Migrations20260420Enabled()) return false; + + int originalAlpha = pModel->m_clrRender().a(); + pModel->m_clrRender = Color(255, 255, 255, originalAlpha); + + return true; +} + +KHook::Return CHookManager::Hook_AcceptInput(CEntityIdentity* pThis, CUtlSymbolLarge* pInputName, CEntityInstance* pActivator, CEntityInstance* pCaller, variant_t* value, int nOutputID, void* a7, void* a8) +{ + VPROF_SCOPE_BEGIN("CHookManager::Hook_AcceptInput"); + + if (g_cvarEnableZR.Get()) + { + bool result = ZR_Detour_CEntityIdentity_AcceptInput(pThis, pInputName, pActivator, pCaller, value, nOutputID); + + if (!result) return {KHook::Action::Supersede, result}; + } + + if (!V_strnicmp(pInputName->String(), "KeyValue", 8)) + { + if ((value->m_type == FIELD_CSTRING || value->m_type == FIELD_STRING) && value->m_pszString) + { + return {KHook::Action::Supersede, CustomIO_HandleInput(pThis->m_pInstance, value->m_pszString, pActivator, pCaller)}; + } + Message("Invalid value type for input %s\n", pInputName->String()); + return {KHook::Action::Supersede, false}; + } + + if (!V_strnicmp(pInputName->String(), "IgniteL", 7)) + { + float flDuration = 0.f; + + if ((value->m_type == FIELD_CSTRING || value->m_type == FIELD_STRING) && value->m_pszString) flDuration = V_StringToFloat32(value->m_pszString, 0.f); + else flDuration = value->m_float32; + + CCSPlayerPawn* pPawn = reinterpret_cast(pThis->m_pInstance); + + if (pPawn->IsPawn() && IgnitePawn(pPawn, flDuration, pPawn, pPawn)) return {KHook::Action::Supersede, true}; + } + else if (!V_strnicmp(pInputName->String(), "AddScore", 8)) + { + int iScore = 0; + + if ((value->m_type == FIELD_CSTRING || value->m_type == FIELD_STRING) && value->m_pszString) iScore = V_StringToInt32(value->m_pszString, 0); + else iScore = value->m_int32; + + CCSPlayerPawn* pPawn = reinterpret_cast(pThis->m_pInstance); + + if (pPawn->IsPawn() && pPawn->GetOriginalController()) + { + pPawn->GetOriginalController()->AddScore(iScore); + return {KHook::Action::Supersede, true}; + } + } + else if (!V_strcasecmp(pInputName->String(), "SetMessage")) + { + if (const auto pHudHint = reinterpret_cast(pThis->m_pInstance)->AsHudHint()) + { + if ((value->m_type == FIELD_CSTRING || value->m_type == FIELD_STRING) && value->m_pszString) pHudHint->m_iszMessage(GameEntitySystem()->AllocPooledString(value->m_pszString)); + return {KHook::Action::Supersede, true}; + } + } + else if (!V_strcasecmp(pInputName->String(), "SetModel")) + { + if (const auto pModelEntity = reinterpret_cast(pThis->m_pInstance)->AsBaseModelEntity()) + { + if ((value->m_type == FIELD_CSTRING || value->m_type == FIELD_STRING) && value->m_pszString && PrepareMapSetModel(pModelEntity)) pModelEntity->SetModel(value->m_pszString); + + return {KHook::Action::Supersede, true}; + } + } + else if (const auto pGameUI = reinterpret_cast(pThis->m_pInstance)->AsGameUI()) + { + if (!V_strcasecmp(pInputName->String(), "Activate")) return {KHook::Action::Supersede, CGameUIHandler::OnActivate(pGameUI, reinterpret_cast(pActivator))}; + if (!V_strcasecmp(pInputName->String(), "Deactivate")) return {KHook::Action::Supersede, CGameUIHandler::OnDeactivate(pGameUI, reinterpret_cast(pActivator))}; + } + else if (const auto pViewControl = reinterpret_cast(pThis->m_pInstance)->AsPointViewControl()) + { + if (!V_strcasecmp(pInputName->String(), "EnableCamera")) return {KHook::Action::Supersede, CPointViewControlHandler::OnEnable(pViewControl, reinterpret_cast(pActivator))}; + if (!V_strcasecmp(pInputName->String(), "DisableCamera")) return {KHook::Action::Supersede, CPointViewControlHandler::OnDisable(pViewControl, reinterpret_cast(pActivator))}; + if (!V_strcasecmp(pInputName->String(), "EnableCameraAll")) return {KHook::Action::Supersede, CPointViewControlHandler::OnEnableAll(pViewControl)}; + if (!V_strcasecmp(pInputName->String(), "DisableCameraAll")) return {KHook::Action::Supersede, CPointViewControlHandler::OnDisableAll(pViewControl)}; + } + + VPROF_SCOPE_END(); + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_GetNearestNavArea(CNavMesh* pNavMesh, float* unk2, unsigned int* unk3, unsigned int unk4, int64_t unk5, float unk6, int64_t unk7) +{ + if (g_cvarBlockNavLookup.Get()) return {KHook::Action::Supersede, nullptr}; + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_ProcessMovement(CCSPlayer_MovementServices* pThis, void* pMove) +{ + CCSPlayerPawn* pPawn = pThis->GetPawn(); + + if (!pPawn->IsAlive() || !GetGlobals()) return {KHook::Action::Ignore}; + + CCSPlayerController* pController = pPawn->GetOriginalController(); + + if (!pController || !pController->IsConnected()) return {KHook::Action::Ignore}; + + float flSpeedMod = pController->GetZEPlayer()->GetSpeedMod(); + + if (flSpeedMod == 1.f) return {KHook::Action::Ignore}; + + m_flStoreFrametime = GetGlobals()->frametime; + GetGlobals()->frametime *= flSpeedMod; + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_ProcessMovement_Post(CCSPlayer_MovementServices* pThis, void* pMove) +{ + GetGlobals()->frametime = m_flStoreFrametime; + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_ProcessUsercmds(CCSPlayerController* pController, CUserCmd* cmds, int numcmds, bool paused, float margin) +{ + VPROF_SCOPE_BEGIN("CHookManager::Hook_ProcessUsercmds"); + + for (int i = 0; i < numcmds; i++) + { + if (g_cvarDisableSubtickMovement.Get() || g_cvarUseOldPush.Get()) + { + auto subtickMoves = cmds[i].cmd.mutable_base()->mutable_subtick_moves(); + auto iterator = subtickMoves->begin(); + + while (iterator != subtickMoves->end()) + { + uint64 button = iterator->button(); + + if (button >= IN_DUCK && button <= IN_MOVERIGHT && button != IN_USE) + { + subtickMoves->erase(iterator); + } + else + { + if (iterator->pitch_delta() != 0.0f) iterator->set_pitch_delta(0.0f); + + if (iterator->yaw_delta() != 0.0f) iterator->set_yaw_delta(0.0f); + + iterator++; + } + } + } + + if (g_cvarDisableSubtickShooting.Get()) + { + cmds[i].cmd.set_attack1_start_history_index(-1); + cmds[i].cmd.set_attack2_start_history_index(-1); + cmds[i].cmd.mutable_input_history()->Clear(); + } + } + + VPROF_SCOPE_END(); + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_InputTriggerForAllPlayers(CGamePlayerEquip* pEntity, InputData_t* pInput) +{ + CGamePlayerEquipHandler::TriggerForAllPlayers(pEntity, pInput); + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_InputTriggerForActivatedPlayer(CGamePlayerEquip* pEntity, InputData_t* pInput) +{ + if (CGamePlayerEquipHandler::TriggerForActivatedPlayer(pEntity, pInput)) return {KHook::Action::Ignore}; + + return {KHook::Action::Supersede}; +} + +KHook::Return CHookManager::Hook_GravityTouch(CTriggerGravity* pEntity, CBaseEntity* pOther) +{ + if (CTriggerGravityHandler::GravityTouching(pEntity, pOther)) return {KHook::Action::Supersede}; + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_GetFreeClient(int64_t unk1, const __m128i* unk2, unsigned int unk3, int64_t unk4, char unk5, void* unk6) +{ + if (!GetClientList() || !GetGlobals()) return {KHook::Action::Supersede, nullptr}; + + if (GetGlobals()->maxClients != GetClientList()->Count()) return {KHook::Action::Ignore}; + + for (int i = 0; i < GetClientList()->Count(); i++) + { + CServerSideClient* pClient = (*GetClientList())[i]; + + if (pClient && pClient->GetSignonState() < SIGNONSTATE_CONNECTED) return {KHook::Action::Supersede, pClient}; + } + + return {KHook::Action::Supersede, nullptr}; +} + +KHook::Return CHookManager::Hook_GetMaxSpeed(CCSPlayerPawn* pPawn) +{ + auto flMaxSpeed = m_hGetMaxSpeed->CallOriginal(pPawn); + + const auto pController = reinterpret_cast(pPawn->GetController()); + if (const auto pPlayer = pController != nullptr ? pController->GetZEPlayer() : nullptr) flMaxSpeed *= pPlayer->GetMaxSpeed(); + + return {KHook::Action::Supersede, flMaxSpeed}; +} + +KHook::Return CHookManager::Hook_FindUseEntity(CCSPlayer_UseServices* pThis, float a2) +{ + m_bFindingUseEntity = true; + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_FindUseEntity_Post(CCSPlayer_UseServices* pThis, float a2) +{ + m_bFindingUseEntity = false; + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_TraceFunc(int64* a1, int* a2, float* a3, uint64 traceMask) +{ + if (g_cvarPreventUsingPlayers.Get() && m_bFindingUseEntity) + { + uint64 newMask = traceMask & (~(CONTENTS_PLAYER & CONTENTS_NPC)); + KHook::Recall(nullptr, {KHook::Action::Ignore}, a1, a2, a3, newMask); + } + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_TraceShape(int64* a1, int64 a2, int64 a3, int64 a4, CTraceFilter* filter, int64 a6) +{ + if (g_cvarPreventUsingPlayers.Get() && m_bFindingUseEntity) + { + filter->DisableInteractsWithLayer(LAYER_INDEX_CONTENTS_PLAYER); + filter->DisableInteractsWithLayer(LAYER_INDEX_CONTENTS_NPC); + } + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_FireOutputInternal(const CEntityIOOutput* pThis, CEntityInstance* pActivator, CEntityInstance* pCaller, const CVariant* value, float flDelay, void* a6, void* a7) +{ + if (g_cvarEnableButtonWatch.Get()) ButtonWatch(pThis, pActivator, pCaller, value, flDelay); + + if (g_cvarEnableEntWatch.Get()) EW_FireOutput(pThis, pActivator, pCaller, value, flDelay); + + return {KHook::Action::Ignore}; +} + +#ifdef PLATFORM_WINDOWS +KHook::Return CHookManager::Hook_GetEyePosition(CBasePlayerPawn* pPawn, Vector* pRet) +{ + if (pPawn->IsAlive() && CPointViewControlHandler::IsViewControl(reinterpret_cast(pPawn))) + { + const auto& origin = pPawn->GetEyePosition(); + pRet->Init(origin.x, origin.y, origin.z); + return {KHook::Action::Supersede, pRet}; + } + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_GetEyeAngles(CBasePlayerPawn* pPawn, QAngle* pRet) +{ + if (pPawn->IsAlive() && CPointViewControlHandler::IsViewControl(reinterpret_cast(pPawn))) + { + const auto& angles = pPawn->v_angle(); + pRet->Init(angles.x, angles.y, angles.z); + return {KHook::Action::Supersede, pRet}; + } + + return {KHook::Action::Ignore}; +} +#else +KHook::Return CHookManager::Hook_GetEyePosition(CBasePlayerPawn* pPawn) +{ + if (pPawn->IsAlive() && CPointViewControlHandler::IsViewControl(reinterpret_cast(pPawn))) + { + const auto& origin = pPawn->GetEyePosition(); + return {KHook::Action::Supersede, origin}; + } + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_GetEyeAngles(CBasePlayerPawn* pPawn) +{ + if (pPawn->IsAlive() && CPointViewControlHandler::IsViewControl(reinterpret_cast(pPawn))) + { + const auto& angles = pPawn->v_angle(); + return {KHook::Action::Supersede, angles}; + } + + return {KHook::Action::Ignore}; +} +#endif + +KHook::Return CHookManager::Hook_InputTestActivator(CBaseFilter* pThis, InputData_t& inputdata) +{ + if (!inputdata.pActivator) return {KHook::Action::Supersede}; + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_CheckSteamBan_Post() +{ + if (!g_cvarFixGameBans.Get()) return {KHook::Action::Ignore}; + + auto pMap = addresses::sm_mapGcBanInformation; + + if (pMap->Count() > 0) pMap->RemoveAll(); + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_CanAcquire(CCSPlayer_ItemServices* pItemServices, CEconItemView* pEconItem, AcquireMethod iAcquireMethod, uint64_t unk4) +{ + if (g_cvarEnableZR.Get()) + { + AcquireResult zrResult = ZR_Detour_CCSPlayer_ItemServices_CanAcquire(pItemServices, pEconItem); + + if (zrResult != AcquireResult::Allowed) return {KHook::Action::Supersede, zrResult}; + } + + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_ScriptSetModel(uint64_t unk1) +{ + m_bInScriptSetModel = true; + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_ScriptSetModel_Post(uint64_t unk1) +{ + m_bInScriptSetModel = false; + return {KHook::Action::Ignore}; +} + +KHook::Return CHookManager::Hook_SetModel(CBaseModelEntity* pModel, const char* pszModel) +{ + if (!m_bInScriptSetModel) return {KHook::Action::Ignore}; + + if (PrepareMapSetModel(pModel)) return {KHook::Action::Ignore}; + + return {KHook::Action::Supersede}; +} + +KHook::Return CHookManager::Hook_GoToIntermission(CCSGameRules* pThis, bool bAbortedMatch) +{ + if (!g_pMapVoteSystem->IsIntermissionAllowed(false) && g_cvarVoteManagerEnable.Get()) return {KHook::Action::Supersede}; + + if (g_cvarVoteManagerEnable.Get()) g_pVoteManager->OnIntermission(); + + return {KHook::Action::Ignore}; +} diff --git a/src/hookmanager.h b/src/hookmanager.h new file mode 100644 index 00000000..3e077f7e --- /dev/null +++ b/src/hookmanager.h @@ -0,0 +1,292 @@ +/** + * ============================================================================= + * 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 "cs2_sdk/entityio.h" +#include "interfaces/interfaces.h" +#include "khook.hpp" +#include + +class CCheckTransmitInfo; +class IRecipientFilter; +class ISoundEmitterSystemBase; +class CBaseEntity; +class CBaseFilter; +class CCSPlayerController; +class CEntityIndex; +class CCommand; +class CTriggerPush; +class CTriggerGravity; +class CGameConfig; +class CGameRules; +class CTakeDamageInfo; +class CCSPlayer_WeaponServices; +class CCSPlayer_MovementServices; +class CCSPlayer_ItemServices; +class CBasePlayerWeapon; +class INetworkMessageInternal; +class IEngineServiceMgr; +class CServerSideClient; +class INetChannel; +class CBasePlayerPawn; +class CUserCmd; +class CGamePlayerEquip; +class InputData_t; +class CCSPlayerPawn; +class CCSPlayer_UseServices; +class CTraceFilter; +class CNavMesh; +class CBaseModelEntity; +class Vector; +class QAngle; +class CEconItemView; +class CCSGameRules; +struct CTakeDamageResult; +class INetworkServerService; +class IGameTypes; +class CEntitySystem; +class CVPhys2World; +class INetworkGameServer; +class IGameSpawnGroupMgr; +class IGameEventManager2; +class CBufferString; +class ISource2WorldSession; +class CEntityPrecacheContext; +class CNetMessage; +class CCommandContext; +class CPlayerSlot; +struct CSplitScreenSlot; +struct ConCommandRef; +struct EntitySpawnInfo_t; +struct Entity2Networkable_t; +struct GameSessionConfiguration_t; +class KeyValues; +class CUtlStringList; +template class CBitVec; +template class CUtlVector; + +enum ENetworkDisconnectionReason : int; +enum NetChannelBufType_t : signed char; + +enum class AcquireMethod +{ + PickUp, + Buy, +}; + +enum class AcquireResult +{ + Allowed, + InvalidItem, + AlreadyOwned, + AlreadyPurchased, + ReachedGrenadeTypeLimit, + ReachedGrenadeTotalLimit, + NotAllowedByTeam, + NotAllowedByMap, + NotAllowedByMode, + NotAllowedForPurchase, + NotAllowedByProhibition, +}; + +struct TouchLinked_t +{ + uint32_t TouchFlags; + +private: + uint8_t padding_0[20]; + +public: + CBaseHandle SourceHandle; + CBaseHandle TargetHandle; + +private: + uint8_t padding_1[224]; + +public: + [[nodiscard]] bool IsUnTouching() const + { + return !!(TouchFlags & 0x10); + } + + [[nodiscard]] bool IsTouching() const + { + return (!!(TouchFlags & 4)) || (!!(TouchFlags & 8)); + } +}; +static_assert(sizeof(TouchLinked_t) == 256, "Touch_t size mismatch"); + +class CHookManager +{ +public: + CHookManager(CGameConfig* pGameConfig); + ~CHookManager(); + + void CreateHooks(CGameConfig* gameConfig); + void RemoveHooks(); + +public: + KHook::Return Hook_TakeDamageOld(CBaseEntity*, CTakeDamageInfo*, CTakeDamageResult*); + KHook::Return Hook_TakeDamageOld_Post(CBaseEntity*, CTakeDamageInfo*, CTakeDamageResult*); + KHook::Return Hook_TriggerPushTouch(CTriggerPush*, CBaseEntity*); + KHook::Return Hook_IsHearingClient(void*, int); + KHook::Return Hook_SayTextFilter(IRecipientFilter&, const char*, CCSPlayerController*, uint64); + KHook::Return Hook_SayText2Filter(IRecipientFilter&, CCSPlayerController*, uint64, const char*, const char*, const char*, const char*, const char*); + KHook::Return Hook_CanUse(CCSPlayer_WeaponServices*, CBasePlayerWeapon*); + KHook::Return Hook_EquipWeapon(CCSPlayer_WeaponServices*, CBasePlayerWeapon*); + KHook::Return Hook_AcceptInput(CEntityIdentity*, CUtlSymbolLarge*, CEntityInstance*, CEntityInstance*, variant_t*, int, void*, void*); + KHook::Return Hook_GetNearestNavArea(CNavMesh*, float*, unsigned int*, unsigned int, int64_t, float, int64_t); + KHook::Return Hook_ProcessMovement(CCSPlayer_MovementServices*, void*); + KHook::Return Hook_ProcessMovement_Post(CCSPlayer_MovementServices*, void*); + KHook::Return Hook_ProcessUsercmds(CCSPlayerController*, CUserCmd*, int, bool, float); + KHook::Return Hook_InputTriggerForAllPlayers(CGamePlayerEquip*, InputData_t*); + KHook::Return Hook_InputTriggerForActivatedPlayer(CGamePlayerEquip*, InputData_t*); + KHook::Return Hook_GravityTouch(CTriggerGravity*, CBaseEntity*); + KHook::Return Hook_GetFreeClient(int64_t, const __m128i*, unsigned int, int64_t, char, void*); + KHook::Return Hook_GetMaxSpeed(CCSPlayerPawn*); + KHook::Return Hook_FindUseEntity(CCSPlayer_UseServices*, float); + KHook::Return Hook_FindUseEntity_Post(CCSPlayer_UseServices*, float); + KHook::Return Hook_TraceFunc(int64*, int*, float*, uint64); + KHook::Return Hook_TraceShape(int64*, int64, int64, int64, CTraceFilter*, int64); + KHook::Return Hook_FireOutputInternal(const CEntityIOOutput*, CEntityInstance*, CEntityInstance*, const CVariant*, float, void*, void*); +#ifdef PLATFORM_WINDOWS + KHook::Return Hook_GetEyePosition(CBasePlayerPawn*, Vector*); + KHook::Return Hook_GetEyeAngles(CBasePlayerPawn*, QAngle*); +#else + KHook::Return Hook_GetEyePosition(CBasePlayerPawn*); + KHook::Return Hook_GetEyeAngles(CBasePlayerPawn*); +#endif + KHook::Return Hook_InputTestActivator(CBaseFilter*, InputData_t&); + KHook::Return Hook_CheckSteamBan_Post(); + KHook::Return Hook_CanAcquire(CCSPlayer_ItemServices*, CEconItemView*, AcquireMethod, uint64_t); + KHook::Return Hook_ScriptSetModel(uint64_t); + KHook::Return Hook_ScriptSetModel_Post(uint64_t); + KHook::Return Hook_SetModel(CBaseModelEntity*, const char*); + KHook::Return Hook_GoToIntermission(CCSGameRules*, bool); + + KHook::Return Hook_GameFrame_Post(IServerGameDLL*, bool, bool, bool); + KHook::Return Hook_GameServerSteamAPIActivated(IServerGameDLL*); + KHook::Return Hook_ApplyGameSettings(IServerGameDLL*, KeyValues*); + KHook::Return Hook_ClientActive_Post(IServerGameClients*, CPlayerSlot, bool, const char*, uint64); + KHook::Return Hook_ClientDisconnect_Post(IServerGameClients*, CPlayerSlot, ENetworkDisconnectionReason, const char*, uint64, const char*); + KHook::Return Hook_ClientPutInServer_Post(IServerGameClients*, CPlayerSlot, const char*, int, uint64); + KHook::Return Hook_ClientSettingsChanged(IServerGameClients*, CPlayerSlot); + KHook::Return Hook_OnClientConnected(IServerGameClients*, CPlayerSlot, const char*, uint64, const char*, const char*, bool); + KHook::Return Hook_ClientConnect(IServerGameClients*, CPlayerSlot, const char*, uint64, const char*, bool, CBufferString*); + KHook::Return Hook_ClientCommand(IServerGameClients*, CPlayerSlot, const CCommand&); + KHook::Return Hook_PostEventAbstract(IGameEventSystem*, CSplitScreenSlot, bool, int, const uint64*, INetworkMessageInternal*, const CNetMessage*, unsigned long, NetChannelBufType_t); + KHook::Return Hook_StartupServer_Post(INetworkServerService*, const GameSessionConfiguration_t&, ISource2WorldSession*, const char*); + KHook::Return Hook_CheckTransmit_Post(ISource2GameEntities*, CCheckTransmitInfo**, int, CBitVec<16384>&, CBitVec<16384>&, const Entity2Networkable_t**, const uint16*, int); + KHook::Return Hook_DispatchConCommand(ICvar*, ConCommandRef, const CCommandContext&, const CCommand&); + KHook::Return Hook_LoadEventsFromFile(IGameEventManager2*, const char*, bool); + KHook::Return Hook_Spawn_Post(CEntitySystem*, int, const EntitySpawnInfo_t*); + KHook::Return Hook_SetGameSpawnGroupMgr(INetworkGameServer*, IGameSpawnGroupMgr*); + KHook::Return Hook_CreateWorkshopMapGroup(IGameTypes*, const char*, const CUtlStringList&); + KHook::Return Hook_GetTouchingList_Post(CVPhys2World*, CUtlVector*, bool); + KHook::Return Hook_CheckMovingGround(CCSPlayer_MovementServices*, double); + KHook::Return Hook_DropWeapon_Post(CCSPlayer_WeaponServices*, CBasePlayerWeapon*, Vector*, Vector*); + KHook::Return Hook_PlayerEquipUse(CGamePlayerEquip*, InputData_t*); + KHook::Return Hook_PlayerEquipPrecache_Post(CGamePlayerEquip*, CEntityPrecacheContext*); + KHook::Return Hook_TriggerGravityPrecache_Post(CTriggerGravity*, CEntityPrecacheContext*); + KHook::Return Hook_TriggerGravityEndTouch_Post(CTriggerGravity*, CBaseEntity*); + KHook::Return Hook_OnTakeDamage_Alive(CCSPlayerPawn*, CTakeDamageResult*); + KHook::Return Hook_CCSPlayerPawn_Teleport(CCSPlayerPawn*, const Vector*, const QAngle*, const Vector*); + +protected: + // Inline function hooks + KHook::Member* m_hTakeDamageOld; + KHook::Member* m_hTriggerPushTouch; + KHook::Function* m_hIsHearingClient; + KHook::Function* m_hSayTextFilter; + KHook::Function* m_hSayText2Filter; + KHook::Member* m_hCanUse; + KHook::Member* m_hEquipWeapon; + KHook::Member* m_hAcceptInput; + KHook::Member* m_hGetNearestNavArea; + KHook::Member* m_hProcessMovement; + KHook::Member* m_hProcessUsercmds; + KHook::Member* m_hInputTriggerForAllPlayers; + KHook::Member* m_hInputTriggerForActivatedPlayer; + KHook::Member* m_hGravityTouch; + KHook::Function* m_hGetFreeClient; + KHook::Member* m_hGetMaxSpeed; + KHook::Member* m_hFindUseEntity; + KHook::Function* m_hTraceFunc; + KHook::Function* m_hTraceShape; + KHook::Member* m_hFireOutputInternal; +#ifdef PLATFORM_WINDOWS + KHook::Member* m_hGetEyePosition; + KHook::Member* m_hGetEyeAngles; +#else + KHook::Member* m_hGetEyePosition; + KHook::Member* m_hGetEyeAngles; +#endif + KHook::Member* m_hInputTestActivator; + KHook::Function* m_hCheckSteamBan; + KHook::Member* m_hCanAcquire; + KHook::Function* m_hScriptSetModel; + KHook::Member* m_hSetModel; + KHook::Member* m_hGoToIntermission; + + // Virtual function hooks + KHook::Virtual* m_hGameFrame; + KHook::Virtual* m_hGameServerSteamAPIActivated; + KHook::Virtual* m_hApplyGameSettings; + KHook::Virtual* m_hClientActive; + KHook::Virtual* m_hClientDisconnect; + KHook::Virtual* m_hClientPutInServer; + KHook::Virtual* m_hClientSettingsChanged; + KHook::Virtual* m_hOnClientConnected; + KHook::Virtual* m_hClientConnect; + KHook::Virtual* m_hClientCommand; + KHook::Virtual* m_hPostEventAbstract; + KHook::Virtual* m_hStartupServer; + KHook::Virtual&, CBitVec<16384>&, const Entity2Networkable_t**, const uint16*, int>* m_hCheckTransmit; + KHook::Virtual* m_hDispatchConCommand; + KHook::Virtual* m_hLoadEventsFromFile; + KHook::Virtual* m_hSpawn; + KHook::Virtual* m_hSetGameSpawnGroupMgr; + KHook::Virtual* m_hCreateWorkshopMapGroup; + KHook::Virtual*, bool>* m_hGetTouchingList; + KHook::Virtual* m_hCheckMovingGround; + KHook::Virtual* m_hDropWeapon; + KHook::Virtual* m_hPlayerEquipUse; + KHook::Virtual* m_hPlayerEquipPrecache; + KHook::Virtual* m_hTriggerGravityPrecache; + KHook::Virtual* m_hTriggerGravityEndTouch; + KHook::Virtual* m_hOnTakeDamageAlive; + KHook::Virtual* m_hPlayerPawnTeleport; + +protected: + void* m_pCGameEventManagerVTable = nullptr; + void* m_pCEntitySystemVTable = nullptr; + void* m_pCVPhys2WorldVTable = nullptr; + void* m_pCCSPlayer_MovementServicesVTable = nullptr; + void* m_pCCSPlayer_WeaponServicesVTable = nullptr; + void* m_pCGamePlayerEquipVTable = nullptr; + void* m_pTriggerGravityVTable = nullptr; + void* m_pCCSPlayerPawnVTable = nullptr; + +private: + float m_flStoreFrametime = 0.f; + bool m_bFindingUseEntity = false; + bool m_bInScriptSetModel = false; +}; + +extern CHookManager* g_pHookManager; diff --git a/src/topdefender.cpp b/src/topdefender.cpp index ddc7d026..d448c9b2 100644 --- a/src/topdefender.cpp +++ b/src/topdefender.cpp @@ -21,7 +21,6 @@ #include "commands.h" #include "common.h" #include "ctimer.h" -#include "detours.h" #include "entity/ccsplayercontroller.h" #include "idlemanager.h" #include "playermanager.h"