From d6da007fd6855dff3f9d42f2f0286c73b5143081 Mon Sep 17 00:00:00 2001 From: FirstLemon Date: Thu, 24 Jul 2025 14:31:41 +0200 Subject: [PATCH 1/6] holylib: OnMapChangeHook added --- source/modules/holylib.cpp | 43 ++++++++++++++++++++++++++++++++++++++ source/symbols.cpp | 6 ++++++ source/symbols.h | 4 ++++ 3 files changed, 53 insertions(+) diff --git a/source/modules/holylib.cpp b/source/modules/holylib.cpp index 1ba2d997..fa409e65 100644 --- a/source/modules/holylib.cpp +++ b/source/modules/holylib.cpp @@ -12,6 +12,7 @@ #include "sourcesdk/baseclient.h" #include "hl2/hl2_player.h" #include "unordered_set" +#include "host_state.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -373,6 +374,39 @@ LUA_FUNCTION_STATIC(ReceiveClientMessage) return 0; } +static char pLevelName[256], pLandmarkName[256] = {0}; +static Detouring::Hook detour_CHostState_State_ChangeLevelMP; +static void hook_CHostState_State_ChangeLevelMP(const char* levelName, const char* landmarkName) +{ + if (levelName) { + strncpy(pLevelName, levelName, sizeof(pLevelName) - 1); + pLevelName[sizeof(pLevelName) - 1] = '\0'; + } + + if (landmarkName) { + strncpy(pLandmarkName, landmarkName, sizeof(pLandmarkName) - 1); + pLandmarkName[sizeof(pLandmarkName) - 1] = '\0'; + } + else { + pLandmarkName[0] = '\0'; + } + + detour_CHostState_State_ChangeLevelMP.GetTrampoline()(levelName, landmarkName); +} + +void CHolyLibModule::LevelShutdown() +{ + if (Lua::PushHook("HolyLib:OnMapChange")) + { + g_Lua->PushString(pLevelName); + g_Lua->PushString(pLandmarkName); + g_Lua->CallFunctionProtected(3, 0, true); + } + + pLevelName[0] = '\0'; + pLandmarkName[0] = '\0'; +} + void CHolyLibModule::LuaInit(GarrysMod::Lua::ILuaInterface* pLua, bool bServerInit) { if (!bServerInit) @@ -455,6 +489,15 @@ void CHolyLibModule::InitDetour(bool bPreServer) (void*)hook_GetGModServerTags, m_pID ); +<<<<<<< HEAD +======= + Detour::Create( + &detour_CHostState_State_ChangeLevelMP, "CHostState_State_ChangeLevelMP", + engine_loader.GetModule(), Symbols::CHostState_State_ChangeLevelMPSym, + (void *)hook_CHostState_State_ChangeLevelMP, m_pID + ); + +>>>>>>> 91c13cd (holylib: changelevel hook experimenting) func_CBaseAnimating_InvalidateBoneCache = (Symbols::CBaseAnimating_InvalidateBoneCache)Detour::GetFunction(server_loader.GetModule(), Symbols::CBaseAnimating_InvalidateBoneCacheSym); Detour::CheckFunction((void*)func_CBaseAnimating_InvalidateBoneCache, "CBaseAnimating::InvalidateBoneCache"); diff --git a/source/symbols.cpp b/source/symbols.cpp index 8c30f611..8b7921f3 100644 --- a/source/symbols.cpp +++ b/source/symbols.cpp @@ -130,6 +130,12 @@ namespace Symbols Symbol::FromName("_ZN11CBaseEntity11SetMoveTypeE10MoveType_t13MoveCollide_t"), NULL_SIGNATURE, // ToDo }; + const std::vector CHostState_State_ChangeLevelMPSym = { + Symbol::FromName("_Z23HostState_ChangeLevelMPPKcS0_"), // + NULL_SIGNATURE, + NULL_SIGNATURE, + NULL_SIGNATURE, + }; //--------------------------------------------------------------------------------- // Purpose: gameevent Symbols diff --git a/source/symbols.h b/source/symbols.h index 30e6b15c..7c660458 100644 --- a/source/symbols.h +++ b/source/symbols.h @@ -63,6 +63,7 @@ struct edict_t; struct PackWork_t; class CBaseViewModel; class CBaseCombatCharacter; +class CHostState; class CGameTrace; typedef CGameTrace trace_t; @@ -172,6 +173,9 @@ namespace Symbols typedef void (GMCOMMON_CALLING_CONVENTION* CBaseEntity_SetMoveType)(void* ent, int, int); extern const std::vector CBaseEntity_SetMoveTypeSym; + typedef void (GMCOMMON_CALLING_CONVENTION* CHostState_State_ChangeLevelMP)(const char* levelName, const char* LandmarkName); + extern const std::vector CHostState_State_ChangeLevelMPSym; + //--------------------------------------------------------------------------------- // Purpose: gameevent Symbols //--------------------------------------------------------------------------------- From f815dc0e3b6a97ae70a13897e4258eb8b0841e25 Mon Sep 17 00:00:00 2001 From: FirstLemon Date: Thu, 24 Jul 2025 20:24:01 +0200 Subject: [PATCH 2/6] MapChangeHook: fixed issues --- gluatests/lua/autorun/gmod_tests_init.lua | 10 ++++++++- .../lua/tests/modules/holylib/OnMapChange.lua | 22 +++++++++++++++++++ source/modules/holylib.cpp | 9 +++++--- 3 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 gluatests/lua/tests/modules/holylib/OnMapChange.lua diff --git a/gluatests/lua/autorun/gmod_tests_init.lua b/gluatests/lua/autorun/gmod_tests_init.lua index 1bcfb89c..c0b2dd54 100644 --- a/gluatests/lua/autorun/gmod_tests_init.lua +++ b/gluatests/lua/autorun/gmod_tests_init.lua @@ -1,5 +1,13 @@ include( "gmod_tests/sh_init.lua" ) +hook.Add("HolyLib:OnMapChange", "HookOnMapChangeTest", function(levelName, levelLandmark) + local currentMap = game.GetMap() + + if not file.Exists("HookOnMapChangeData.txt", "DATA") then + file.Write("HookOnMapChangeData.txt", currentMap .. "\n" .. levelName .. "\n" .. levelLandmark) + end +end) + -- We change the level once and run everything again as in rare cases a crash might only ocurr after a map change. hook.Add("GLuaTest_Finished", "ChangeLevel", function() if not file.Exists("waschanged.txt", "DATA") then @@ -7,4 +15,4 @@ hook.Add("GLuaTest_Finished", "ChangeLevel", function() RunConsoleCommand("changelevel", game.GetMap()) end -end) \ No newline at end of file +end) diff --git a/gluatests/lua/tests/modules/holylib/OnMapChange.lua b/gluatests/lua/tests/modules/holylib/OnMapChange.lua new file mode 100644 index 00000000..bdf0d39a --- /dev/null +++ b/gluatests/lua/tests/modules/holylib/OnMapChange.lua @@ -0,0 +1,22 @@ +return { + groupName = "HolyLib:OnMapChange", + cases = { + { + name = "Hook is called with correct map name", + when = file.Exists("HookOnMapChangeData.txt", "DATA"), + func = function(state) + local data = file.Read("HookOnMapChangeData.txt", "DATA") + expect(data).to.exist() + + local lines = string.Explode("\n", data) + expect(#lines).to.beGreaterThan(1) + + local oldLevel = lines[1] + local levelName = lines[2] + + expect(levelName).to.beA("string") + expect(levelName).to.equal(oldLevel) + end, + }, + } +} diff --git a/source/modules/holylib.cpp b/source/modules/holylib.cpp index fa409e65..d8978586 100644 --- a/source/modules/holylib.cpp +++ b/source/modules/holylib.cpp @@ -26,6 +26,8 @@ class CHolyLibModule : public IModule virtual const char* Name() { return "holylib"; }; virtual int Compatibility() { return LINUX32 | LINUX64; }; virtual bool SupportsMultipleLuaStates() { return true; }; + + virtual void LevelShutdown() OVERRIDE; }; static CHolyLibModule g_pHolyLibModule; @@ -396,6 +398,10 @@ static void hook_CHostState_State_ChangeLevelMP(const char* levelName, const cha void CHolyLibModule::LevelShutdown() { + if (*pLevelName == '\0') { + return; + } + if (Lua::PushHook("HolyLib:OnMapChange")) { g_Lua->PushString(pLevelName); @@ -489,15 +495,12 @@ void CHolyLibModule::InitDetour(bool bPreServer) (void*)hook_GetGModServerTags, m_pID ); -<<<<<<< HEAD -======= Detour::Create( &detour_CHostState_State_ChangeLevelMP, "CHostState_State_ChangeLevelMP", engine_loader.GetModule(), Symbols::CHostState_State_ChangeLevelMPSym, (void *)hook_CHostState_State_ChangeLevelMP, m_pID ); ->>>>>>> 91c13cd (holylib: changelevel hook experimenting) func_CBaseAnimating_InvalidateBoneCache = (Symbols::CBaseAnimating_InvalidateBoneCache)Detour::GetFunction(server_loader.GetModule(), Symbols::CBaseAnimating_InvalidateBoneCacheSym); Detour::CheckFunction((void*)func_CBaseAnimating_InvalidateBoneCache, "CBaseAnimating::InvalidateBoneCache"); From b620c8ce6f1483a590f50b97d1fbc7c523e34b16 Mon Sep 17 00:00:00 2001 From: Lemon <66844236+FirstLemon@users.noreply.github.com> Date: Sat, 26 Jul 2025 02:13:18 +0200 Subject: [PATCH 3/6] readme: added OnMapChange hook entry --- README.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3cb25946..3053b2eb 100644 --- a/README.md +++ b/README.md @@ -375,6 +375,21 @@ Called when a gets off a ladder -> Direct bind to `CFuncLadder::PlayerGotOff`
If you call `Entity:SetMoveType` on the current entity inside this hook, it would have no effect.
+#### HolyLib:OnMapChange(string levelName, string landmarkName) +> [!NOTE] +> This currently works only `LINUX32` + +Called right before a level transition occurs, this hook allows you to react to map changes initiated via changelevel. +- string levelName — The name of the level we are changing to. +- string levelLandmark — The name of the landmark (if any) otherwise, an empty string. +Example usage: +```lua +hook.Add("HolyLib:OnMapChange", "HelloThere", function(levelName, landmarkName) + print("Next level name: " .. levelName) + print("Using landmark: " .. landmarkName) +end) +``` + ## gameevent This module contains additional functions for the gameevent library.
With the Add/Get/RemoveClient* functions you can control the gameevents that are networked to a client which can be useful.
@@ -4514,4 +4529,4 @@ PrintTable(fileTable) ## Usage with vphysics jolt Currently there might be bugs when combining holylib with VPhysics-Jolt.
This mainly affects the `physenv` module and most other modules should be completely fine.
-Only VPhysics-Jolt builds from https://github.com/RaphaelIT7/VPhysics-Jolt will be suppored for now due to holylib requiring extented functionality.
\ No newline at end of file +Only VPhysics-Jolt builds from https://github.com/RaphaelIT7/VPhysics-Jolt will be suppored for now due to holylib requiring extented functionality.
From 6469eda44d1f9452f4acef4e134c90dbe591b53d Mon Sep 17 00:00:00 2001 From: FirstLemon Date: Sat, 26 Jul 2025 02:46:40 +0200 Subject: [PATCH 4/6] OnMapChange: fixed formatting --- source/modules/holylib.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/source/modules/holylib.cpp b/source/modules/holylib.cpp index d8978586..91e4e76f 100644 --- a/source/modules/holylib.cpp +++ b/source/modules/holylib.cpp @@ -26,7 +26,6 @@ class CHolyLibModule : public IModule virtual const char* Name() { return "holylib"; }; virtual int Compatibility() { return LINUX32 | LINUX64; }; virtual bool SupportsMultipleLuaStates() { return true; }; - virtual void LevelShutdown() OVERRIDE; }; @@ -380,16 +379,19 @@ static char pLevelName[256], pLandmarkName[256] = {0}; static Detouring::Hook detour_CHostState_State_ChangeLevelMP; static void hook_CHostState_State_ChangeLevelMP(const char* levelName, const char* landmarkName) { - if (levelName) { + if (levelName) + { strncpy(pLevelName, levelName, sizeof(pLevelName) - 1); pLevelName[sizeof(pLevelName) - 1] = '\0'; } - if (landmarkName) { + if (landmarkName) + { strncpy(pLandmarkName, landmarkName, sizeof(pLandmarkName) - 1); pLandmarkName[sizeof(pLandmarkName) - 1] = '\0'; } - else { + else + { pLandmarkName[0] = '\0'; } From 56b3ba37788e9758d8f9d18878b1456944624f4f Mon Sep 17 00:00:00 2001 From: FirstLemon Date: Sat, 26 Jul 2025 03:02:27 +0200 Subject: [PATCH 5/6] OnMapHook: fixed more formatting issues darn --- source/modules/holylib.cpp | 8 +++----- source/symbols.cpp | 3 ++- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/source/modules/holylib.cpp b/source/modules/holylib.cpp index 91e4e76f..ddf1ce76 100644 --- a/source/modules/holylib.cpp +++ b/source/modules/holylib.cpp @@ -23,10 +23,10 @@ class CHolyLibModule : public IModule virtual void LuaInit(GarrysMod::Lua::ILuaInterface* pLua, bool bServerInit) OVERRIDE; virtual void LuaShutdown(GarrysMod::Lua::ILuaInterface* pLua) OVERRIDE; virtual void InitDetour(bool bPreServer) OVERRIDE; + virtual void LevelShutdown() OVERRIDE; virtual const char* Name() { return "holylib"; }; virtual int Compatibility() { return LINUX32 | LINUX64; }; virtual bool SupportsMultipleLuaStates() { return true; }; - virtual void LevelShutdown() OVERRIDE; }; static CHolyLibModule g_pHolyLibModule; @@ -389,9 +389,7 @@ static void hook_CHostState_State_ChangeLevelMP(const char* levelName, const cha { strncpy(pLandmarkName, landmarkName, sizeof(pLandmarkName) - 1); pLandmarkName[sizeof(pLandmarkName) - 1] = '\0'; - } - else - { + } else { pLandmarkName[0] = '\0'; } @@ -500,7 +498,7 @@ void CHolyLibModule::InitDetour(bool bPreServer) Detour::Create( &detour_CHostState_State_ChangeLevelMP, "CHostState_State_ChangeLevelMP", engine_loader.GetModule(), Symbols::CHostState_State_ChangeLevelMPSym, - (void *)hook_CHostState_State_ChangeLevelMP, m_pID + (void*)hook_CHostState_State_ChangeLevelMP, m_pID ); func_CBaseAnimating_InvalidateBoneCache = (Symbols::CBaseAnimating_InvalidateBoneCache)Detour::GetFunction(server_loader.GetModule(), Symbols::CBaseAnimating_InvalidateBoneCacheSym); diff --git a/source/symbols.cpp b/source/symbols.cpp index 8b7921f3..e6b80d43 100644 --- a/source/symbols.cpp +++ b/source/symbols.cpp @@ -130,8 +130,9 @@ namespace Symbols Symbol::FromName("_ZN11CBaseEntity11SetMoveTypeE10MoveType_t13MoveCollide_t"), NULL_SIGNATURE, // ToDo }; + const std::vector CHostState_State_ChangeLevelMPSym = { - Symbol::FromName("_Z23HostState_ChangeLevelMPPKcS0_"), // + Symbol::FromName("_Z23HostState_ChangeLevelMPPKcS0_"), NULL_SIGNATURE, NULL_SIGNATURE, NULL_SIGNATURE, From c2609b0f9f0702e865815dbda90ad4aa7240da04 Mon Sep 17 00:00:00 2001 From: FirstLemon Date: Sat, 26 Jul 2025 03:30:20 +0200 Subject: [PATCH 6/6] OnMapChange: using V_strncpy instead of strncpy --- source/modules/holylib.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/source/modules/holylib.cpp b/source/modules/holylib.cpp index ddf1ce76..cfb6ca4c 100644 --- a/source/modules/holylib.cpp +++ b/source/modules/holylib.cpp @@ -381,14 +381,12 @@ static void hook_CHostState_State_ChangeLevelMP(const char* levelName, const cha { if (levelName) { - strncpy(pLevelName, levelName, sizeof(pLevelName) - 1); - pLevelName[sizeof(pLevelName) - 1] = '\0'; + V_strncpy(pLevelName, levelName, sizeof(pLevelName)); } if (landmarkName) { - strncpy(pLandmarkName, landmarkName, sizeof(pLandmarkName) - 1); - pLandmarkName[sizeof(pLandmarkName) - 1] = '\0'; + V_strncpy(pLandmarkName, landmarkName, sizeof(pLandmarkName)); } else { pLandmarkName[0] = '\0'; }