diff --git a/CS2Fixes.vcxproj b/CS2Fixes.vcxproj
index cab59817..cd95b3a2 100644
--- a/CS2Fixes.vcxproj
+++ b/CS2Fixes.vcxproj
@@ -277,6 +277,7 @@
+
@@ -317,6 +318,7 @@
+
diff --git a/CS2Fixes.vcxproj.filters b/CS2Fixes.vcxproj.filters
index fd62f6b5..86bf79c0 100644
--- a/CS2Fixes.vcxproj.filters
+++ b/CS2Fixes.vcxproj.filters
@@ -412,6 +412,9 @@
Header Files\cs2_sdk
+
+ Header Files\cs2_sdk
+
Header Files
@@ -451,7 +454,7 @@
Header Files\utils
-
+
Header Files\utils
diff --git a/gamedata/cs2fixes.jsonc b/gamedata/cs2fixes.jsonc
index 8f87e301..11b272f7 100644
--- a/gamedata/cs2fixes.jsonc
+++ b/gamedata/cs2fixes.jsonc
@@ -372,6 +372,13 @@
"library": "server",
"windows": "48 83 EC ? 4C 8B C2 48 8D 0D ? ? ? ? 48 8D 54 24 ? FF",
"linux": "55 48 89 F2 48 8D 35 ? ? ? ? 48 89 E5 48 83 EC"
+ },
+ // "CWorldRendererMgr::CreateWorld_Internal( %s ): Blocking load because marked for deletion during load"
+ "CWorldRendererMgr_CreateWorld_Internal":
+ {
+ "library": "worldrenderer",
+ "windows": "48 89 5C 24 ? 48 89 54 24 ? 55 56 57 48 81 EC",
+ "linux": "55 48 89 E5 41 56 41 55 41 54 49 89 FC 53 48 89 F3 48 83 EC ? F6 46"
}
},
"Offsets":
diff --git a/src/addresses.cpp b/src/addresses.cpp
index f846e2cf..9b4ce84d 100644
--- a/src/addresses.cpp
+++ b/src/addresses.cpp
@@ -27,10 +27,10 @@
#include "tier0/memdbgon.h"
-#define RESOLVE_SIG(name, variable) \
+#define RESOLVE_SIG(name, variable) \
variable = (decltype(variable))g_GameConfig->ResolveSignature(name); \
- if (!variable) \
- return false; \
+ if (!variable) \
+ return false; \
Message("Found %s at 0x%p\n", name, variable);
#define RESOLVE_SF(scriptDesc, funcName, variable) \
@@ -51,6 +51,7 @@ bool addresses::Initialize()
modules::networksystem = new CModule(ROOTBIN, "networksystem");
modules::vphysics2 = new CModule(ROOTBIN, "vphysics2");
modules::matchmaking = new CModule(GAMEBIN, "matchmaking");
+ modules::worldrenderer = new CModule(ROOTBIN, "worldrenderer");
modules::client = nullptr;
if (!CommandLine()->HasParm("-dedicated"))
diff --git a/src/addresses.h b/src/addresses.h
index b69d453a..09ac769e 100644
--- a/src/addresses.h
+++ b/src/addresses.h
@@ -36,6 +36,7 @@ namespace modules
inline CModule* networksystem;
inline CModule* vphysics2;
inline CModule* matchmaking;
+ inline CModule* worldrenderer;
#ifdef _WIN32
inline CModule* hammer;
#endif
diff --git a/src/cfgparser.cpp b/src/cfgparser.cpp
index adfe3adf..5b80cf7a 100644
--- a/src/cfgparser.cpp
+++ b/src/cfgparser.cpp
@@ -24,18 +24,19 @@
CCfgParser* g_pCfgParser = nullptr;
-CON_COMMAND_F(exec_custom, " - Execute a cfg through the custom cfg parser", FCVAR_SPONLY | FCVAR_LINKED_CONCOMMAND)
+void CCfgParser::PreLevelLoad(const char* pszMapName)
{
- if (args.ArgC() < 2)
- {
- Message("Usage: exec_custom \n");
- return;
- }
-
- g_pCfgParser->ParseCfg(args[1]);
+ // Make sure convars are set before map load, currently used by map migrations
+ ExecuteConfigs(pszMapName);
}
void CCfgParser::ApplyGameSettings(const char* pszMapName)
+{
+ // Execute again for good measure, because this is when configs would normally execute
+ ExecuteConfigs(pszMapName);
+}
+
+void CCfgParser::ExecuteConfigs(const char* pszMapName)
{
// Run plugin cfg
g_pCfgParser->ParseCfg("cs2fixes/cs2fixes");
@@ -47,10 +48,9 @@ void CCfgParser::ApplyGameSettings(const char* pszMapName)
return;
// Run map cfg (if present)
- // We call ParseCfg indirectly through exec_custom, so any commands within the map cfg will be added to the command buffer after nested executes in previous configs
- char cmd[MAX_PATH];
- V_snprintf(cmd, sizeof(cmd), "exec_custom cs2fixes/maps/%s", pszMapName);
- g_pEngineServer2->ServerCommand(cmd);
+ char szCfgPath[MAX_PATH];
+ V_snprintf(szCfgPath, sizeof(szCfgPath), "cs2fixes/maps/%s", pszMapName);
+ ParseCfg(szCfgPath);
}
void CCfgParser::ParseCfg(const char* pszCfgPath)
@@ -71,10 +71,39 @@ void CCfgParser::ParseCfg(const char* pszCfgPath)
while (std::getline(cfgFile, strCommand))
{
- if (!strCommand.empty() && strCommand.back() == '\r')
- strCommand.pop_back();
+ CCommand args;
+
+ if (!args.Tokenize(strCommand.c_str()) || !args.ArgC())
+ continue;
+
+ if (!V_strcasecmp(args[0], "exec_custom"))
+ {
+ if (args.ArgC() < 2)
+ Message("Usage: exec_custom \n");
+ else
+ ParseCfg(args[1]);
+
+ continue;
+ }
+
+ ConCommandRef command(args[0], true);
+ ConVarRefAbstract convar(args[0], true);
+
+ if (command.IsValidRef())
+ {
+ CCommandContext context(CT_FIRST_SPLITSCREEN_CLIENT, -1);
+ command.Dispatch(context, args);
+ continue;
+ }
+
+ if (convar.IsValidRef())
+ {
+ if (args.ArgC() > 1 && !convar.SetString(args[1]))
+ Message("Failed to execute \"%s %s\"\n", args[0], args[1]);
+
+ continue;
+ }
- if (!strCommand.empty())
- g_pEngineServer2->ServerCommand(strCommand.c_str());
+ Message("Unknown command \"%s\"\n", args[0]);
}
}
diff --git a/src/cfgparser.h b/src/cfgparser.h
index 78433b7d..53f3296c 100644
--- a/src/cfgparser.h
+++ b/src/cfgparser.h
@@ -22,7 +22,9 @@
class CCfgParser
{
public:
+ void PreLevelLoad(const char* pszMapName);
void ApplyGameSettings(const char* pszMapName);
+ void ExecuteConfigs(const char* pszMapName);
void ParseCfg(const char* pszCfgPath);
};
diff --git a/src/commands.cpp b/src/commands.cpp
index b608cda5..5df95a71 100644
--- a/src/commands.cpp
+++ b/src/commands.cpp
@@ -1194,7 +1194,12 @@ CON_COMMAND_CHAT(uitest, " () - Spawn UI panel")
if (args.ArgC() > 2)
hLayout->SetDialogVariableString("MyLabel", "CustomText", args[2], player);
- hLayout->AddClickCallback([](CCSPlayerController* pController, CCSCustomHudLayout* pHud, std::string sButtonId) {
+ hLayout->SetDisconnectCallback([](CCSCustomHudLayout* pHud, int slot) {
+ CCSCustomHudLayout::DefaultOnDisconnect(pHud, slot);
+ Message("Custom disconnect callback called\n");
+ });
+
+ hLayout->SetClickCallback([](CCSPlayerController* pController, CCSCustomHudLayout* pHud, std::string sButtonId) {
ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "%s clicked %s on %s\n", pController->GetPlayerName().c_str(), sButtonId.c_str(), pHud->GetName());
if (sButtonId == "dismiss_button")
diff --git a/src/cs2_sdk/entity/customhudlayout.cpp b/src/cs2_sdk/entity/customhudlayout.cpp
index fc15346c..72f25a87 100644
--- a/src/cs2_sdk/entity/customhudlayout.cpp
+++ b/src/cs2_sdk/entity/customhudlayout.cpp
@@ -20,7 +20,7 @@
#include "customhudlayout.h"
#include "entity.h"
-std::unordered_map g_mapClickCallbacks;
+std::unordered_map CCSCustomHudLayout::sm_mapCustomLayoutCallbacks;
CCSCustomHudLayout* CCSCustomHudLayout::Create(std::string sLayout, std::string sTargetName)
{
@@ -37,34 +37,67 @@ CCSCustomHudLayout* CCSCustomHudLayout::Create(std::string sLayout, std::string
pKeyValues->SetString("targetname", sTargetName.c_str());
pLayout->DispatchSpawn(pKeyValues);
+ pLayout->SetDisconnectCallback(&DefaultOnDisconnect);
+
return pLayout;
}
-void CCSCustomHudLayout::ClearClickCallbacks()
+void CCSCustomHudLayout::ClearCallbacks()
{
- g_mapClickCallbacks.clear();
+ sm_mapCustomLayoutCallbacks.clear();
}
void CCSCustomHudLayout::OnClick(CCSPlayerController* pController, const std::string& sButtonId)
{
- if (auto it = g_mapClickCallbacks.find(GetHandle().ToInt()); it != g_mapClickCallbacks.end())
- it->second(pController, this, sButtonId);
+ if (auto it = sm_mapCustomLayoutCallbacks.find(GetHandle().ToInt()); it != sm_mapCustomLayoutCallbacks.end())
+ it->second.m_OnClick(pController, this, sButtonId);
+}
+
+void CCSCustomHudLayout::OnClientDisconnect(int slot)
+{
+ auto iterator = sm_mapCustomLayoutCallbacks.begin();
+
+ while (iterator != sm_mapCustomLayoutCallbacks.end())
+ {
+ auto pLayout = CHandle(iterator->first).Get();
+
+ if (!pLayout)
+ {
+ iterator = sm_mapCustomLayoutCallbacks.erase(iterator);
+ continue;
+ }
+
+ iterator->second.m_OnDisconnect(pLayout, slot);
+ iterator++;
+ }
+}
+
+void CCSCustomHudLayout::DefaultOnDisconnect(CCSCustomHudLayout* pLayout, int slot)
+{
+ pLayout->ClearClasses(slot);
+ pLayout->ClearDialogVariables(slot);
+ pLayout->SetInputCaptureEnabled(false, slot);
}
void CCSCustomHudLayout::OnEntityDeleted()
{
- g_mapClickCallbacks.erase(GetHandle().ToInt());
+ sm_mapCustomLayoutCallbacks.erase(GetHandle().ToInt());
}
-CCSCustomHudLayoutState& CCSCustomHudLayout::GetLayoutState(CCSPlayerController* pController)
+CCSCustomHudLayoutState& CCSCustomHudLayout::GetLayoutState(int nSlot)
{
- if (!pController)
+ if (nSlot < 0 || nSlot >= 64)
return *m_globalLayoutState;
- return *(CCSCustomHudLayoutState*)m_vecPlayerLayoutStates.GetManipulator()(SCHEMA_COLLECTION_MANIPULATOR_ACTION_GET_ELEMENT, m_vecPlayerLayoutStates, pController->GetPlayerSlot(), 0);
+ return *(CCSCustomHudLayoutState*)m_vecPlayerLayoutStates.GetManipulator()(SCHEMA_COLLECTION_MANIPULATOR_ACTION_GET_ELEMENT, m_vecPlayerLayoutStates, nSlot, 0);
}
-void CCSCustomHudLayout::SetHasClass(std::string sPanelId, std::string sClassName, bool bHasClass, CCSPlayerController* pController)
+CCSCustomHudLayoutState& CCSCustomHudLayout::GetLayoutState(CCSPlayerController* pController)
+{
+ return pController ? GetLayoutState(pController->GetPlayerSlot()) : *m_globalLayoutState;
+}
+
+void CCSCustomHudLayout::SetHasClass(std::string sPanelId, std::string sClassName, bool bHasClass, int nSlot)
{
auto panelIndex = m_vecPanelIds->Find(sPanelId.c_str());
@@ -76,7 +109,7 @@ void CCSCustomHudLayout::SetHasClass(std::string sPanelId, std::string sClassNam
if (classIndex == -1)
classIndex = m_vecClassNames->AddToTail(sClassName.c_str());
- auto& layoutState = GetLayoutState(pController);
+ auto& layoutState = GetLayoutState(nSlot);
HUDPanelHasClass_t hasClass(panelIndex, classIndex, bHasClass);
@@ -86,11 +119,14 @@ void CCSCustomHudLayout::SetHasClass(std::string sPanelId, std::string sClassNam
layoutState.m_vecHasClasses->AddToTail(hasClass);
else
layoutState.m_vecHasClasses->Element(hasClassIndex).m_eClassStatus = hasClass.m_eClassStatus;
+}
- layoutState.m_vecHasClasses.NetworkStateChanged();
+void CCSCustomHudLayout::SetHasClass(std::string sPanelId, std::string sClassName, bool bHasClass, CCSPlayerController* pController)
+{
+ SetHasClass(sPanelId, sClassName, bHasClass, pController ? pController->GetPlayerSlot() : -1);
}
-void CCSCustomHudLayout::SetDialogVariableString(std::string sPanelId, std::string sVariableName, std::string sValue, CCSPlayerController* pController)
+void CCSCustomHudLayout::SetDialogVariableString(std::string sPanelId, std::string sVariableName, std::string sValue, int nSlot)
{
auto panelIndex = m_vecPanelIds->Find(sPanelId.c_str());
@@ -102,23 +138,70 @@ void CCSCustomHudLayout::SetDialogVariableString(std::string sPanelId, std::stri
if (variableIndex == -1)
variableIndex = m_vecDialogVariableNames->AddToTail(sVariableName.c_str());
- auto& layoutState = GetLayoutState(pController);
+ auto& layoutState = GetLayoutState(nSlot);
HUDPanelDialogVariableString_t dialogVariable(panelIndex, variableIndex, sValue.c_str(), true);
auto dialogVariableIndex = layoutState.m_vecDialogVariableStrings->Find(dialogVariable);
if (dialogVariableIndex == -1)
+ {
layoutState.m_vecDialogVariableStrings->AddToTail(dialogVariable);
+ }
else
+ {
layoutState.m_vecDialogVariableStrings->Element(dialogVariableIndex).m_sValue = sValue.c_str();
+ layoutState.m_vecDialogVariableStrings->Element(dialogVariableIndex).m_bIsSet = true;
+ }
+}
- layoutState.m_vecDialogVariableStrings.NetworkStateChanged();
+void CCSCustomHudLayout::SetDialogVariableString(std::string sPanelId, std::string sVariableName, std::string sValue, CCSPlayerController* pController)
+{
+ SetDialogVariableString(sPanelId, sVariableName, sValue, pController ? pController->GetPlayerSlot() : -1);
+}
+
+void CCSCustomHudLayout::ClearClasses(int nSlot)
+{
+ auto& layoutState = GetLayoutState(nSlot);
+
+ for (int i = 0; i < layoutState.m_vecHasClasses->Count(); i++)
+ {
+ if (layoutState.m_vecHasClasses->Element(i).m_eClassStatus == k_eHudPanelClassStatus_Undefined)
+ continue;
+
+ auto pClass = (HUDPanelHasClass_t*)layoutState.m_vecHasClasses.GetManipulator()(SCHEMA_COLLECTION_MANIPULATOR_ACTION_GET_ELEMENT, layoutState.m_vecHasClasses, i, 0);
+ pClass->m_eClassStatus = k_eHudPanelClassStatus_Undefined;
+ }
+}
+
+void CCSCustomHudLayout::ClearDialogVariables(int nSlot)
+{
+ auto& layoutState = GetLayoutState(nSlot);
+
+ for (int i = 0; i < layoutState.m_vecDialogVariableStrings->Count(); i++)
+ {
+ if (!layoutState.m_vecDialogVariableStrings->Element(i).m_bIsSet)
+ continue;
+
+ auto pString = (HUDPanelDialogVariableString_t*)layoutState.m_vecDialogVariableStrings.GetManipulator()(SCHEMA_COLLECTION_MANIPULATOR_ACTION_GET_ELEMENT, layoutState.m_vecDialogVariableStrings, i, 0);
+ pString->m_bIsSet = false;
+ pString->m_sValue = "";
+ }
+}
+
+void CCSCustomHudLayout::SetInputCaptureEnabled(bool bEnable, int nSlot)
+{
+ GetLayoutState(nSlot).m_bInputCaptureEnabled = bEnable;
}
void CCSCustomHudLayout::SetInputCaptureEnabled(bool bEnable, CCSPlayerController* pController)
{
- GetLayoutState(pController).m_bInputCaptureEnabled = bEnable;
+ SetInputCaptureEnabled(bEnable, pController ? pController->GetPlayerSlot() : -1);
+}
+
+bool CCSCustomHudLayout::IsInputCaptureEnabled(int nSlot)
+{
+ return GetLayoutState(nSlot).m_bInputCaptureEnabled;
}
bool CCSCustomHudLayout::IsInputCaptureEnabled(CCSPlayerController* pController)
@@ -126,7 +209,12 @@ bool CCSCustomHudLayout::IsInputCaptureEnabled(CCSPlayerController* pController)
return GetLayoutState(pController).m_bInputCaptureEnabled;
}
-void CCSCustomHudLayout::AddClickCallback(CustomHudClickCallback_t callback)
+void CCSCustomHudLayout::SetClickCallback(CustomHudClickCallback_t callback)
+{
+ sm_mapCustomLayoutCallbacks[GetHandle().ToInt()].m_OnClick = callback;
+}
+
+void CCSCustomHudLayout::SetDisconnectCallback(CustomHudDisconnectCallback_t callback)
{
- g_mapClickCallbacks.insert({GetHandle().ToInt(), callback});
+ sm_mapCustomLayoutCallbacks[GetHandle().ToInt()].m_OnDisconnect = callback;
}
diff --git a/src/cs2_sdk/entity/customhudlayout.h b/src/cs2_sdk/entity/customhudlayout.h
index 30f37c5b..1df3374d 100644
--- a/src/cs2_sdk/entity/customhudlayout.h
+++ b/src/cs2_sdk/entity/customhudlayout.h
@@ -25,6 +25,13 @@
class CCSCustomHudLayout;
using CustomHudClickCallback_t = std::function;
+using CustomHudDisconnectCallback_t = std::function;
+
+struct CustomHudLayoutCallbacks_t
+{
+ CustomHudClickCallback_t m_OnClick = nullptr;
+ CustomHudDisconnectCallback_t m_OnDisconnect = nullptr;
+};
enum EHudPanelClassStatus_t : int
{
@@ -103,18 +110,39 @@ class CCSCustomHudLayout : public CBaseEntity
SCHEMA_FIELD_POINTER(CUtlVector, m_vecDialogVariableNames);
static CCSCustomHudLayout* Create(std::string sLayout, std::string sTargetName = "");
- static void ClearClickCallbacks();
+ static void ClearCallbacks();
+ CCSCustomHudLayoutState& GetLayoutState(int nSlot);
CCSCustomHudLayoutState& GetLayoutState(CCSPlayerController* pController = nullptr);
+ void SetHasClass(std::string sPanelId, std::string sClassName, bool bHasClass, int nSlot);
void SetHasClass(std::string sPanelId, std::string sClassName, bool bHasClass, CCSPlayerController* pController = nullptr);
+
+ void SetDialogVariableString(std::string sPanelId, std::string sVariableName, std::string sValue, int nSlot);
void SetDialogVariableString(std::string sPanelId, std::string sVariableName, std::string sValue, CCSPlayerController* pController = nullptr);
+
+ // NOTE: These will not apply any visible changes to ingame clients, despite them seeing empty vectors.
+ // They are only applicable for cleanup after clients disconnect.
+ void ClearClasses(int nSlot = -1);
+ void ClearDialogVariables(int nSlot = -1);
+
+ void SetInputCaptureEnabled(bool bEnable, int nSlot);
void SetInputCaptureEnabled(bool bEnable, CCSPlayerController* pController);
- bool IsInputCaptureEnabled(CCSPlayerController* pController);
+ bool IsInputCaptureEnabled(int nSlot);
+ bool IsInputCaptureEnabled(CCSPlayerController* pController = nullptr);
- void AddClickCallback(CustomHudClickCallback_t callback);
+ void SetClickCallback(CustomHudClickCallback_t callback);
void OnClick(CCSPlayerController* pController, const std::string& sButtonId);
+ // All layouts spawned with Create will by default have DefaultOnDisconnect as their disconnect callback
+ // which clears all classes and variables for a slot, so only use this if you want custom logic
+ void SetDisconnectCallback(CustomHudDisconnectCallback_t callback);
+ static void OnClientDisconnect(int nSlot);
+ static void DefaultOnDisconnect(CCSCustomHudLayout* pLayout, int nSlot);
+
void OnEntityDeleted();
+
+private:
+ static std::unordered_map sm_mapCustomLayoutCallbacks;
};
\ No newline at end of file
diff --git a/src/cs2_sdk/map.h b/src/cs2_sdk/map.h
new file mode 100644
index 00000000..74bbe3e8
--- /dev/null
+++ b/src/cs2_sdk/map.h
@@ -0,0 +1,40 @@
+/**
+ * =============================================================================
+ * 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 "utlstring.h"
+
+class CSingleWorldRep
+{
+private:
+ void* vtable;
+
+public:
+ CUtlString m_name;
+ char pad[0x20];
+ void* m_pCWorld;
+};
+
+struct LumpData
+{
+ CUtlString m_name;
+ char pad[0x18];
+ CKV3Arena* m_allocatorContext;
+};
diff --git a/src/cs2fixes.cpp b/src/cs2fixes.cpp
index 69580130..be45a77a 100644
--- a/src/cs2fixes.cpp
+++ b/src/cs2fixes.cpp
@@ -27,7 +27,6 @@
#include "common.h"
#include "ctimer.h"
#include "cvarwhitelist.h"
-#include "utils/sighook.h"
#include "discord.h"
#include "entities.h"
#include "entity/customhudlayout.h"
@@ -41,6 +40,7 @@
#include "hud_manager.h"
#include "icvar.h"
#include "idlemanager.h"
+#include "khook_helpers.h"
#include "map_votes.h"
#include "mapmigrations.h"
#include "networkstringtabledefs.h"
@@ -191,6 +191,7 @@ bool CS2Fixes::Load(PluginId id, ISmmAPI* ismm, char* error, size_t maxlen, bool
GET_V_IFACE_ANY(GetServerFactory, g_pSource2GameEntities, ISource2GameEntities, SOURCE2GAMEENTITIES_INTERFACE_VERSION);
GET_V_IFACE_ANY(GetServerFactory, g_pSource2GameClients, IServerGameClients, SOURCE2GAMECLIENTS_INTERFACE_VERSION);
GET_V_IFACE_ANY(GetEngineFactory, g_pNetworkServerService, INetworkServerService, NETWORKSERVERSERVICE_INTERFACE_VERSION);
+ GET_V_IFACE_ANY(GetEngineFactory, g_pEngineServiceMgr, IEngineServiceMgr, ENGINESERVICEMGR_INTERFACE_VERSION);
GET_V_IFACE_ANY(GetEngineFactory, g_gameEventSystem, IGameEventSystem, GAMEEVENTSYSTEM_INTERFACE_VERSION);
GET_V_IFACE_ANY(GetEngineFactory, g_pNetworkMessages, INetworkMessages, NETWORKMESSAGES_INTERFACE_VERSION);
GET_V_IFACE_ANY(GetEngineFactory, g_pGameTypes, IGameTypes, GAMETYPES_INTERFACE_VERSION);
@@ -219,7 +220,7 @@ bool CS2Fixes::Load(PluginId id, ISmmAPI* ismm, char* error, size_t maxlen, bool
g_bRequiredInitLoaded = false;
InitVirtualHooks();
- InitSigHooks();
+ InitKHooks();
if (!InitPatches())
g_bRequiredInitLoaded = false;
@@ -639,7 +640,7 @@ void CS2Fixes::OnLevelShutdown()
if (g_cvarVoteManagerEnable.Get())
g_pMapVoteSystem->OnLevelShutdown();
- CCSCustomHudLayout::ClearClickCallbacks();
+ CCSCustomHudLayout::ClearCallbacks();
}
bool CS2Fixes::Pause(char* error, size_t maxlen)
diff --git a/src/detours.cpp b/src/detours.cpp
index b1bae9f1..37a8f3b7 100644
--- a/src/detours.cpp
+++ b/src/detours.cpp
@@ -30,7 +30,6 @@
#include "customio.h"
#include "cvarwhitelist.h"
#include "detours.h"
-#include "utils/sighook.h"
#include "entities.h"
#include "entity/cbasemodelentity.h"
#include "entity/cbeam.h"
@@ -47,6 +46,7 @@
#include "gameconfig.h"
#include "igameevents.h"
#include "irecipientfilter.h"
+#include "khook_helpers.h"
#include "map_votes.h"
#include "mapmigrations.h"
#include "module.h"
@@ -59,49 +59,51 @@
#include "tier0/memdbgon.h"
-SIG_HOOK_FUNCTION(UTIL_SayTextFilter, Detour_UTIL_SayTextFilter, nullptr);
-SIG_HOOK_FUNCTION(UTIL_SayText2Filter, Detour_UTIL_SayText2Filter, nullptr);
-SIG_HOOK_MEMBER(TriggerPush_Touch, Detour_TriggerPush_Touch, nullptr);
-SIG_HOOK_MEMBER(CBaseEntity_TakeDamageOld, Detour_CBaseEntity_TakeDamageOld, Detour_CBaseEntity_TakeDamageOld_Post);
-SIG_HOOK_MEMBER(CCSPlayer_WeaponServices_CanUse, Detour_CCSPlayer_WeaponServices_CanUse, nullptr);
-SIG_HOOK_MEMBER(CCSPlayer_WeaponServices_EquipWeapon, Detour_CCSPlayer_WeaponServices_EquipWeapon, nullptr);
-SIG_HOOK_MEMBER(CEntityIdentity_AcceptInput, Detour_CEntityIdentity_AcceptInput, nullptr);
-SIG_HOOK_MEMBER(CNavMesh_GetNearestNavArea, Detour_CNavMesh_GetNearestNavArea, nullptr);
-SIG_HOOK_MEMBER(ProcessMovement, Detour_ProcessMovement, Detour_ProcessMovement_Post);
-SIG_HOOK_MEMBER(ProcessUsercmds, Detour_ProcessUsercmds, nullptr);
-SIG_HOOK_MEMBER(CGamePlayerEquip_InputTriggerForAllPlayers, Detour_CGamePlayerEquip_InputTriggerForAllPlayers, nullptr);
-SIG_HOOK_MEMBER(CGamePlayerEquip_InputTriggerForActivatedPlayer, Detour_CGamePlayerEquip_InputTriggerForActivatedPlayer, nullptr);
-SIG_HOOK_MEMBER(CTriggerGravity_GravityTouch, Detour_CTriggerGravity_GravityTouch, nullptr);
-SIG_HOOK_FUNCTION(GetFreeClient, Detour_GetFreeClient, nullptr);
+KHOOK_FUNCTION(UTIL_SayTextFilter, Detour_UTIL_SayTextFilter, nullptr);
+KHOOK_FUNCTION(UTIL_SayText2Filter, Detour_UTIL_SayText2Filter, nullptr);
+KHOOK_MEMBER(TriggerPush_Touch, Detour_TriggerPush_Touch, nullptr);
+KHOOK_MEMBER(CBaseEntity_TakeDamageOld, Detour_CBaseEntity_TakeDamageOld, Detour_CBaseEntity_TakeDamageOld_Post);
+KHOOK_MEMBER(CCSPlayer_WeaponServices_CanUse, Detour_CCSPlayer_WeaponServices_CanUse, nullptr);
+KHOOK_MEMBER(CCSPlayer_WeaponServices_EquipWeapon, Detour_CCSPlayer_WeaponServices_EquipWeapon, nullptr);
+KHOOK_MEMBER(CEntityIdentity_AcceptInput, Detour_CEntityIdentity_AcceptInput, nullptr);
+KHOOK_MEMBER(CNavMesh_GetNearestNavArea, Detour_CNavMesh_GetNearestNavArea, nullptr);
+KHOOK_MEMBER(ProcessMovement, Detour_ProcessMovement, Detour_ProcessMovement_Post);
+KHOOK_MEMBER(ProcessUsercmds, Detour_ProcessUsercmds, nullptr);
+KHOOK_MEMBER(CGamePlayerEquip_InputTriggerForAllPlayers, Detour_CGamePlayerEquip_InputTriggerForAllPlayers, nullptr);
+KHOOK_MEMBER(CGamePlayerEquip_InputTriggerForActivatedPlayer, Detour_CGamePlayerEquip_InputTriggerForActivatedPlayer, nullptr);
+KHOOK_MEMBER(CTriggerGravity_GravityTouch, Detour_CTriggerGravity_GravityTouch, nullptr);
+KHOOK_FUNCTION(GetFreeClient, Detour_GetFreeClient, nullptr);
#ifdef __linux__
// Inlined by MSVC as of 2025-07-28 CS2 update
// TODO: Find some alternative that supports Windows
-SIG_HOOK_MEMBER(CCSPlayerPawn_GetMaxSpeed, Detour_CCSPlayerPawn_GetMaxSpeed, nullptr);
+KHOOK_MEMBER(CCSPlayerPawn_GetMaxSpeed, Detour_CCSPlayerPawn_GetMaxSpeed, nullptr);
#endif
-SIG_HOOK_MEMBER(FindUseEntity, Detour_FindUseEntity, Detour_FindUseEntity_Post);
-SIG_HOOK_FUNCTION(TraceFunc, Detour_TraceFunc, nullptr);
-SIG_HOOK_FUNCTION(TraceShape, Detour_TraceShape, nullptr);
-SIG_HOOK_MEMBER(CBasePlayerPawn_GetEyePosition, Detour_CBasePlayerPawn_GetEyePosition, nullptr);
-SIG_HOOK_MEMBER(CBasePlayerPawn_GetEyeAngles, Detour_CBasePlayerPawn_GetEyeAngles, nullptr);
-SIG_HOOK_MEMBER(CBaseFilter_InputTestActivator, Detour_CBaseFilter_InputTestActivator, nullptr);
-SIG_HOOK_FUNCTION(GameSystem_Think_CheckSteamBan, Detour_GameSystem_Think_CheckSteamBan, Detour_GameSystem_Think_CheckSteamBan_Post);
-SIG_HOOK_MEMBER(CCSPlayer_ItemServices_CanAcquire, Detour_CCSPlayer_ItemServices_CanAcquire, nullptr);
-SIG_HOOK_FUNCTION(CS_Script_SetModel, Detour_CS_Script_SetModel, Detour_CS_Script_SetModel_Post);
-SIG_HOOK_MEMBER(CBaseModelEntity_SetModel, Detour_CBaseModelEntity_SetModel, nullptr);
-SIG_HOOK_MEMBER(CCSGameRules_GoToIntermission, Detour_CCSGameRules_GoToIntermission, nullptr);
-SIG_HOOK_MEMBER(SetBeamOrigin, Detour_SetBeamOrigin, nullptr);
-SIG_HOOK_MEMBER(SetBeamEndPos, Detour_SetBeamEndPos, nullptr);
-SIG_HOOK_FUNCTION(IsCommandWhitelisted, Detour_IsCommandWhitelisted, nullptr);
-
-std::vector& GetSigHookList()
-{
- static std::vector s_vecSigHooks;
+KHOOK_MEMBER(FindUseEntity, Detour_FindUseEntity, Detour_FindUseEntity_Post);
+KHOOK_FUNCTION(TraceFunc, Detour_TraceFunc, nullptr);
+KHOOK_FUNCTION(TraceShape, Detour_TraceShape, nullptr);
+KHOOK_MEMBER(CEntityIOOutput_FireOutputInternal, Detour_CEntityIOOutput_FireOutputInternal, nullptr);
+KHOOK_MEMBER(CBasePlayerPawn_GetEyePosition, Detour_CBasePlayerPawn_GetEyePosition, nullptr);
+KHOOK_MEMBER(CBasePlayerPawn_GetEyeAngles, Detour_CBasePlayerPawn_GetEyeAngles, nullptr);
+KHOOK_MEMBER(CBaseFilter_InputTestActivator, Detour_CBaseFilter_InputTestActivator, nullptr);
+KHOOK_FUNCTION(GameSystem_Think_CheckSteamBan, Detour_GameSystem_Think_CheckSteamBan, Detour_GameSystem_Think_CheckSteamBan_Post);
+KHOOK_MEMBER(CCSPlayer_ItemServices_CanAcquire, Detour_CCSPlayer_ItemServices_CanAcquire, nullptr);
+KHOOK_FUNCTION(CS_Script_SetModel, Detour_CS_Script_SetModel, Detour_CS_Script_SetModel_Post);
+KHOOK_MEMBER(CBaseModelEntity_SetModel, Detour_CBaseModelEntity_SetModel, nullptr);
+KHOOK_MEMBER(CCSGameRules_GoToIntermission, Detour_CCSGameRules_GoToIntermission, nullptr);
+KHOOK_MEMBER(SetBeamOrigin, Detour_SetBeamOrigin, nullptr);
+KHOOK_MEMBER(SetBeamEndPos, Detour_SetBeamEndPos, nullptr);
+KHOOK_FUNCTION(IsCommandWhitelisted, Detour_IsCommandWhitelisted, nullptr);
+KHOOK_MEMBER(CWorldRendererMgr_CreateWorld_Internal, nullptr, Detour_CWorldRendererMgr_CreateWorld_Internal_Post);
+
+std::vector& GetKHookList()
+{
+ static std::vector s_vecSigHooks;
return s_vecSigHooks;
}
-void InitSigHooks()
+void InitKHooks()
{
- for (auto hook : GetSigHookList())
+ for (auto hook : GetKHookList())
hook->Configure();
}
@@ -762,13 +764,13 @@ KHook::Return Detour_CCSPlayerPawn_GetMaxSpeed(CCSPlayerPawn* pPawn)
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)
+KHook::Return Detour_FindUseEntity(CCSPlayer_UseServices* pThis, float unk2, int64_t unk3)
{
g_bFindingUseEntity = true;
return {KHook::Action::Ignore};
}
-KHook::Return Detour_FindUseEntity_Post(CCSPlayer_UseServices* pThis, float a2)
+KHook::Return Detour_FindUseEntity_Post(CCSPlayer_UseServices* pThis, float unk2, int64_t unk3)
{
g_bFindingUseEntity = false;
return {KHook::Action::Ignore};
@@ -796,7 +798,7 @@ KHook::Return Detour_TraceShape(int64* a1, int64 a2, int64 a3, int64 a4, C
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)
+KHook::Return Detour_CEntityIOOutput_FireOutputInternal(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);
@@ -977,3 +979,24 @@ KHook::Return Detour_IsCommandWhitelisted(void* pAddonManager, const char*
return {KHook::Action::Supersede, g_pConvarWhitelist->IsWhitelisted(pszCommandName)};
}
+
+KHook::Return Detour_CWorldRendererMgr_CreateWorld_Internal_Post(IWorldRendererMgr* pThis, CSingleWorldRep* singleWorld)
+{
+ // The world can fail to be created and the function will return nullptr.
+ if (!KHook::GetOriginalReturn())
+ return {KHook::Action::Ignore};
+
+ auto pWorld = singleWorld->m_pCWorld;
+ auto vecLumpData = (CUtlVector*)((uint8_t*)pWorld + 0x1E0);
+
+ FOR_EACH_VEC(*vecLumpData, i)
+ {
+ auto& lump = (*vecLumpData)[i];
+ auto lumpData = *(LumpData**)lump;
+ auto vecEntityKeyValues = (CUtlVector*)((uint8_t*)lumpData + 0x1220);
+
+ g_pMapMigrations->RunMigrations(vecEntityKeyValues);
+ }
+
+ return {KHook::Action::Ignore};
+}
diff --git a/src/detours.h b/src/detours.h
index 08d0d677..34d7a182 100644
--- a/src/detours.h
+++ b/src/detours.h
@@ -20,6 +20,7 @@
#pragma once
#include "cs2_sdk/entityio.h"
#include "khook.hpp"
+#include "map.h"
#include
#include "cs2_sdk/entityio.h"
@@ -98,11 +99,11 @@ KHook::Return Detour_CGamePlayerEquip_InputTriggerForActivatedPlayer(CGame
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_FindUseEntity(CCSPlayer_UseServices* pThis, float unk2, int64_t unk3);
+KHook::Return Detour_FindUseEntity_Post(CCSPlayer_UseServices* pThis, float unk2, int64_t unk3);
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*);
+KHook::Return Detour_CEntityIOOutput_FireOutputInternal(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*);
@@ -121,3 +122,4 @@ KHook::Return Detour_CCSGameRules_GoToIntermission(CCSGameRules* pThis, bo
KHook::Return Detour_SetBeamOrigin(CBeam* pThis, const Vector* pVecWorldPosition);
KHook::Return Detour_SetBeamEndPos(CBeam* pThis, const Vector* pVecWorldPosition);
KHook::Return Detour_IsCommandWhitelisted(void* pAddonManager, const char* pszCommandName);
+KHook::Return Detour_CWorldRendererMgr_CreateWorld_Internal_Post(IWorldRendererMgr* pThis, CSingleWorldRep* pSingleWorld);
diff --git a/src/entitylistener.cpp b/src/entitylistener.cpp
index e4f72ffd..ab5d84a0 100644
--- a/src/entitylistener.cpp
+++ b/src/entitylistener.cpp
@@ -27,7 +27,6 @@
#include "entityclass.h"
#include "entwatch.h"
#include "gameconfig.h"
-#include "mapmigrations.h"
#include "plat.h"
CEntityListener* g_pEntityListener = nullptr;
@@ -45,7 +44,6 @@ void CEntityListener::OnEntitySpawned(CEntityInstance* pEntity)
reinterpret_cast(pEntity)->SetCollisionGroup(COLLISION_GROUP_DEBRIS);
EntityHandler_OnEntitySpawned(reinterpret_cast(pEntity));
- g_pMapMigrations->OnEntitySpawned_Post(reinterpret_cast(pEntity));
if (g_cvarEnableEntWatch.Get())
EW_OnEntitySpawned(pEntity);
diff --git a/src/gameconfig.cpp b/src/gameconfig.cpp
index b4295bac..5113c301 100644
--- a/src/gameconfig.cpp
+++ b/src/gameconfig.cpp
@@ -161,6 +161,8 @@ CModule** CGameConfig::GetModule(const char* name)
return &modules::networksystem;
else if (strcmp(library, "matchmaking") == 0)
return &modules::matchmaking;
+ else if (strcmp(library, "worldrenderer") == 0)
+ return &modules::worldrenderer;
#ifdef _WIN32
else if (strcmp(library, "hammer") == 0)
return &modules::hammer;
diff --git a/src/map_votes.cpp b/src/map_votes.cpp
index 72dd2479..1f2a1364 100644
--- a/src/map_votes.cpp
+++ b/src/map_votes.cpp
@@ -24,6 +24,7 @@
#include "entity/cgamerules.h"
#include "eventlistener.h"
#include "iserver.h"
+#include "mapmigrations.h"
#include "playermanager.h"
#include "steam/steam_gameserver.h"
#include "strtools.h"
@@ -73,6 +74,10 @@ CON_COMMAND_CHAT_FLAGS(map, " - Change map", ADMFLAG_CHANGEMAP)
return -1.0f;
});
+ // Map migrations need map info ahead of time
+ if (pMap->GetWorkshopId() != 0)
+ CMapSystemWorkshopDetailsQuery::Create(pMap->GetWorkshopId());
+
ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "Changing map to \x06%s\x01...", pMap->GetName());
});
}
@@ -340,7 +345,13 @@ void CMapVoteSystem::StartVote()
for (int i = 0; i < m_iVoteSize; i++)
{
int iMapIndex = g_pGameRules->m_nEndMatchMapGroupVoteOptions[i];
+ uint64 iWorkshopId = GetMapByIndex(iMapIndex)->GetWorkshopId();
+
Message("The %d-th chosen map index %d is %s\n", i, iMapIndex, GetMapName(iMapIndex));
+
+ // Map migrations need map info ahead of time
+ if (iWorkshopId != 0)
+ CMapSystemWorkshopDetailsQuery::Create(iWorkshopId);
}
static ConVarRefAbstract mp_endmatch_votenextleveltime("mp_endmatch_votenextleveltime");
@@ -858,7 +869,10 @@ void CMapVoteSystem::ForceNextMap(CCSPlayerController* pController, const char*
return;
}
- // When found, print the map and store the forced map
+ // Map migrations need map info ahead of time
+ if (pMap->GetWorkshopId() != 0)
+ CMapSystemWorkshopDetailsQuery::Create(pMap->GetWorkshopId());
+
g_pMapVoteSystem->SetForcedNextMap(pMap);
ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "\x06%s \x01has been forced as the next map.\n", g_pMapVoteSystem->GetForcedNextMap()->GetName());
});
@@ -886,30 +900,24 @@ void CMapVoteSystem::PrintDownloadProgress()
void CMapVoteSystem::OnMapDownloaded(DownloadItemResult_t* pResult)
{
- if (std::find(m_DownloadQueue.begin(), m_DownloadQueue.end(), pResult->m_nPublishedFileId) == m_DownloadQueue.end() || !GetSteamUGC())
+ if (m_DownloadQueue.empty() || m_DownloadQueue.front() != pResult->m_nPublishedFileId)
return;
- // Some weird rate limiting that's been observed? Back off for a while then retry download
+ // This result is also used for some kind of rate limiting, back off for a while then retry download
if (pResult->m_eResult == k_EResultNoConnection)
{
- PublishedFileId_t workshopID = m_DownloadQueue.front();
- Message("Addon %llu download failed with status code 3, retrying in 2 minutes\n", workshopID);
-
- m_pRateLimitedDownloadTimer = CTimer::Create(120.0f, TIMERFLAG_NONE, [workshopID]() {
- GetSteamUGC()->DownloadItem(workshopID, false);
-
- return -1.0f;
- });
-
+ Message("Addon %llu download failed with status code 3, retrying in 5 minutes\n", m_DownloadQueue.front());
+ StartMapDownload(300.0f);
return;
}
- m_DownloadQueue.pop_front();
+ if (pResult->m_eResult != k_EResultOK)
+ Message("Addon %llu download failed with status code %i, skipping\n", pResult->m_nPublishedFileId, pResult->m_eResult);
- if (GetDownloadQueueSize() == 0)
- return;
+ m_DownloadQueue.pop_front();
- GetSteamUGC()->DownloadItem(m_DownloadQueue.front(), false);
+ // Go slowly, try to avoid the rate limit because it can break required on-demand downloads elsewhere
+ StartMapDownload(30.0f);
}
void CMapVoteSystem::QueueMapDownload(PublishedFileId_t iWorkshopId)
@@ -920,7 +928,28 @@ void CMapVoteSystem::QueueMapDownload(PublishedFileId_t iWorkshopId)
m_DownloadQueue.push_back(iWorkshopId);
if (m_DownloadQueue.front() == iWorkshopId)
- GetSteamUGC()->DownloadItem(iWorkshopId, false);
+ StartMapDownload();
+}
+
+void CMapVoteSystem::StartMapDownload(float flDelay)
+{
+ if (auto pTimer = m_pDownloadTimer.lock())
+ pTimer->Cancel();
+
+ if (m_DownloadQueue.empty())
+ return;
+
+ PublishedFileId_t workshopID = m_DownloadQueue.front();
+
+ m_pDownloadTimer = CTimer::Create(flDelay, TIMERFLAG_NONE, [workshopID]() {
+ if (!GetSteamUGC() || !GetSteamUGC()->DownloadItem(workshopID, false))
+ {
+ Message("Addon %llu download failed to start, retrying\n", workshopID);
+ return 30.0f;
+ }
+
+ return -1.0f;
+ });
}
bool CMapVoteSystem::LoadMapList()
@@ -1144,9 +1173,6 @@ void CMapVoteSystem::OnPlayerCountChange()
void CMapVoteSystem::ApplyGameSettings(const char* pszMapName, uint64 iWorkshopId)
{
- if (!g_cvarVoteManagerEnable.Get())
- return;
-
auto pair = GetMapInfoByIdentifiers(pszMapName, iWorkshopId);
if (pair.first != -1)
@@ -1154,6 +1180,9 @@ void CMapVoteSystem::ApplyGameSettings(const char* pszMapName, uint64 iWorkshopI
else
SetCurrentMap(std::make_shared(pszMapName, iWorkshopId, pszMapName[0] == '\0' && iWorkshopId == 0));
+ if (!g_cvarVoteManagerEnable.Get())
+ return;
+
ProcessGroupCooldowns();
}
@@ -1362,8 +1391,8 @@ bool CMapVoteSystem::ReloadMapList(bool bReloadMap)
if (!m_pDownloadProgressTimer.expired())
m_pDownloadProgressTimer.lock()->Cancel();
- if (!m_pRateLimitedDownloadTimer.expired())
- m_pRateLimitedDownloadTimer.lock()->Cancel();
+ if (!m_pDownloadTimer.expired())
+ m_pDownloadTimer.lock()->Cancel();
}
if (!g_pMapVoteSystem->LoadMapList())
@@ -1396,8 +1425,7 @@ std::shared_ptr CMapSystemWorkshopDetailsQuery::
{
if (!GetSteamUGC())
{
- Panic("A workshop map query was attempted on null ISteamUGC, returning early.\n");
- ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Failed to query workshop map information for ID \x06%llu\x01.", iWorkshopId);
+ ReportCreateFailure(iWorkshopId, pController, callbackSuccess);
return nullptr;
}
@@ -1406,13 +1434,20 @@ std::shared_ptr CMapSystemWorkshopDetailsQuery::
if (hQuery == k_UGCQueryHandleInvalid)
{
- ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Failed to query workshop map information for ID \x06%llu\x01.", iWorkshopId);
+ ReportCreateFailure(iWorkshopId, pController, callbackSuccess);
return nullptr;
}
GetSteamUGC()->SetAllowCachedResponse(hQuery, 0);
SteamAPICall_t hCall = GetSteamUGC()->SendQueryUGCRequest(hQuery);
+ if (hCall == k_uAPICallInvalid)
+ {
+ GetSteamUGC()->ReleaseQueryUGCRequest(hQuery);
+ ReportCreateFailure(iWorkshopId, pController, callbackSuccess);
+ return nullptr;
+ }
+
auto pQuery = std::make_shared(hQuery, iWorkshopId, pController, callbackSuccess);
g_pMapVoteSystem->AddWorkshopDetailsQuery(pQuery);
pQuery->m_CallResult.Set(hCall, pQuery.get(), &CMapSystemWorkshopDetailsQuery::OnQueryCompleted);
@@ -1420,23 +1455,40 @@ std::shared_ptr CMapSystemWorkshopDetailsQuery::
return pQuery;
}
+void CMapSystemWorkshopDetailsQuery::ReportCreateFailure(uint64 iWorkshopId, CCSPlayerController* pController, QueryCallback_t callbackSuccess)
+{
+ Message("Failed to query workshop map information for ID %llu\n", iWorkshopId);
+
+ if (callbackSuccess)
+ ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Failed to query workshop map information for ID \x06%llu\x01.", iWorkshopId);
+}
+
void CMapSystemWorkshopDetailsQuery::OnQueryCompleted(SteamUGCQueryCompleted_t* pCompletedQuery, bool bFailed)
{
CCSPlayerController* pController = m_hController.Get();
SteamUGCDetails_t details;
- // Only allow null controller if controller was originally null (console)
- if (m_bConsole || pController)
+ // Is this a player map lookup? Only allow null controller if controller was originally null (console)
+ bool bMapLookupActive = m_callbackSuccess && (m_bConsole || pController);
+
+ if (bFailed || pCompletedQuery->m_eResult != k_EResultOK || pCompletedQuery->m_unNumResultsReturned < 1 || !GetSteamUGC()->GetQueryUGCResult(pCompletedQuery->m_handle, 0, &details) || details.m_eResult != k_EResultOK)
{
- if (bFailed || pCompletedQuery->m_eResult != k_EResultOK || pCompletedQuery->m_unNumResultsReturned < 1 || !GetSteamUGC()->GetQueryUGCResult(pCompletedQuery->m_handle, 0, &details) || details.m_eResult != k_EResultOK)
- {
+ if (bMapLookupActive)
ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "Failed to query workshop map information for ID \x06%llu\x01.", m_iWorkshopId);
- }
- else if (details.m_nConsumerAppID != 730 || details.m_eFileType != k_EWorkshopFileTypeCommunity)
- {
- ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "The ID \x06%llu\x01 is not a valid CS2 workshop map.", m_iWorkshopId);
- }
else
+ Message("Failed to query workshop map information for ID %llu\n", m_iWorkshopId);
+ }
+ else if (details.m_nConsumerAppID != 730 || details.m_eFileType != k_EWorkshopFileTypeCommunity)
+ {
+ if (bMapLookupActive)
+ ClientPrint(pController, HUD_PRINTTALK, CHAT_PREFIX "The ID \x06%llu\x01 is not a valid CS2 workshop map.", m_iWorkshopId);
+ }
+ else
+ {
+ // Map migrations also need to know when maps were updated
+ g_pMapMigrations->UpdateMapUpdateTime(m_iWorkshopId, details.m_rtimeUpdated);
+
+ if (bMapLookupActive)
{
// Try to get a head start on downloading the map if needed
GetSteamUGC()->DownloadItem(m_iWorkshopId, false);
diff --git a/src/map_votes.h b/src/map_votes.h
index ac55efac..e6990288 100644
--- a/src/map_votes.h
+++ b/src/map_votes.h
@@ -131,7 +131,6 @@ class CGroup
float m_fCooldown;
};
-// Implementation is a bit hardcoded for HandlePlayerMapLookup use
class CMapSystemWorkshopDetailsQuery : public std::enable_shared_from_this
{
public:
@@ -149,7 +148,8 @@ class CMapSystemWorkshopDetailsQuery : public std::enable_shared_from_this Create(uint64 iWorkshopId, CCSPlayerController* pController, QueryCallback_t callbackSuccess);
+ static std::shared_ptr Create(uint64 iWorkshopId, CCSPlayerController* pController = nullptr, QueryCallback_t callbackSuccess = nullptr);
+ static void ReportCreateFailure(uint64 iWorkshopId, CCSPlayerController* pController, QueryCallback_t callbackSuccess);
private:
void OnQueryCompleted(SteamUGCQueryCompleted_t* pCompletedQuery, bool bFailed);
@@ -202,6 +202,7 @@ class CMapVoteSystem
bool IsMapListLoaded() { return m_bMapListLoaded; }
CUtlStringList CreateWorkshopMapGroup();
void QueueMapDownload(PublishedFileId_t iWorkshopId);
+ void StartMapDownload(float flDelay = 0.0f);
void PrintDownloadProgress();
std::shared_ptr GetCurrentMap() { return m_pCurrentMap; }
void SetCurrentMap(std::shared_ptr pCurrentMap) { m_pCurrentMap = pCurrentMap; }
@@ -245,7 +246,7 @@ class CMapVoteSystem
bool g_bDisableCooldowns = false;
std::filesystem::file_time_type m_timeMapListModified = std::filesystem::file_time_type::min();
std::weak_ptr m_pDownloadProgressTimer;
- std::weak_ptr m_pRateLimitedDownloadTimer;
+ std::weak_ptr m_pDownloadTimer;
std::vector> m_vecWorkshopDetailsQueries;
int m_iSessionMaxPlayerCount = 0;
};
diff --git a/src/mapmigrations.cpp b/src/mapmigrations.cpp
index ed855a98..ec6aed15 100644
--- a/src/mapmigrations.cpp
+++ b/src/mapmigrations.cpp
@@ -20,8 +20,8 @@
#include "mapmigrations.h"
#include "cs2fixes.h"
#include "entity.h"
-#include "entity/cbasemodelentity.h"
#include "entity/cbasetoggle.h"
+#include "map_votes.h"
#include "utils.h"
#include "vprof.h"
@@ -33,35 +33,40 @@ const time_t g_time20260420 = 1776725888;
CConVar g_cvarMapMigrations20260121("cs2f_mapmigrations_20260121", FCVAR_NONE, "Current mode for 2026-01-21 CS2 update map migrations. [0 = Force disabled, 1 = Force enabled, 2 = Automatically enabled for maps updated before 2026-01-21 & disabled if updated after]", 2);
CConVar g_cvarMapMigrations20260420("cs2f_mapmigrations_20260420", FCVAR_NONE, "Current mode for 2026-04-20 CS2 update map migrations. [0 = Force disabled, 1 = Force enabled, 2 = Automatically enabled for maps updated before 2026-04-20 & disabled if updated after]", 2);
-void CMapMigrations::ApplyGameSettings(uint64 iWorkshopId)
+void CMapMigrations::PreLevelLoad(uint64 iWorkshopId)
{
m_timeMapUpdated = std::numeric_limits::max();
// Don't run on default maps
if (iWorkshopId != 0)
- CMapMigrationWorkshopDetailsQuery::Create(iWorkshopId);
-}
-
-void CMapMigrations::OnRoundPrestart()
-{
- m_vecModelEntitiesUsingRendermodeEnum.clear();
- m_vecEquippedWeapons.clear();
+ {
+ if (m_mapUpdateTimes.contains(iWorkshopId))
+ m_timeMapUpdated = m_mapUpdateTimes[iWorkshopId];
+ else
+ Message("Skipping pre-load map migrations for %llu, update time is not available\n", iWorkshopId);
+ }
}
-void CMapMigrations::OnEntitySpawned_Pre(CBaseEntity* pEntity, const CEntityKeyValues* pKeyValues)
+void CMapMigrations::ApplyGameSettings(uint64 iWorkshopId)
{
- // Stupid workaround for CEntityKeyValues being inaccessible after entity spawn
- // We need access to this in 2026-01-21 rendermode migrations when called from UpdateMapUpdateTime
+ // If map update time wasn't available before map load, try to get the update time anyways, for later migrations
+ if (iWorkshopId != 0 && !m_mapUpdateTimes.contains(iWorkshopId))
+ CMapSystemWorkshopDetailsQuery::Create(iWorkshopId);
+
//std::string temp = pKeyValues->GetString("rendermode", "-1");
//std::string name = pEntity->GetClassname();
//Message("key=%s, value=%s\n",name.c_str(), temp.c_str());
- if (pEntity->AsBaseModelEntity() && V_StringToInt32(pKeyValues->GetString("rendermode"), -1, NULL, NULL, PARSING_FLAG_SKIP_WARNING|PARSING_FLAG_SKIP_ASSERT) == -1)
+ //de_dust2 point_worldtext has key=rendermode value kRenderNormal instead of an integer, many maps have it, throws a parse assert.
+
+ // Stupid workaround for CEntityKeyValues being inaccessible after entity spawn
+ // We need access to this in 2026-01-21 rendermode migrations when called from UpdateMapUpdateTime
+ if (pEntity->AsBaseModelEntity() && V_StringToInt32(pKeyValues->GetString("rendermode"), -1, NULL, NULL, PARSING_FLAG_SKIP_WARNING|PARSING_FLAG_SKIP_ASSERT) == -1)
m_vecModelEntitiesUsingRendermodeEnum.push_back(pEntity->GetHandle());
}
-void CMapMigrations::OnEntitySpawned_Post(CBaseEntity* pEntity)
+void CMapMigrations::OnRoundPrestart()
{
- RunMigrations(pEntity);
+ m_vecEquippedWeapons.clear();
}
void CMapMigrations::OnEquipWeapon(CBasePlayerWeapon* pWeapon)
@@ -70,43 +75,51 @@ void CMapMigrations::OnEquipWeapon(CBasePlayerWeapon* pWeapon)
Migrations_20260420(pWeapon);
}
-void CMapMigrations::RunMigrations(CBaseEntity* pEntity)
+void CMapMigrations::RunMigrations(CUtlVector* pVecEntityKeyValues)
{
+ if (g_cvarMapMigrations20260121.Get() > 0)
+ Migrations_Rendermode(pVecEntityKeyValues);
+
if (g_cvarMapMigrations20260121.Get() == 1 || (g_cvarMapMigrations20260121.Get() == 2 && m_timeMapUpdated < g_time20260121))
- Migrations_20260121(pEntity);
+ Migrations_20260121(pVecEntityKeyValues);
}
-void CMapMigrations::Migrations_20260121(CBaseEntity* pEntity)
+void CMapMigrations::Migrations_Rendermode(CUtlVector* pVecEntityKeyValues)
{
- if (!V_strcasecmp(pEntity->GetClassname(), "func_door_rotating"))
+ FOR_EACH_VEC(*pVecEntityKeyValues, i)
{
- uint32 spawnFlags = pEntity->m_spawnflags();
+ auto pKeyValues = (*pVecEntityKeyValues)[i];
- if (!(spawnFlags & SF_DOOR_ONEWAY))
- pEntity->m_spawnflags = spawnFlags + SF_DOOR_ONEWAY;
- }
-
- CBaseModelEntity* pModelEntity = pEntity->AsBaseModelEntity();
+ if (!pKeyValues->HasValue("rendermode"))
+ continue;
- if (pModelEntity)
- {
- // Also need to make sure the entity is using index-based rendermodes, and not enum-based ones (which will already automatically migrate correctly)
- // This differentiation is lost after entity spawn, so we had to check the original keyvalue earlier instead
- for (int i = 0; i < m_vecModelEntitiesUsingRendermodeEnum.size(); i++)
- if (m_vecModelEntitiesUsingRendermodeEnum[i] == pModelEntity->GetHandle())
- return;
+ int renderMode = V_StringToInt32(pKeyValues->GetString("rendermode"), -1, NULL, NULL, PARSING_FLAG_SKIP_WARNING);
- RenderMode_t renderMode = pModelEntity->m_nRenderMode();
+ // Enum-named render modes already migrate correctly
+ if (renderMode == -1)
+ continue;
- // Legacy kRenderTransAlpha
if (renderMode == 4)
- pModelEntity->m_nRenderMode = kRenderTransAlpha;
- // Legacy kRenderNone
+ pKeyValues->SetString("rendermode", "kRenderTransAlpha");
else if (renderMode == 10)
- pModelEntity->m_nRenderMode = kRenderNone;
+ pKeyValues->SetString("rendermode", "kRenderNone");
// All other removed render modes, fall back to normal
else if (renderMode > kRenderNormal)
- pModelEntity->m_nRenderMode = kRenderNormal;
+ pKeyValues->SetString("rendermode", "kRenderNormal");
+ }
+}
+
+void CMapMigrations::Migrations_20260121(CUtlVector* pVecEntityKeyValues)
+{
+ FOR_EACH_VEC(*pVecEntityKeyValues, i)
+ {
+ auto pKeyValues = (*pVecEntityKeyValues)[i];
+
+ if (!V_strcasecmp(pKeyValues->GetString("classname"), "func_door_rotating") && pKeyValues->HasValue("spawnflags"))
+ {
+ uint32 spawnFlags = pKeyValues->GetUint("spawnflags");
+ pKeyValues->SetUint("spawnflags", spawnFlags | SF_DOOR_ONEWAY);
+ }
}
}
@@ -159,55 +172,11 @@ bool CMapMigrations::Migrations20260420Enabled()
return g_cvarMapMigrations20260420.Get() == 1 || (g_cvarMapMigrations20260420.Get() == 2 && m_timeMapUpdated < g_time20260420);
}
-void CMapMigrations::UpdateMapUpdateTime(time_t timeMapUpdated)
-{
- m_timeMapUpdated = timeMapUpdated;
-
- CBaseEntity* pTarget = nullptr;
-
- // May be called late, so also check any existing entities first
- while ((pTarget = UTIL_FindEntityByClassname(pTarget, "*")))
- RunMigrations(pTarget);
-}
-
-std::shared_ptr CMapMigrationWorkshopDetailsQuery::Create(uint64 iWorkshopId)
+void CMapMigrations::UpdateMapUpdateTime(uint64 iWorkshopId, time_t timeMapUpdated)
{
- if (!GetSteamUGC())
- {
- Panic("Map migrations failed to find current map update time: null ISteamUGC\n");
- return nullptr;
- }
-
- uint64 iWorkshopIDArray[1] = {iWorkshopId};
- UGCQueryHandle_t hQuery = GetSteamUGC()->CreateQueryUGCDetailsRequest(iWorkshopIDArray, 1);
-
- if (hQuery == k_UGCQueryHandleInvalid)
- {
- Panic("Map migrations failed to find current map update time: failed to query workshop map information for ID %llu\n", iWorkshopId);
- return nullptr;
- }
-
- GetSteamUGC()->SetAllowCachedResponse(hQuery, 0);
- SteamAPICall_t hCall = GetSteamUGC()->SendQueryUGCRequest(hQuery);
-
- auto pQuery = std::make_shared(hQuery, iWorkshopId);
- g_pMapMigrations->AddWorkshopDetailsQuery(pQuery);
- pQuery->m_CallResult.Set(hCall, pQuery.get(), &CMapMigrationWorkshopDetailsQuery::OnQueryCompleted);
-
- return pQuery;
-}
-
-void CMapMigrationWorkshopDetailsQuery::OnQueryCompleted(SteamUGCQueryCompleted_t* pCompletedQuery, bool bFailed)
-{
- SteamUGCDetails_t details;
-
- if (bFailed || pCompletedQuery->m_eResult != k_EResultOK || pCompletedQuery->m_unNumResultsReturned < 1 || !GetSteamUGC()->GetQueryUGCResult(pCompletedQuery->m_handle, 0, &details) || details.m_eResult != k_EResultOK)
- Panic("Map migrations failed to find current map update time: failed to query workshop map information for ID %llu\n", m_iWorkshopId);
- else
- g_pMapMigrations->UpdateMapUpdateTime(details.m_rtimeUpdated);
-
- if (GetSteamUGC())
- GetSteamUGC()->ReleaseQueryUGCRequest(m_hQuery);
+ m_mapUpdateTimes[iWorkshopId] = timeMapUpdated;
- g_pMapMigrations->RemoveWorkshopDetailsQuery(shared_from_this());
+ // If we get triggered through ApplyGameSettings
+ if (g_pMapVoteSystem->GetCurrentMap() && g_pMapVoteSystem->GetCurrentMap()->GetWorkshopId() == iWorkshopId)
+ m_timeMapUpdated = timeMapUpdated;
}
\ No newline at end of file
diff --git a/src/mapmigrations.h b/src/mapmigrations.h
index 6d4cca13..dfcf31e8 100644
--- a/src/mapmigrations.h
+++ b/src/mapmigrations.h
@@ -23,8 +23,6 @@
#include "convar.h"
#include "cs2_sdk/entity/ccsweaponbase.h"
#include "ehandle.h"
-#include "entitysystem.h"
-#include "steam/isteamugc.h"
#include
#undef max
@@ -32,43 +30,23 @@ extern CConVar g_cvarMapMigrations20260121;
#define SF_DOOR_ONEWAY 16
-class CMapMigrationWorkshopDetailsQuery : public std::enable_shared_from_this
-{
-public:
- CMapMigrationWorkshopDetailsQuery(UGCQueryHandle_t hQuery, uint64 iWorkshopId) :
- m_hQuery(hQuery), m_iWorkshopId(iWorkshopId)
- {}
-
- static std::shared_ptr Create(uint64 iWorkshopId);
-
-private:
- void OnQueryCompleted(SteamUGCQueryCompleted_t* pCompletedQuery, bool bFailed);
-
- UGCQueryHandle_t m_hQuery;
- CCallResult m_CallResult;
- uint64 m_iWorkshopId;
-};
-
class CMapMigrations
{
public:
+ void PreLevelLoad(uint64 iWorkshopId);
void ApplyGameSettings(uint64 iWorkshopId);
void OnRoundPrestart();
- void OnEntitySpawned_Pre(CBaseEntity* pEntity, const CEntityKeyValues* pKeyValues);
- void OnEntitySpawned_Post(CBaseEntity* pEntity);
void OnEquipWeapon(CBasePlayerWeapon* pWeapon);
- void RunMigrations(CBaseEntity* pEntity);
- void Migrations_20260121(CBaseEntity* pEntity);
+ void RunMigrations(CUtlVector* pVecEntityKeyValues);
+ void Migrations_Rendermode(CUtlVector* pVecEntityKeyValues);
+ void Migrations_20260121(CUtlVector* pVecEntityKeyValues);
void Migrations_20260420(CBasePlayerWeapon* pWeapon);
bool Migrations20260420Enabled();
- void UpdateMapUpdateTime(time_t timeMapUpdated);
- void AddWorkshopDetailsQuery(std::shared_ptr pQuery) { m_vecWorkshopDetailsQueries.push_back(pQuery); }
- void RemoveWorkshopDetailsQuery(std::shared_ptr pQuery) { m_vecWorkshopDetailsQueries.erase(std::remove(m_vecWorkshopDetailsQueries.begin(), m_vecWorkshopDetailsQueries.end(), pQuery), m_vecWorkshopDetailsQueries.end()); }
+ void UpdateMapUpdateTime(uint64 iWorkshopId, time_t timeMapUpdated);
private:
time_t m_timeMapUpdated = std::numeric_limits::max();
- std::vector> m_vecWorkshopDetailsQueries;
- std::vector> m_vecModelEntitiesUsingRendermodeEnum;
+ std::unordered_map m_mapUpdateTimes;
std::vector> m_vecEquippedWeapons;
};
diff --git a/src/mempatch.cpp b/src/mempatch.cpp
index 5db8fb4a..2dcd025e 100644
--- a/src/mempatch.cpp
+++ b/src/mempatch.cpp
@@ -19,10 +19,10 @@
#include "mempatch.h"
#include "common.h"
+#include "khook.hpp"
#include "tier0/dbg.h"
#include "tier1/strtools.h"
#include "utils/module.h"
-#include "khook.hpp"
#include "tier0/memdbgon.h"
diff --git a/src/utils/khook_helpers.h b/src/utils/khook_helpers.h
new file mode 100644
index 00000000..fd004856
--- /dev/null
+++ b/src/utils/khook_helpers.h
@@ -0,0 +1,206 @@
+/**
+ * =============================================================================
+ * 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 "../gameconfig.h"
+
+class CKHookBase
+{
+public:
+ virtual void Configure() = 0;
+};
+
+extern std::vector& GetKHookList();
+void InitKHooks();
+
+template
+class CKHookFunction : public CKHookBase
+{
+public:
+ using fnCallback = KHook::Return (*)(ARGS...);
+
+ CKHookFunction(const char* pSignature, fnCallback cbPre, fnCallback cbPost) :
+ m_hook(cbPre, cbPost), m_pSignatureName(pSignature)
+ {
+ GetKHookList().push_back(this);
+ }
+
+ virtual void Configure() override
+ {
+ auto address = g_GameConfig->ResolveSignature(m_pSignatureName);
+
+ if (!address)
+ {
+ g_bRequiredInitLoaded = false;
+ return;
+ }
+
+ m_hook.Configure(address);
+
+ Message("Hooked %s at 0x%p\n", m_pSignatureName, address);
+ }
+
+ RETURN CallOriginal(ARGS... args)
+ {
+ return m_hook.CallOriginal(args...);
+ }
+
+private:
+ KHook::Function m_hook;
+ const char* m_pSignatureName;
+};
+
+template
+auto MakeKHookFunction(const char* pSignature, KHook::Return (*cbPre)(ARGS...), std::nullptr_t cbPost)
+{
+ return std::make_unique>(pSignature, cbPre, nullptr);
+}
+
+template
+auto MakeKHookFunction(const char* pSignature, std::nullptr_t cbPre, KHook::Return (*cbPost)(ARGS...))
+{
+ return std::make_unique>(pSignature, nullptr, cbPost);
+}
+
+template
+auto MakeKHookFunction(const char* pSignature, KHook::Return (*cbPre)(ARGS...), KHook::Return (*cbPost)(ARGS...))
+{
+ return std::make_unique>(pSignature, cbPre, cbPost);
+}
+
+#define KHOOK_FUNCTION(name, pre, post) \
+ static auto hook##name = MakeKHookFunction(#name, pre, post)
+
+template
+class CKHookMember : public CKHookBase
+{
+public:
+ using fnCallback = KHook::Return (*)(CLASS*, ARGS...);
+
+ CKHookMember(const char* pSignature, fnCallback cbPre, fnCallback cbPost) :
+ m_hook(cbPre, cbPost), m_pSignatureName(pSignature)
+ {
+ GetKHookList().push_back(this);
+ }
+
+ virtual void Configure() override
+ {
+ auto address = g_GameConfig->ResolveSignature(m_pSignatureName);
+
+ if (!address)
+ {
+ g_bRequiredInitLoaded = false;
+ return;
+ }
+
+ m_hook.Configure(address);
+
+ Message("Hooked %s at 0x%p\n", m_pSignatureName, address);
+ }
+
+ RETURN CallOriginal(CLASS* this_ptr, ARGS... args)
+ {
+ return m_hook.CallOriginal(this_ptr, args...);
+ }
+
+private:
+ KHook::Member m_hook;
+ const char* m_pSignatureName;
+};
+
+template
+auto MakeKHookMember(const char* pSignature, KHook::Return (*cbPre)(CLASS*, ARGS...), std::nullptr_t cbPost)
+{
+ return std::make_unique>(pSignature, cbPre, nullptr);
+}
+
+template
+auto MakeKHookMember(const char* pSignature, std::nullptr_t cbPre, KHook::Return (*cbPost)(CLASS*, ARGS...))
+{
+ return std::make_unique>(pSignature, nullptr, cbPost);
+}
+
+template
+auto MakeKHookMember(const char* pSignature, KHook::Return (*cbPre)(CLASS*, ARGS...), KHook::Return (*cbPost)(CLASS*, ARGS...))
+{
+ return std::make_unique>(pSignature, cbPre, cbPost);
+}
+
+#define KHOOK_MEMBER(name, pre, post) \
+ static auto hook##name = MakeKHookMember(#name, pre, post)
+
+template
+class CKHookVirtual : public CKHookBase
+{
+public:
+ using fnCallback = KHook::Return (*)(CLASS*, ARGS...);
+
+ CKHookVirtual(RETURN (CLASS::*pFunction)(ARGS...), CLASS*& pInstance, fnCallback cbPre, fnCallback cbPost) :
+ m_hook(pFunction, cbPre, cbPost), m_pInstance(pInstance)
+ {
+ GetKHookList().push_back(this);
+ }
+
+ ~CKHookVirtual()
+ {
+ if (m_hook.IsActive())
+ m_hook.Remove(m_pInstance);
+ }
+
+ virtual void Configure() override
+ {
+ if (!m_pInstance)
+ {
+ g_bRequiredInitLoaded = false;
+ return;
+ }
+
+ m_hook.Add(m_pInstance);
+ }
+
+ RETURN CallOriginal(CLASS* this_ptr, ARGS... args)
+ {
+ return m_hook.CallOriginal(this_ptr, args...);
+ }
+
+private:
+ KHook::Virtual m_hook;
+ CLASS*& m_pInstance;
+};
+
+template
+auto MakeKHookVirtual(RETURN (CLASS::*pFunction)(ARGS...), CLASS*& pInstance, KHook::Return (*cbPre)(CLASS*, ARGS...), std::nullptr_t cbPost)
+{
+ return std::make_unique>(pFunction, pInstance, cbPre, nullptr);
+}
+
+template
+auto MakeKHookVirtual(RETURN (CLASS::*pFunction)(ARGS...), CLASS*& pInstance, std::nullptr_t cbPre, KHook::Return (*cbPost)(CLASS*, ARGS...))
+{
+ return std::make_unique>(pFunction, pInstance, nullptr, cbPost);
+}
+
+template
+auto MakeKHookVirtual(RETURN (CLASS::*pFunction)(ARGS...), CLASS*& pInstance, KHook::Return (*cbPre)(CLASS*, ARGS...), KHook::Return (*cbPost)(CLASS*, ARGS...))
+{
+ return std::make_unique>(pFunction, pInstance, cbPre, cbPost);
+}
+
+#define KHOOK_VIRTUAL(name, function, instance, pre, post) \
+ static auto hook##name = MakeKHookVirtual(function, instance, pre, post)
diff --git a/src/utils/sighook.h b/src/utils/sighook.h
deleted file mode 100644
index 7adc7048..00000000
--- a/src/utils/sighook.h
+++ /dev/null
@@ -1,147 +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 "../gameconfig.h"
-#include "khook.hpp"
-
-class CSigHookBase
-{
-public:
- CSigHookBase(const char* pSignature) :
- m_pSignatureName(pSignature)
- {
- }
-
- virtual void Configure() = 0;
-
-protected:
- const char* m_pSignatureName;
-};
-
-extern std::vector& GetSigHookList();
-void InitSigHooks();
-
-template
-class CSigHookFunction : public CSigHookBase
-{
-public:
- using fnCallback = KHook::Return (*)(ARGS...);
-
- CSigHookFunction(const char* pSignature, fnCallback cbPre = nullptr, fnCallback cbPost = nullptr) :
- CSigHookBase(pSignature), m_hook(cbPre, cbPost)
- {
- GetSigHookList().push_back(this);
- }
-
- virtual void Configure() override
- {
- auto address = g_GameConfig->ResolveSignature(m_pSignatureName);
-
- if (!address)
- g_bRequiredInitLoaded = false;
-
- m_hook.Configure(address);
-
- Message("Hooked %s at 0x%p\n", m_pSignatureName, address);
- }
-
- RETURN CallOriginal(ARGS... args)
- {
- return m_hook.CallOriginal(args...);
- }
-
-private:
- KHook::Function m_hook;
-};
-
-template
-auto MakeSigHookFunction(const char* pSignature, KHook::Return (*cbPre)(ARGS...), std::nullptr_t cbPost)
-{
- return std::make_unique>(pSignature, cbPre, nullptr);
-}
-
-template
-auto MakeSigHookFunction(const char* pSignature, std::nullptr_t cbPre, KHook::Return (*cbPost)(ARGS...))
-{
- return std::make_unique>(pSignature, nullptr, cbPost);
-}
-
-template
-auto MakeSigHookFunction(const char* pSignature, KHook::Return (*cbPre)(ARGS...), KHook::Return (*cbPost)(ARGS...))
-{
- return std::make_unique>(pSignature, cbPre, cbPost);
-}
-
-#define SIG_HOOK_FUNCTION(name, pre, post) \
- static auto hook##name = MakeSigHookFunction(#name, pre, post)
-
-template
-class CSigHookMember : public CSigHookBase
-{
-public:
- using fnCallback = KHook::Return (*)(CLASS*, ARGS...);
-
- CSigHookMember(const char* pSignature, fnCallback cbPre = nullptr, fnCallback cbPost = nullptr) :
- CSigHookBase(pSignature), m_hook(cbPre, cbPost)
- {
- GetSigHookList().push_back(this);
- }
-
- virtual void Configure() override
- {
- auto address = g_GameConfig->ResolveSignature(m_pSignatureName);
-
- if (!address)
- g_bRequiredInitLoaded = false;
-
- m_hook.Configure(address);
-
- Message("Hooked %s at 0x%p\n", m_pSignatureName, address);
- }
-
- RETURN CallOriginal(CLASS* this_ptr, ARGS... args)
- {
- return m_hook.CallOriginal(this_ptr, args...);
- }
-
-private:
- KHook::Member m_hook;
-};
-
-template
-auto MakeSigHookMember(const char* pSignature, KHook::Return (*cbPre)(CLASS*, ARGS...), std::nullptr_t cbPost)
-{
- return std::make_unique>(pSignature, cbPre, nullptr);
-}
-
-template
-auto MakeSigHookMember(const char* pSignature, std::nullptr_t cbPre, KHook::Return (*cbPost)(CLASS*, ARGS...))
-{
- return std::make_unique>(pSignature, nullptr, cbPost);
-}
-
-template
-auto MakeSigHookMember(const char* pSignature, KHook::Return (*cbPre)(CLASS*, ARGS...), KHook::Return (*cbPost)(CLASS*, ARGS...))
-{
- return std::make_unique>(pSignature, cbPre, cbPost);
-}
-
-#define SIG_HOOK_MEMBER(name, pre, post) \
- static auto hook##name = MakeSigHookMember(#name, pre, post)
diff --git a/src/virtual_hooks.cpp b/src/virtual_hooks.cpp
index 4c53b24e..51c58fb9 100644
--- a/src/virtual_hooks.cpp
+++ b/src/virtual_hooks.cpp
@@ -27,6 +27,7 @@
#include "cs_gameevents.pb.h"
#include "cstrike15_usermessages.pb.h"
#include "ctimer.h"
+#include "engine/IEngineService.h"
#include "entities.h"
#include "entity/ccsplayercontroller.h"
#include "entity/customhudlayout.h"
@@ -40,6 +41,7 @@
#include "icvar.h"
#include "idlemanager.h"
#include "iserver.h"
+#include "khook_helpers.h"
#include "leader.h"
#include "map_votes.h"
#include "mapmigrations.h"
@@ -58,25 +60,26 @@
class GameSessionConfiguration_t
{};
-KHook::Virtual gameFrameHook(&IServerGameDLL::GameFrame, nullptr, Hook_GameFrame_Post);
-KHook::Virtual gameServerSteamAPIActivatedHook(&IServerGameDLL::GameServerSteamAPIActivated, Hook_GameServerSteamAPIActivated, nullptr);
-KHook::Virtual applyGameSettingsHook(&IServerGameDLL::ApplyGameSettings, Hook_ApplyGameSettings, nullptr);
-KHook::Virtual clientActiveHook(&IServerGameClients::ClientActive, nullptr, Hook_ClientActive_Post);
-KHook::Virtual clientDisconnectHook(&IServerGameClients::ClientDisconnect, nullptr, Hook_ClientDisconnect_Post);
-KHook::Virtual clientPutInServerHook(&IServerGameClients::ClientPutInServer, nullptr, Hook_ClientPutInServer_Post);
-KHook::Virtual clientSettingsChangedHook(&IServerGameClients::ClientSettingsChanged, Hook_ClientSettingsChanged, nullptr);
-KHook::Virtual onClientConnectedHook(&IServerGameClients::OnClientConnected, Hook_OnClientConnected, nullptr);
-KHook::Virtual clientConnectHook(&IServerGameClients::ClientConnect, Hook_ClientConnect, nullptr);
-KHook::Virtual clientCommandHook(&IServerGameClients::ClientCommand, Hook_ClientCommand, nullptr);
-KHook::Virtual clientSvcUserMessageHook(&IServerGameClients::ClientSvcUserMessage, Hook_ClientSvcUserMessage, nullptr);
-KHook::Virtual postEventAbstractHook(&IGameEventSystem::PostEventAbstract, Hook_PostEventAbstract, nullptr);
-KHook::Virtual startupServerHook(&INetworkServerService::StartupServer, nullptr, Hook_StartupServer_Post);
-KHook::Virtual&, CBitVec<16384>&, const Entity2Networkable_t**, const uint16*, int> checkTransmitHook(&ISource2GameEntities::CheckTransmit, nullptr, Hook_CheckTransmit_Post);
-KHook::Virtual dispatchConCommandHook(&ICvar::DispatchConCommand, Hook_DispatchConCommand, nullptr);
+KHOOK_VIRTUAL(GameFrame, &IServerGameDLL::GameFrame, g_pSource2Server, nullptr, Hook_GameFrame_Post);
+KHOOK_VIRTUAL(GameServerSteamAPIActivated, &IServerGameDLL::GameServerSteamAPIActivated, g_pSource2Server, Hook_GameServerSteamAPIActivated, nullptr);
+KHOOK_VIRTUAL(ApplyGameSettings, &IServerGameDLL::ApplyGameSettings, g_pSource2Server, Hook_ApplyGameSettings, nullptr);
+KHOOK_VIRTUAL(ClientActive, &IServerGameClients::ClientActive, g_pSource2GameClients, nullptr, Hook_ClientActive_Post);
+KHOOK_VIRTUAL(ClientDisconnect, &IServerGameClients::ClientDisconnect, g_pSource2GameClients, nullptr, Hook_ClientDisconnect_Post);
+KHOOK_VIRTUAL(ClientPutInServer, &IServerGameClients::ClientPutInServer, g_pSource2GameClients, nullptr, Hook_ClientPutInServer_Post);
+KHOOK_VIRTUAL(ClientSettingsChanged, &IServerGameClients::ClientSettingsChanged, g_pSource2GameClients, Hook_ClientSettingsChanged, nullptr);
+KHOOK_VIRTUAL(OnClientConnected, &IServerGameClients::OnClientConnected, g_pSource2GameClients, Hook_OnClientConnected, nullptr);
+KHOOK_VIRTUAL(ClientConnect, &IServerGameClients::ClientConnect, g_pSource2GameClients, Hook_ClientConnect, nullptr);
+KHOOK_VIRTUAL(ClientCommand, &IServerGameClients::ClientCommand, g_pSource2GameClients, Hook_ClientCommand, nullptr);
+KHOOK_VIRTUAL(ClientSvcUserMessage, &IServerGameClients::ClientSvcUserMessage, g_pSource2GameClients, Hook_ClientSvcUserMessage, nullptr);
+KHOOK_VIRTUAL(PostEventAbstract, &IGameEventSystem::PostEventAbstract, g_gameEventSystem, Hook_PostEventAbstract, nullptr);
+KHOOK_VIRTUAL(StartupServer, &INetworkServerService::StartupServer, g_pNetworkServerService, nullptr, Hook_StartupServer_Post);
+KHOOK_VIRTUAL(CheckTransmit, &ISource2GameEntities::CheckTransmit, g_pSource2GameEntities, nullptr, Hook_CheckTransmit_Post);
+KHOOK_VIRTUAL(DispatchConCommand, &ICvar::DispatchConCommand, g_pCVar, Hook_DispatchConCommand, nullptr);
+KHOOK_VIRTUAL(SwitchToLoop, &IEngineServiceMgr::SwitchToLoop, g_pEngineServiceMgr, Hook_SwitchToLoop, nullptr);
+
KHook::Virtual createWorkshopMapGroupHook(0U, Hook_CreateWorkshopMapGroup, nullptr);
KHook::Virtual loadEventsFromFileHook(&IGameEventManager2::LoadEventsFromFile, Hook_LoadEventsFromFile, nullptr);
KHook::Virtual fireEventHook(&IGameEventManager2::FireEvent, Hook_FireEvent, nullptr);
-KHook::Virtual spawnHook(&CEntitySystem::Spawn, Hook_Spawn, nullptr);
KHook::Virtual processVoiceDataHook(&CServerSideClient::ProcessVoiceData, Hook_ProcessVoiceData, nullptr);
KHook::Virtual setGameSpawnGroupMgrHook(&INetworkGameServer::SetGameSpawnGroupMgr, Hook_SetGameSpawnGroupMgr, nullptr);
KHook::Virtual*, bool> getTouchingListHook(0U, nullptr, Hook_GetTouchingList_Post);
@@ -90,7 +93,6 @@ KHook::Virtual onTakeDamageAliveHook(0U
KHook::Virtual playerPawnTeleportHook(0U, Hook_CCSPlayerPawn_Teleport, Hook_CCSPlayerPawn_Teleport_Post);
IGameEventManager2* g_pCGameEventManagerVTable = nullptr;
-CEntitySystem* g_pCEntitySystemVTable = nullptr;
CVPhys2World* g_pCVPhys2WorldVTable = nullptr;
CCSPlayer_MovementServices* g_pCCSPlayer_MovementServicesVTable = nullptr;
CCSPlayer_WeaponServices* g_pCCSPlayer_WeaponServicesVTable = nullptr;
@@ -139,27 +141,10 @@ void SetupGlobalVirtualHook(KHook::Virtual& hook, CLASS*
void InitVirtualHooks()
{
- 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);
- clientSvcUserMessageHook.Add(g_pSource2GameClients);
- postEventAbstractHook.Add(g_gameEventSystem);
- startupServerHook.Add(g_pNetworkServerService);
- checkTransmitHook.Add(g_pSource2GameEntities);
- dispatchConCommandHook.Add(g_pCVar);
-
SetupVirtualHook(createWorkshopMapGroupHook, "IGameTypes_CreateWorkshopMapGroup", g_pGameTypes);
SetupGlobalVirtualHook(loadEventsFromFileHook, g_pCGameEventManagerVTable, modules::server, "CGameEventManager");
SetupGlobalVirtualHook(fireEventHook, g_pCGameEventManagerVTable, modules::server, "CGameEventManager");
- SetupGlobalVirtualHook(spawnHook, g_pCEntitySystemVTable, modules::server, "CGameEntitySystem");
SetupGlobalVirtualHook(processVoiceDataHook, g_pCServerSideClientVTable, modules::engine, "CServerSideClient");
SetupGlobalVirtualHook(getTouchingListHook, g_pCVPhys2WorldVTable, modules::vphysics2, "CVPhys2World", "CVPhys2World::GetTouchingList");
SetupGlobalVirtualHook(checkMovingGroundHook, g_pCCSPlayer_MovementServicesVTable, modules::server, "CCSPlayer_MovementServices", "CCSPlayer_MovementServices::CheckMovingGround");
@@ -174,24 +159,8 @@ void InitVirtualHooks()
void RemoveVirtualHooks()
{
- 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);
- clientSvcUserMessageHook.Remove(g_pSource2GameClients);
- postEventAbstractHook.Remove(g_gameEventSystem);
- startupServerHook.Remove(g_pNetworkServerService);
- checkTransmitHook.Remove(g_pSource2GameEntities);
- dispatchConCommandHook.Remove(g_pCVar);
loadEventsFromFileHook.RemoveGlobal((IGameEventManager2*)&g_pCGameEventManagerVTable);
fireEventHook.RemoveGlobal((IGameEventManager2*)&g_pCGameEventManagerVTable);
- spawnHook.RemoveGlobal((CEntitySystem*)&g_pCEntitySystemVTable);
processVoiceDataHook.RemoveGlobal((CServerSideClient*)&g_pCServerSideClientVTable);
setGameSpawnGroupMgrHook.Remove(GetNetworkGameServer());
createWorkshopMapGroupHook.Remove(g_pGameTypes);
@@ -293,6 +262,7 @@ KHook::Return Hook_ClientDisconnect_Post(IServerGameClients* pThis, CPlaye
if (reason != NETWORK_DISCONNECT_LOOPSHUTDOWN && reason != NETWORK_DISCONNECT_SHUTDOWN)
g_pAdminSystem->AddDisconnectedPlayer(pszName, xuid, pPlayer ? pPlayer->GetIpAddress() : "");
+ CCSCustomHudLayout::OnClientDisconnect(slot.Get());
g_playerManager->OnClientDisconnect(slot);
return {KHook::Action::Ignore};
@@ -435,7 +405,7 @@ KHook::Return Hook_PostEventAbstract(IGameEventSystem* pThis, CSplitScreen
uint64 clientMask = *(uint64*)clients & g_playerManager->GetSilenceSoundMask();
- postEventAbstractHook.CallOriginal(pThis, nSlot, bLocalOnly, nClientCount, &clientMask, pEvent, msg, nSize, bufType);
+ hookPostEventAbstract->CallOriginal(pThis, nSlot, bLocalOnly, nClientCount, &clientMask, pEvent, msg, nSize, bufType);
msg->set_weapon_id(weapon_id);
msg->set_sound_type(sound_type);
@@ -702,7 +672,7 @@ KHook::Return Hook_DispatchConCommand(ICvar* pThis, ConCommandRef cmdHandl
if (!bGagged && !bSilent && !bFlooding)
{
- dispatchConCommandHook.CallOriginal(pThis, cmdHandle, ctx, args);
+ hookDispatchConCommand->CallOriginal(pThis, cmdHandle, ctx, args);
// Reset idle time if message is sent to chat
if (g_cvarIdleKickTime.Get() > 0.0f)
@@ -753,6 +723,17 @@ KHook::Return Hook_DispatchConCommand(ICvar* pThis, ConCommandRef cmdHandl
return {KHook::Action::Ignore};
}
+KHook::Return Hook_SwitchToLoop(IEngineServiceMgr* pThis, const char* pszLoopModeName, KeyValues* pKV, uint32 nId, const char* pszAddonName, bool bUnk)
+{
+ if (pKV && !V_strcmp(pszLoopModeName, "levelload"))
+ {
+ g_pCfgParser->PreLevelLoad(pKV->GetString("levelname", ""));
+ g_pMapMigrations->PreLevelLoad(pKV->GetUint64("customgamemode", 0));
+ }
+
+ return {KHook::Action::Ignore};
+}
+
KHook::Return Hook_CreateWorkshopMapGroup(IGameTypes* pThis, const char* name, const CUtlStringList& mapList)
{
if (g_cvarVoteManagerEnable.Get() && g_pMapVoteSystem->IsMapListLoaded())
@@ -771,7 +752,7 @@ KHook::Return Hook_LoadEventsFromFile(IGameEventManager2* pThis, const char
KHook::Return Hook_FireEvent(IGameEventManager2* pThis, IGameEvent* pEvent, bool bDontBroadcast)
{
// Make player_connect obey cs2f_map_steamids_enable as well
- if (!g_cvarEnableMapSteamIds.Get() && !V_stricmp(pEvent->GetName(), "player_connect"))
+ if (!g_cvarEnableMapSteamIds.Get() && pEvent && !V_stricmp(pEvent->GetName(), "player_connect"))
{
pEvent->SetString("networkid", "");
pEvent->SetUint64("xuid", 0);
@@ -780,14 +761,6 @@ KHook::Return Hook_FireEvent(IGameEventManager2* pThis, IGameEvent* pEvent
return {KHook::Action::Ignore};
}
-KHook::Return Hook_Spawn(CEntitySystem* pThis, int nCount, const EntitySpawnInfo_t* pInfo)
-{
- for (int i = 0; i < nCount; i++)
- g_pMapMigrations->OnEntitySpawned_Pre(reinterpret_cast(pInfo[i].m_pEntity->m_pInstance), pInfo[i].m_pKeyValues);
-
- return {KHook::Action::Ignore};
-}
-
KHook::Return Hook_ProcessVoiceData(CServerSideClient* pClient, const CCLCMsg_VoiceData_t& msg)
{
ZEPlayer* pPlayer = g_playerManager->GetPlayer(pClient->GetPlayerSlot());
diff --git a/src/virtual_hooks.h b/src/virtual_hooks.h
index 90871bb6..36f0b450 100644
--- a/src/virtual_hooks.h
+++ b/src/virtual_hooks.h
@@ -36,6 +36,7 @@ class CCSPlayerPawn;
class CVPhys2World;
class CTriggerGravity;
class CGameConfig;
+class IEngineServiceMgr;
void InitVirtualHooks();
void RemoveVirtualHooks();
@@ -57,10 +58,10 @@ KHook::Return Hook_StartupServer_Post(INetworkServerService* pThis, const
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_SwitchToLoop(IEngineServiceMgr* pThis, const char* pszLoopModeName, KeyValues* pKV, uint32 nId, const char* pszAddonName, bool bUnk);
KHook::Return Hook_CreateWorkshopMapGroup(IGameTypes* pThis, const char* name, const CUtlStringList& mapList);
KHook::Return Hook_LoadEventsFromFile(IGameEventManager2* pThis, const char* filename, bool bSearchAll);
KHook::Return Hook_FireEvent(IGameEventManager2* pThis, IGameEvent* pEvent, bool bDontBroadcast);
-KHook::Return Hook_Spawn(CEntitySystem* pThis, int nCount, const EntitySpawnInfo_t* pInfo);
KHook::Return Hook_ProcessVoiceData(CServerSideClient* pClient, const CCLCMsg_VoiceData_t& msg);
KHook::Return Hook_SetGameSpawnGroupMgr(INetworkGameServer* pThis, IGameSpawnGroupMgr* pSpawnGroupMgr);
KHook::Return Hook_GetTouchingList_Post(CVPhys2World* pThis, CUtlVector* pList, bool unknown);