From 7c6d6e423adea386fa08e2489ed45e37637b3a99 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Mon, 12 Aug 2024 02:21:23 +0800 Subject: [PATCH 01/73] Rework detours --- addons/sourcemod/gamedata/sendproxy.txt | 31 ++ extension/extension.cpp | 398 ++++++------------------ extension/extension.h | 38 ++- extension/wrappers.h | 48 ++- 4 files changed, 192 insertions(+), 323 deletions(-) diff --git a/addons/sourcemod/gamedata/sendproxy.txt b/addons/sourcemod/gamedata/sendproxy.txt index 68e0c2d..0f7f244 100644 --- a/addons/sourcemod/gamedata/sendproxy.txt +++ b/addons/sourcemod/gamedata/sendproxy.txt @@ -8,8 +8,34 @@ "game" "left4dead2" } + "Offsets" + { + "CGameClient::edict" + { + "linux" "120656" + } + } + + "Addresses" + { + "framesnapshotmanager" + { + "linux" + { + "signature" "framesnapshotmanager" + } + "read" "0" + } + } + "Signatures" { + "framesnapshotmanager" + { + "library" "engine" + "linux" "@framesnapshotmanager" + } + "CGameClient::ShouldSendMessages" { "library" "engine" @@ -52,6 +78,11 @@ "linux" "@_ZN21CFrameSnapshotManager21RemoveEntityReferenceEi" "windows" "\x55\x8B\xEC\x51\x8B\x45\x08\x53\x8B\x18" } + "CFrameSnapshotManager::TakeTickSnapshot" + { + "library" "engine" + "linux" "@_ZN21CFrameSnapshotManager16TakeTickSnapshotEi" + } } } "tf" diff --git a/extension/extension.cpp b/extension/extension.cpp index d52c608..2773ee1 100644 --- a/extension/extension.cpp +++ b/extension/extension.cpp @@ -48,6 +48,7 @@ #include "extension.h" #include "interfaceimpl.h" #include "natives.h" +#include "wrappers.h" //path: hl2sdk-/public/.h, "../public/" included to prevent compile errors due wrong directory scanning by compiler on my computer, and I'm too lazy to find where I can change that =D #include <../public/iserver.h> @@ -55,14 +56,10 @@ SH_DECL_HOOK1_void(IServerGameClients, ClientDisconnect, SH_NOATTRIB, false, edict_t *); SH_DECL_HOOK1_void(IServerGameDLL, GameFrame, SH_NOATTRIB, false, bool); -SH_DECL_HOOK0(IServer, GetClientCount, const, false, int); -DECL_DETOUR(CGameServer_SendClientMessages); -DECL_DETOUR(CGameClient_ShouldSendMessages); DECL_DETOUR(CFrameSnapshotManager_UsePreviouslySentPacket); DECL_DETOUR(CFrameSnapshotManager_GetPreviouslySentPacket); DECL_DETOUR(CFrameSnapshotManager_CreatePackedEntity); -// DECL_DETOUR(CFrameSnapshotManager_RemoveEntityReference); DECL_DETOUR(SV_ComputeClientPacks); class CGameClient; @@ -71,11 +68,7 @@ class CGlobalEntityList; CGameClient * g_pCurrentGameClientPtr = nullptr; int g_iCurrentClientIndexInLoop = -1; //used for optimization -bool g_bCurrentGameClientCallFwd = false; bool g_bCallingForNullClients = false; -bool g_bFirstTimeCalled = true; -bool g_bSVComputePacksDone = false; -IServer * g_pIServer = nullptr; SendProxyManager g_SendProxyManager; SendProxyManagerInterfaceImpl * g_pMyInterface = nullptr; @@ -88,10 +81,9 @@ CUtlVector g_HooksGamerules; CUtlVector g_ChangeHooks; CUtlVector g_ChangeHooksGamerules; -CUtlVector g_vHookedEdicts; - IServerGameEnts * gameents = nullptr; IServerGameClients * gameclients = nullptr; +IBinTools* bintools = NULL; ISDKTools * g_pSDKTools = nullptr; ISDKHooks * g_pSDKHooks = nullptr; IGameConfig * g_pGameConf = nullptr; @@ -104,19 +96,21 @@ edict_t * g_pGameRulesProxyEdict = nullptr; int g_iGameRulesProxyIndex = -1; PackedEntityHandle_t g_PlayersPackedGameRules[SM_MAXPLAYERS] = {INVALID_PACKED_ENTITY_HANDLE}; void * g_pGameRules = nullptr; -bool g_bShouldChangeGameRulesState = false; bool g_bSendSnapshots = false; bool g_bEdictChanged[MAX_EDICTS] = {false}; -CGlobalVars * g_pGlobals = nullptr; - static CBaseEntity * FindEntityByServerClassname(int, const char *); static void CallChangeCallbacks(PropChangeHook * pInfo, void * pOldValue, void * pNewValue); static void CallChangeGamerulesCallbacks(PropChangeHookGamerules * pInfo, void * pOldValue, void * pNewValue); const char * g_szGameRulesProxy; +CFrameSnapshotManager* framesnapshotmanager = nullptr; +void* CFrameSnapshotManager::s_pfnTakeTickSnapshot = nullptr; +ICallWrapper* CFrameSnapshotManager::s_callTakeTickSnapshot = nullptr; +int CGameClient::s_iOffs_edict = -1; + //detours /*Call stack: @@ -142,7 +136,6 @@ const char * g_szGameRulesProxy; DETOUR_DECL_MEMBER3(CFrameSnapshotManager_UsePreviouslySentPacket, bool, CFrameSnapshot*, pSnapshot, int, entity, int, entSerialNumber) { if (g_iCurrentClientIndexInLoop == -1 - || !g_bCurrentGameClientCallFwd || entity != g_iGameRulesProxyIndex) { return DETOUR_MEMBER_CALL(CFrameSnapshotManager_UsePreviouslySentPacket)(pSnapshot, entity, entSerialNumber); @@ -159,7 +152,6 @@ DETOUR_DECL_MEMBER3(CFrameSnapshotManager_UsePreviouslySentPacket, bool, CFrameS DETOUR_DECL_MEMBER2(CFrameSnapshotManager_GetPreviouslySentPacket, PackedEntity*, int, entity, int, entSerialNumber) { if (g_iCurrentClientIndexInLoop == -1 - || !g_bCurrentGameClientCallFwd || entity != g_iGameRulesProxyIndex) { return DETOUR_MEMBER_CALL(CFrameSnapshotManager_GetPreviouslySentPacket)(entity, entSerialNumber); @@ -180,7 +172,6 @@ DETOUR_DECL_MEMBER2(CFrameSnapshotManager_GetPreviouslySentPacket, PackedEntity* DETOUR_DECL_MEMBER2(CFrameSnapshotManager_CreatePackedEntity, PackedEntity*, CFrameSnapshot*, pSnapshot, int, entity) { if (g_iCurrentClientIndexInLoop == -1 - || !g_bCurrentGameClientCallFwd || entity != g_iGameRulesProxyIndex) { return DETOUR_MEMBER_CALL(CFrameSnapshotManager_CreatePackedEntity)(pSnapshot, entity); @@ -203,68 +194,30 @@ DETOUR_DECL_MEMBER2(CFrameSnapshotManager_CreatePackedEntity, PackedEntity*, CFr return result; } -// DETOUR_DECL_MEMBER1(CFrameSnapshotManager_RemoveEntityReference, void, PackedEntityHandle_t, handle) -// { -// CFrameSnapshotManager *framesnapshotmanager = (CFrameSnapshotManager *)this; - -// PackedEntity *packedEntity = framesnapshotmanager->m_PackedEntities[handle]; -// if ( packedEntity->m_ReferenceCount <= 1) -// { -// for (int i = 0; i < (sizeof(g_PlayersPackedGameRules) / sizeof(g_PlayersPackedGameRules[0])); ++i) -// { -// if (g_PlayersPackedGameRules[i] == handle) -// { -// g_PlayersPackedGameRules[i] = INVALID_PACKED_ENTITY_HANDLE; - -// #ifdef DEBUG -// char buffer[128]; -// for (int client = 1; client <= playerhelpers->GetMaxClients(); client++) -// { -// IGamePlayer *plr = playerhelpers->GetGamePlayer(client); -// if (plr && plr->IsInGame() && !plr->IsFakeClient()) -// { -// smutils->Format(buffer, sizeof(buffer), "RemoveEntityReference: (%d / %d)", handle, i + 1); -// gamehelpers->TextMsg(client, 3, buffer); -// } -// } -// #endif -// } -// } -// } - -// DETOUR_MEMBER_CALL(CFrameSnapshotManager_RemoveEntityReference)(handle); -// } - #ifdef _FORCE_DEBUG #undef DEBUG #endif -DETOUR_DECL_MEMBER1(CGameServer_SendClientMessages, void, bool, bSendSnapshots) +#if defined __linux__ +void __attribute__((__cdecl__)) SV_ComputeClientPacks_ActualCall(int iClientCount, CGameClient ** pClients, CFrameSnapshot * pSnapShot); +#else +void __cdecl SV_ComputeClientPacks_ActualCall(int iClientCount, CGameClient ** pClients, CFrameSnapshot * pSnapShot); +#endif + +//the better idea rewrite it with __declspec(naked) for csgo or use __stdcall function as main callback instead of this +DETOUR_DECL_STATIC3(SV_ComputeClientPacks, void, int, iClientCount, CGameClient **, pClients, CFrameSnapshot *, pSnapShot) { - if (!bSendSnapshots) - { - g_bSendSnapshots = false; - return DETOUR_MEMBER_CALL(CGameServer_SendClientMessages)(false); //if so, we do not interested in this call - } - else - g_bSendSnapshots = true; - if (!g_pIServer && g_pSDKTools) - g_pIServer = g_pSDKTools->GetIServer(); - if (!g_pIServer) - return DETOUR_MEMBER_CALL(CGameServer_SendClientMessages)(true); //if so, we should stop to process this function! See below - if (g_bFirstTimeCalled) - { -#ifdef _WIN32 - //HACK, don't delete this, or server will be crashed on start! - g_pIServer->GetClientCount(); +#if defined _WIN32 && SOURCE_ENGINE == SE_CSGO + //so, here it is __userpurge call, we need manually get our arguments + __asm mov iClientCount, ecx + __asm mov pClients, edx + __asm mov pSnapShot, ebx // @Forgetest: ???Why??? #endif - SH_ADD_HOOK(IServer, GetClientCount, g_pIServer, SH_MEMBER(&g_SendProxyManager, &SendProxyManager::GetClientCount), false); - g_bFirstTimeCalled = false; - } + bool bEdictChanged[MAX_EDICTS]; for (int i = 0; i < MAX_EDICTS; ++i) { - g_bEdictChanged[i] = false; + bEdictChanged[i] = false; edict_t *edict = gamehelpers->EdictOfIndex(i); if (!edict || !edict->GetUnknown() || edict->IsFree()) @@ -272,204 +225,37 @@ DETOUR_DECL_MEMBER1(CGameServer_SendClientMessages, void, bool, bSendSnapshots) if (i > 0 && i <= playerhelpers->GetMaxClients()) { - if (!g_pIServer->GetClient(i-1)->IsActive()) + if (!playerhelpers->GetGamePlayer(i)->IsInGame()) continue; } if (!edict->HasStateChanged()) continue; - g_bEdictChanged[i] = true; + bEdictChanged[i] = true; } - bool bCalledForNullIClientsThisTime = false; - for (int iClients = 1; iClients <= playerhelpers->GetMaxClients(); iClients++) + g_iCurrentClientIndexInLoop = gamehelpers->IndexOfEdict(pClients[0]->GetEdict()) - 1; + SV_ComputeClientPacks_ActualCall(1, &pClients[0], pSnapShot); + + for (int i = 1; i < iClientCount; ++i) { - IGamePlayer * pPlayer = playerhelpers->GetGamePlayer(iClients); - bool bFake = (pPlayer->IsFakeClient() && !(pPlayer->IsSourceTV() -#if SOURCE_ENGINE != SE_CSGO - || pPlayer->IsReplay() -#endif - )); - volatile IClient * pClient = nullptr; //volatile used to prevent optimizations here for some reason - if (!pPlayer->IsConnected() || bFake || (pClient = g_pIServer->GetClient(iClients - 1)) == nullptr) + g_iCurrentClientIndexInLoop = gamehelpers->IndexOfEdict(pClients[i]->GetEdict()) - 1; + + CFrameSnapshot *snap = framesnapshotmanager->TakeTickSnapshot(pSnapShot->m_nTickCount); + + for (int i = 0; i < MAX_EDICTS; ++i) { - if (!bCalledForNullIClientsThisTime && !g_bCallingForNullClients) + if (bEdictChanged[i]) { - g_bCurrentGameClientCallFwd = false; - g_bCallingForNullClients = true; - DETOUR_MEMBER_CALL(CGameServer_SendClientMessages)(true); - g_bCallingForNullClients = false; + gamehelpers->EdictOfIndex(i)->m_fStateFlags |= FL_EDICT_CHANGED; } - bCalledForNullIClientsThisTime = true; - continue; - } - if (!pPlayer->IsInGame() || bFake) //We should call SV_ComputeClientPacks, but shouldn't call forwards! - g_bCurrentGameClientCallFwd = false; - else - g_bCurrentGameClientCallFwd = true; - g_pCurrentGameClientPtr = (CGameClient *)((char *)pClient - 4); - g_iCurrentClientIndexInLoop = iClients - 1; - DETOUR_MEMBER_CALL(CGameServer_SendClientMessages)(true); - } - g_bCurrentGameClientCallFwd = false; - g_iCurrentClientIndexInLoop = -1; - g_bShouldChangeGameRulesState = false; -} - -DETOUR_DECL_MEMBER0(CGameClient_ShouldSendMessages, bool) -{ - if (!g_bSendSnapshots) - return DETOUR_MEMBER_CALL(CGameClient_ShouldSendMessages)(); - if (g_bCallingForNullClients) - { - IClient * pClient = (IClient *)((char *)this + 4); -#if SOURCE_ENGINE == SE_TF2 - //don't remove this code - int iUserID = pClient->GetUserID(); - IGamePlayer * pPlayer = playerhelpers->GetGamePlayer(pClient->GetPlayerSlot() + 1); - if (pPlayer->GetUserId() != iUserID) //if so, there something went wrong, check this now! -#endif - { - if (pClient->IsHLTV() -#if SOURCE_ENGINE == SE_TF2 - || pClient->IsReplay() -#endif - || (pClient->IsConnected() && !pClient->IsActive())) - return true; //Also we need to allow connect for inactivated clients, sourcetv & replay } - return false; - } - bool bOriginalResult = DETOUR_MEMBER_CALL(CGameClient_ShouldSendMessages)(); - if (!bOriginalResult) - return false; - if ((CGameClient *)this == g_pCurrentGameClientPtr) - return true; -#if defined PLATFORM_x32 - else - { - volatile int iToSet = g_iCurrentClientIndexInLoop - 1; -#if SOURCE_ENGINE == SE_TF2 -#ifdef _WIN32 - //some little trick to deceive msvc compiler - __asm _emit 0x5F - __asm _emit 0x5E - __asm push edx - __asm mov edx, iToSet - __asm _emit 0x3B - __asm _emit 0xF2 - __asm jge CompFailed - __asm _emit 0x8B - __asm _emit 0xF2 - __asm CompFailed: - __asm pop edx - __asm _emit 0x56 - __asm _emit 0x57 -#elif defined __linux__ - volatile int iTemp; - asm volatile("movl %%esi, %0" : "=g" (iTemp)); - if (iTemp < iToSet) - asm volatile( - "movl %0, %%esi\n\t" - "movl %%esi, %%edx\n\t" - "addl $84, %%esp\n\t" - "popl %%esi\n\t" - "pushl %%edx\n\t" - "subl $84, %%esp\n\t" - : : "g" (iToSet) : "%edx"); -#endif -#elif SOURCE_ENGINE == SE_CSGO -#ifdef _WIN32 - volatile int iEax, iEdi, iEsi; - //save registers - __asm mov iEdi, edi - __asm mov iEsi, esi - __asm mov iEax, eax - __asm mov eax, ebp - //load stack ptr - //we need to pop esi and edi to pop ebp register, we don't care about values in these, we also will use them as variables - __asm pop esi - __asm pop edi - __asm mov edi, iToSet - __asm mov esp, ebp - __asm pop ebp - //load needed info and compare - __asm mov esi, [ebp-0x7F8] //0x7F8 is an offset of loop variable - __asm cmp esi, edi - __asm jge CompFailed - //good, store our value - __asm mov [ebp-0x7F8], edi - __asm CompFailed: - //push old and restore original registers - __asm push ebp - __asm mov ebp, eax - __asm mov esp, ebp - __asm sub esp, 0x10 - __asm mov esi, iEsi - __asm mov edi, iEdi - __asm mov eax, iEax - __asm push edi - __asm push esi -#elif defined __linux__ - volatile int iTemp; - //we don't need to clubber edi register here, some low level shit - asm volatile("movl %%edi, %0" : "=g" (iTemp)); - if (iTemp < iToSet) - asm volatile("movl %0, %%edi" : : "g" (iToSet)); -#endif -#endif - } -#endif - return false; -} - -#if defined __linux__ -void __attribute__((__cdecl__)) SV_ComputeClientPacks_ActualCall(int iClientCount, CGameClient ** pClients, CFrameSnapshot * pSnapShot); -#else -void __cdecl SV_ComputeClientPacks_ActualCall(int iClientCount, CGameClient ** pClients, CFrameSnapshot * pSnapShot); -#endif -//the better idea rewrite it with __declspec(naked) for csgo or use __stdcall function as main callback instead of this -DETOUR_DECL_STATIC3(SV_ComputeClientPacks, void, int, iClientCount, CGameClient **, pClients, CFrameSnapshot *, pSnapShot) -{ -#if defined _WIN32 && SOURCE_ENGINE == SE_CSGO - //so, here it is __userpurge call, we need manually get our arguments - __asm mov iClientCount, ecx - __asm mov pClients, edx - __asm mov pSnapShot, ebx -#endif - g_bSVComputePacksDone = false; - if (!iClientCount || !g_bSendSnapshots || pClients[0] != g_pCurrentGameClientPtr) - return SV_ComputeClientPacks_ActualCall(iClientCount, pClients, pSnapShot); - IClient * pClient = (IClient *)((char *)pClients[0] + 4); - int iClient = pClient->GetPlayerSlot(); - if (g_iCurrentClientIndexInLoop != iClient) - return SV_ComputeClientPacks_ActualCall(iClientCount, pClients, pSnapShot); - //Also here we can change actual values for each client! But for what? - //Just mark all hooked edicts as changed to bypass check in SV_PackEntity! - for (int i = 0; i < g_vHookedEdicts.Count(); i++) - { - edict_t * pEdict = g_vHookedEdicts[i]; - if (pEdict && !pEdict->IsFree() && pEdict->GetUnknown() && !(pEdict->m_fStateFlags & FL_EDICT_CHANGED)) - pEdict->m_fStateFlags |= FL_EDICT_CHANGED; + SV_ComputeClientPacks_ActualCall(1, &pClients[i], snap); } - for (int i = 0; i < MAX_EDICTS; ++i) - { - if (!g_bEdictChanged[i]) - continue; - edict_t *pEdict = gamehelpers->EdictOfIndex(i); - if (pEdict && !(pEdict->m_fStateFlags & FL_EDICT_CHANGED)) - pEdict->m_fStateFlags |= FL_EDICT_CHANGED; - } - if (g_bShouldChangeGameRulesState && g_pGameRulesProxyEdict) - { - if (!(g_pGameRulesProxyEdict->m_fStateFlags & FL_EDICT_CHANGED)) - g_pGameRulesProxyEdict->m_fStateFlags |= FL_EDICT_CHANGED; - } - if (g_bCurrentGameClientCallFwd) - g_bSVComputePacksDone = true; - return SV_ComputeClientPacks_ActualCall(iClientCount, pClients, pSnapShot); + g_iCurrentClientIndexInLoop = -1; } #if defined _WIN32 && SOURCE_ENGINE == SE_CSGO @@ -479,7 +265,7 @@ __declspec(naked) void __cdecl SV_ComputeClientPacks_ActualCall(int iClientCount __asm mov edx, pClients //we don't care about values in edx & ecx __asm mov ecx, iClientCount __asm mov ebx, pSnapShot - __asm push ebx + __asm push ebx // @Forgetest: ???Why??? __asm call SV_ComputeClientPacks_Actual __asm add esp, 0x4 //restore our stack __asm retn @@ -657,13 +443,6 @@ void Hook_GameFrame(bool simulating) RETURN_META(MRES_IGNORED); } -int SendProxyManager::GetClientCount() const -{ - if (g_iCurrentClientIndexInLoop != -1) - RETURN_META_VALUE(MRES_SUPERCEDE, g_iCurrentClientIndexInLoop + 1); - RETURN_META_VALUE(MRES_IGNORED, 0/*META_RESULT_ORIG_RET(int)*/); -} - //main sm class implementation bool SendProxyManager::SDK_OnLoad(char *error, size_t maxlength, bool late) @@ -684,16 +463,34 @@ bool SendProxyManager::SDK_OnLoad(char *error, size_t maxlength, bool late) snprintf(error, maxlength, "Could not read config file sendproxy.txt: %s", conf_error); return false; } - + + if (!g_pGameConf->GetMemSig("CFrameSnapshotManager::TakeTickSnapshot", &CFrameSnapshotManager::s_pfnTakeTickSnapshot)) + { + if (conf_error[0]) + snprintf(error, maxlength, "Unable to find signature address ""\"CFrameSnapshotManager::TakeTickSnapshot\""" (%s)", conf_error); + return false; + } + + if (!g_pGameConf->GetOffset("CGameClient::edict", &CGameClient::s_iOffs_edict)) + { + if (conf_error[0]) + snprintf(error, maxlength, "Unable to find offset ""\"CGameClient::edict\""" (%s)", conf_error); + return false; + } + + if (!g_pGameConf->GetAddress("framesnapshotmanager", (void**)&framesnapshotmanager)) + { + if (conf_error[0]) + snprintf(error, maxlength, "Unable to find offset ""\"CGameClient::edict\""" (%s)", conf_error); + return false; + } + CDetourManager::Init(smutils->GetScriptingEngine(), g_pGameConf); bool bDetoursInited = false; - CREATE_DETOUR(CGameServer_SendClientMessages, "CGameServer::SendClientMessages", bDetoursInited); - CREATE_DETOUR(CGameClient_ShouldSendMessages, "CGameClient::ShouldSendMessages", bDetoursInited); CREATE_DETOUR(CFrameSnapshotManager_UsePreviouslySentPacket, "CFrameSnapshotManager::UsePreviouslySentPacket", bDetoursInited); CREATE_DETOUR(CFrameSnapshotManager_GetPreviouslySentPacket, "CFrameSnapshotManager::GetPreviouslySentPacket", bDetoursInited); CREATE_DETOUR(CFrameSnapshotManager_CreatePackedEntity, "CFrameSnapshotManager::CreatePackedEntity", bDetoursInited); - // CREATE_DETOUR(CFrameSnapshotManager_RemoveEntityReference, "CFrameSnapshotManager::RemoveEntityReference", bDetoursInited); CREATE_DETOUR_STATIC(SV_ComputeClientPacks, "SV_ComputeClientPacks", bDetoursInited); if (!bDetoursInited) @@ -707,6 +504,7 @@ bool SendProxyManager::SDK_OnLoad(char *error, size_t maxlength, bool late) sharesys->AddDependency(myself, "sdktools.ext", true, true); sharesys->AddDependency(myself, "sdkhooks.ext", true, true); + sharesys->AddDependency(myself, "bintools.ext", true, true); g_pMyInterface = new SendProxyManagerInterfaceImpl(); sharesys->AddInterface(myself, g_pMyInterface); @@ -722,11 +520,43 @@ void SendProxyManager::SDK_OnAllLoaded() sharesys->AddNatives(myself, g_MyNatives); SM_GET_LATE_IFACE(SDKTOOLS, g_pSDKTools); SM_GET_LATE_IFACE(SDKHOOKS, g_pSDKHooks); + SM_GET_LATE_IFACE(BINTOOLS, bintools); if (g_pSDKHooks) { g_pSDKHooks->AddEntityListener(this); } + + if (bintools) + { + SourceMod::PassInfo params[] = { + { PassType_Basic, PASSFLAG_BYVAL, sizeof(int), NULL, 0 }, + { PassType_Basic, PASSFLAG_BYVAL, sizeof(CFrameSnapshot*), NULL, 0 } + }; + + CFrameSnapshotManager::s_callTakeTickSnapshot = bintools->CreateCall(CFrameSnapshotManager::s_pfnTakeTickSnapshot, CallConv_ThisCall, ¶ms[1], ¶ms[0], 1); + if (CFrameSnapshotManager::s_callTakeTickSnapshot == NULL) { + smutils->LogError(myself, "Unable to create ICallWrapper for \"CFrameSnapshotManager::TakeTickSnapshot\"!"); + return; + } + } +} + +bool SendProxyManager::QueryInterfaceDrop(SMInterface* pInterface) +{ + return pInterface != bintools; +} + +void SendProxyManager::NotifyInterfaceDrop(SMInterface* pInterface) +{ + SDK_OnUnload(); +} + +bool SendProxyManager::QueryRunning(char* error, size_t maxlength) +{ + SM_CHECK_IFACE(BINTOOLS, bintools); + + return true; } void SendProxyManager::SDK_OnUnload() @@ -743,15 +573,10 @@ void SendProxyManager::SDK_OnUnload() SH_REMOVE_HOOK(IServerGameClients, ClientDisconnect, gameclients, SH_STATIC(Hook_ClientDisconnect), false); SH_REMOVE_HOOK(IServerGameDLL, GameFrame, gamedll, SH_STATIC(Hook_GameFrame), false); - if (!g_bFirstTimeCalled) - SH_REMOVE_HOOK(IServer, GetClientCount, g_pIServer, SH_MEMBER(this, &SendProxyManager::GetClientCount), false); - DESTROY_DETOUR(CGameServer_SendClientMessages); - DESTROY_DETOUR(CGameClient_ShouldSendMessages); DESTROY_DETOUR(CFrameSnapshotManager_UsePreviouslySentPacket); DESTROY_DETOUR(CFrameSnapshotManager_GetPreviouslySentPacket); DESTROY_DETOUR(CFrameSnapshotManager_CreatePackedEntity); - // DESTROY_DETOUR(CFrameSnapshotManager_RemoveEntityReference); DESTROY_DETOUR(SV_ComputeClientPacks); gameconfs->CloseGameConfigFile(g_pGameConf); @@ -806,8 +631,6 @@ bool SendProxyManager::SDK_OnMetamodLoad(ISmmAPI *ismm, char *error, size_t maxl GET_V_IFACE_ANY(GetServerFactory, gameclients, IServerGameClients, INTERFACEVERSION_SERVERGAMECLIENTS); GET_V_IFACE_ANY(GetEngineFactory, g_pCVar, ICvar, CVAR_INTERFACE_VERSION); - g_pGlobals = ismm->GetCGlobals(); - SH_ADD_HOOK(IServerGameDLL, GameFrame, gamedll, SH_STATIC(Hook_GameFrame), false); SH_ADD_HOOK(IServerGameClients, ClientDisconnect, gameclients, SH_STATIC(Hook_ClientDisconnect), false); @@ -888,8 +711,6 @@ bool SendProxyManager::AddHookToList(SendPropHook hook) } } g_Hooks.AddToTail(hook); - if (!bEdictHooked) - g_vHookedEdicts.AddToTail(hook.pEnt); return true; } @@ -1007,12 +828,6 @@ void SendProxyManager::UnhookProxy(int i) return; } } - for (int j = 0; j < g_vHookedEdicts.Count(); j++) - if (g_vHookedEdicts[j] == g_Hooks[i].pEnt) - { - g_vHookedEdicts.Remove(j); - break; - } CallListenersForHookID(i); g_Hooks[i].pVar->SetProxyFn(g_Hooks[i].pRealProxy); g_Hooks.Remove(i); @@ -1224,9 +1039,6 @@ void CallChangeGamerulesCallbacks(PropChangeHookGamerules * pInfo, void * pOldVa bool CallInt(SendPropHook &hook, int *ret, int iElement) { - if (!g_bSVComputePacksDone) - return false; - AUTO_LOCK_FM(g_WorkMutex); if (!hook.pVar->IsInsideArray()) @@ -1270,9 +1082,6 @@ bool CallInt(SendPropHook &hook, int *ret, int iElement) bool CallIntGamerules(SendPropHookGamerules &hook, int *ret, int iElement) { - if (!g_bSVComputePacksDone) - return false; - AUTO_LOCK_FM(g_WorkMutex); if (!hook.pVar->IsInsideArray()) @@ -1315,9 +1124,6 @@ bool CallIntGamerules(SendPropHookGamerules &hook, int *ret, int iElement) bool CallFloat(SendPropHook &hook, float *ret, int iElement) { - if (!g_bSVComputePacksDone) - return false; - AUTO_LOCK_FM(g_WorkMutex); if (!hook.pVar->IsInsideArray()) @@ -1361,9 +1167,6 @@ bool CallFloat(SendPropHook &hook, float *ret, int iElement) bool CallFloatGamerules(SendPropHookGamerules &hook, float *ret, int iElement) { - if (!g_bSVComputePacksDone) - return false; - AUTO_LOCK_FM(g_WorkMutex); if (!hook.pVar->IsInsideArray()) @@ -1406,9 +1209,6 @@ bool CallFloatGamerules(SendPropHookGamerules &hook, float *ret, int iElement) bool CallString(SendPropHook &hook, char **ret, int iElement) { - if (!g_bSVComputePacksDone) - return false; - AUTO_LOCK_FM(g_WorkMutex); if (!hook.pVar->IsInsideArray()) @@ -1453,9 +1253,6 @@ bool CallString(SendPropHook &hook, char **ret, int iElement) bool CallStringGamerules(SendPropHookGamerules &hook, char **ret, int iElement) { - if (!g_bSVComputePacksDone) - return false; - AUTO_LOCK_FM(g_WorkMutex); if (!hook.pVar->IsInsideArray()) @@ -1508,9 +1305,6 @@ bool CallStringGamerules(SendPropHookGamerules &hook, char **ret, int iElement) bool CallVector(SendPropHook &hook, Vector &vec, int iElement) { - if (!g_bSVComputePacksDone) - return false; - AUTO_LOCK_FM(g_WorkMutex); if (!hook.pVar->IsInsideArray()) @@ -1563,9 +1357,6 @@ bool CallVector(SendPropHook &hook, Vector &vec, int iElement) bool CallVectorGamerules(SendPropHookGamerules &hook, Vector &vec, int iElement) { - if (!g_bSVComputePacksDone) - return false; - AUTO_LOCK_FM(g_WorkMutex); if (!hook.pVar->IsInsideArray()) @@ -1713,9 +1504,6 @@ void GlobalProxy(const SendProp *pProp, const void *pStructBase, const void * pD void GlobalProxyGamerules(const SendProp *pProp, const void *pStructBase, const void * pData, DVariant *pOut, int iElement, int objectID) { - if (!g_bShouldChangeGameRulesState) - g_bShouldChangeGameRulesState = true; //If this called once, so, the props wants to be sent at this time, and we should do this for all clients! - bool bHandled = false; for (int i = 0; i < g_HooksGamerules.Count(); i++) { diff --git a/extension/extension.h b/extension/extension.h index b61f285..9786c53 100644 --- a/extension/extension.h +++ b/extension/extension.h @@ -181,6 +181,43 @@ class SendProxyManager : virtual bool SDK_OnLoad(char * error, size_t maxlength, bool late); virtual void SDK_OnUnload(); virtual void SDK_OnAllLoaded(); + + /** + * @brief Asks the extension whether it's safe to remove an external + * interface it's using. If it's not safe, return false, and the + * extension will be unloaded afterwards. + * + * NOTE: It is important to also hook NotifyInterfaceDrop() in order to clean + * up resources. + * + * @param pInterface Pointer to interface being dropped. This + * pointer may be opaque, and it should not + * be queried using SMInterface functions unless + * it can be verified to match an existing + * pointer of known type. + * @return True to continue, false to unload this + * extension afterwards. + */ + bool QueryInterfaceDrop(SMInterface* pInterface) override; + + /** + * @brief Notifies the extension that an external interface it uses is being removed. + * + * @param pInterface Pointer to interface being dropped. This + * pointer may be opaque, and it should not + * be queried using SMInterface functions unless + * it can be verified to match an existing + */ + void NotifyInterfaceDrop(SMInterface* pInterface) override; + + /** + * @brief Return false to tell Core that your extension should be considered unusable. + * + * @param error Error buffer. + * @param maxlength Size of error buffer. + * @return True on success, false otherwise. + */ + bool QueryRunning(char* error, size_t maxlength) override; virtual void OnCoreMapEnd(); virtual void OnCoreMapStart(edict_t *, int, int); @@ -200,7 +237,6 @@ class SendProxyManager : void UnhookChange(int i, CallBackInfo * pInfo); void UnhookChangeGamerules(int i, CallBackInfo * pInfo); - virtual int GetClientCount() const; public: // ISMEntityListener virtual void OnEntityDestroyed(CBaseEntity * pEntity); public: diff --git a/extension/wrappers.h b/extension/wrappers.h index 0e152f4..604d3ec 100644 --- a/extension/wrappers.h +++ b/extension/wrappers.h @@ -1,6 +1,7 @@ #ifndef _WRAPPERS_H_INC_ #define _WRAPPERS_H_INC_ +#include #include "tier0/threadtools.h" #include "tier1/utlvector.h" #include "tier1/utllinkedlist.h" @@ -41,32 +42,32 @@ class CFrameSnapshot CInterlockedInt m_nReferences; }; -class PackedEntity -{ -public: - ServerClass *m_pServerClass; // Valid on the server - ClientClass *m_pClientClass; // Valid on the client - - int m_nEntityIndex; // Entity index. - CInterlockedInt m_ReferenceCount; // reference count; -}; +class PackedEntity; #define INVALID_PACKED_ENTITY_HANDLE (0) typedef int PackedEntityHandle_t; -typedef struct -{ - PackedEntity *pEntity; // original packed entity - int counter; // increaseing counter to find LRU entries - int bits; // uncompressed data length in bits - char data[MAX_PACKEDENTITY_DATA]; // uncompressed data cache -} UnpackedDataCache_t; - +struct UnpackedDataCache_t; class CFrameSnapshotManager { public: virtual ~CFrameSnapshotManager( void ); + static void* s_pfnTakeTickSnapshot; + static ICallWrapper* s_callTakeTickSnapshot; + + CFrameSnapshot* TakeTickSnapshot(int tickcount) + { + struct { + CFrameSnapshotManager *pThis; + int tickcount; + } stack{ this, tickcount }; + + CFrameSnapshot *ret; + s_callTakeTickSnapshot->Execute(&stack, &ret); + return ret; + } + public: int pad[21]; @@ -87,4 +88,17 @@ class CFrameSnapshotManager CUtlVector m_iExplicitDeleteSlots; }; +class CGameClient +{ +public: + static int s_iOffs_edict; + + edict_t* GetEdict() + { + return *(edict_t**)(reinterpret_cast(this) + s_iOffs_edict); + } +}; + +extern CFrameSnapshotManager* framesnapshotmanager; + #endif \ No newline at end of file From b4bd3a7559c3f9a14ca293e82ee5dd911ebf822d Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Mon, 12 Aug 2024 03:08:32 +0800 Subject: [PATCH 02/73] Fix snapshot leaking memory --- extension/extension.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/extension/extension.cpp b/extension/extension.cpp index 2773ee1..86ab92f 100644 --- a/extension/extension.cpp +++ b/extension/extension.cpp @@ -253,6 +253,11 @@ DETOUR_DECL_STATIC3(SV_ComputeClientPacks, void, int, iClientCount, CGameClient } SV_ComputeClientPacks_ActualCall(1, &pClients[i], snap); + + // should be "pSnapshot->ReleaseReference()" but no need for an extra sig + // since it's almost certain that the snapshot won't get deleted now + Assert(snap->m_nReferences == 2); + snap->m_nReferences--; } g_iCurrentClientIndexInLoop = -1; From d022234443690c68fc9a1b1523bfe8c39958bc7f Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Mon, 12 Aug 2024 03:29:20 +0800 Subject: [PATCH 03/73] Enable optimization --- .github/workflows/build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 27a1c99..312c2c9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -120,7 +120,8 @@ jobs: --hl2sdk-root="${{ github.workspace }}/alliedmodders" \ --sm-path="${{ github.workspace }}/alliedmodders/sourcemod" \ --mms-path="${{ github.workspace }}/alliedmodders/mmsource-1.10" \ - --sdks=${{ join(fromJSON(env.SDKS)) }} + --sdks=${{ join(fromJSON(env.SDKS)) }} \ + --enable-optimize ambuild - name: Copy to addons directory From 5b42cbb63a942d7b87f1936f544efbcb580cba16 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Tue, 13 Aug 2024 00:01:44 +0800 Subject: [PATCH 04/73] Attempt to fix missing `ThreadInterlockedDecrement` --- addons/sourcemod/gamedata/sendproxy.txt | 5 +++++ extension/extension.cpp | 18 ++++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/addons/sourcemod/gamedata/sendproxy.txt b/addons/sourcemod/gamedata/sendproxy.txt index 0f7f244..ddcba47 100644 --- a/addons/sourcemod/gamedata/sendproxy.txt +++ b/addons/sourcemod/gamedata/sendproxy.txt @@ -83,6 +83,11 @@ "library" "engine" "linux" "@_ZN21CFrameSnapshotManager16TakeTickSnapshotEi" } + "CFrameSnapshot::ReleaseReference" + { + "library" "engine" + "linux" "@_ZN14CFrameSnapshot16ReleaseReferenceEv" + } } } "tf" diff --git a/extension/extension.cpp b/extension/extension.cpp index 86ab92f..d6d7a3a 100644 --- a/extension/extension.cpp +++ b/extension/extension.cpp @@ -254,10 +254,7 @@ DETOUR_DECL_STATIC3(SV_ComputeClientPacks, void, int, iClientCount, CGameClient SV_ComputeClientPacks_ActualCall(1, &pClients[i], snap); - // should be "pSnapshot->ReleaseReference()" but no need for an extra sig - // since it's almost certain that the snapshot won't get deleted now - Assert(snap->m_nReferences == 2); - snap->m_nReferences--; + snap->ReleaseReference(); } g_iCurrentClientIndexInLoop = -1; @@ -476,6 +473,13 @@ bool SendProxyManager::SDK_OnLoad(char *error, size_t maxlength, bool late) return false; } + if (!g_pGameConf->GetMemSig("CFrameSnapshot::ReleaseReference", &CFrameSnapshot::s_pfnReleaseReference)) + { + if (conf_error[0]) + snprintf(error, maxlength, "Unable to find signature address ""\"CFrameSnapshot::ReleaseReference\""" (%s)", conf_error); + return false; + } + if (!g_pGameConf->GetOffset("CGameClient::edict", &CGameClient::s_iOffs_edict)) { if (conf_error[0]) @@ -539,6 +543,12 @@ void SendProxyManager::SDK_OnAllLoaded() { PassType_Basic, PASSFLAG_BYVAL, sizeof(CFrameSnapshot*), NULL, 0 } }; + CFrameSnapshot::s_callReleaseReference = bintools->CreateCall(CFrameSnapshot::s_pfnReleaseReference, CallConv_ThisCall, NULL, params, 0); + if (CFrameSnapshot::s_callReleaseReference == NULL) { + smutils->LogError(myself, "Unable to create ICallWrapper for \"CFrameSnapshot::s_callReleaseReference\"!"); + return; + } + CFrameSnapshotManager::s_callTakeTickSnapshot = bintools->CreateCall(CFrameSnapshotManager::s_pfnTakeTickSnapshot, CallConv_ThisCall, ¶ms[1], ¶ms[0], 1); if (CFrameSnapshotManager::s_callTakeTickSnapshot == NULL) { smutils->LogError(myself, "Unable to create ICallWrapper for \"CFrameSnapshotManager::TakeTickSnapshot\"!"); From 37fd62d41d76fc7249a019257184542fdd0b1777 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Tue, 13 Aug 2024 01:00:21 +0800 Subject: [PATCH 05/73] Attempt to fix missing `ThreadInterlockedDecrement` --- extension/extension.cpp | 2 ++ extension/wrappers.h | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/extension/extension.cpp b/extension/extension.cpp index d6d7a3a..8d3bcf6 100644 --- a/extension/extension.cpp +++ b/extension/extension.cpp @@ -110,6 +110,8 @@ CFrameSnapshotManager* framesnapshotmanager = nullptr; void* CFrameSnapshotManager::s_pfnTakeTickSnapshot = nullptr; ICallWrapper* CFrameSnapshotManager::s_callTakeTickSnapshot = nullptr; int CGameClient::s_iOffs_edict = -1; +void* CFrameSnapshot::s_pfnReleaseReference = nullptr; +ICallWrapper *CFrameSnapshot::s_callReleaseReference = nullptr; //detours diff --git a/extension/wrappers.h b/extension/wrappers.h index 604d3ec..235fa62 100644 --- a/extension/wrappers.h +++ b/extension/wrappers.h @@ -16,6 +16,17 @@ class CEventInfo; class CFrameSnapshot { public: + static void *s_pfnReleaseReference; + static ICallWrapper *s_callReleaseReference; + void ReleaseReference() + { + struct { + CFrameSnapshot *pThis; + } stack{ this }; + + s_callReleaseReference->Execute(&stack, NULL); + } + // Index info CFrameSnapshotManager::m_FrameSnapshots. CInterlockedInt m_ListIndex; From a0ccd9ec2b898c8a905315df71e234add1994cd9 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Tue, 13 Aug 2024 22:34:27 +0800 Subject: [PATCH 06/73] Attempt to fix wrong player data --- extension/extension.cpp | 33 +++++++++++++++------------------ extension/extension.h | 5 +++++ 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/extension/extension.cpp b/extension/extension.cpp index 8d3bcf6..6f48da7 100644 --- a/extension/extension.cpp +++ b/extension/extension.cpp @@ -94,7 +94,7 @@ ConVar * sv_parallel_sendsnapshot = nullptr; edict_t * g_pGameRulesProxyEdict = nullptr; int g_iGameRulesProxyIndex = -1; -PackedEntityHandle_t g_PlayersPackedGameRules[SM_MAXPLAYERS] = {INVALID_PACKED_ENTITY_HANDLE}; +PackedEntityHandle_t g_PlayersPackedGameRules[g_iMaxPlayers][MAX_EDICTS] = {INVALID_PACKED_ENTITY_HANDLE}; void * g_pGameRules = nullptr; bool g_bSendSnapshots = false; @@ -137,24 +137,22 @@ ICallWrapper *CFrameSnapshot::s_callReleaseReference = nullptr; DETOUR_DECL_MEMBER3(CFrameSnapshotManager_UsePreviouslySentPacket, bool, CFrameSnapshot*, pSnapshot, int, entity, int, entSerialNumber) { - if (g_iCurrentClientIndexInLoop == -1 - || entity != g_iGameRulesProxyIndex) + if (g_iCurrentClientIndexInLoop == -1) { return DETOUR_MEMBER_CALL(CFrameSnapshotManager_UsePreviouslySentPacket)(pSnapshot, entity, entSerialNumber); } - if (g_PlayersPackedGameRules[g_iCurrentClientIndexInLoop] == INVALID_PACKED_ENTITY_HANDLE) + if (g_PlayersPackedEntities[g_iCurrentClientIndexInLoop][entity] == INVALID_PACKED_ENTITY_HANDLE) return false; CFrameSnapshotManager *framesnapshotmanager = (CFrameSnapshotManager *)this; - framesnapshotmanager->m_pLastPackedData[entity] = g_PlayersPackedGameRules[g_iCurrentClientIndexInLoop]; + framesnapshotmanager->m_pLastPackedData[entity] = g_PlayersPackedEntities[g_iCurrentClientIndexInLoop]; return DETOUR_MEMBER_CALL(CFrameSnapshotManager_UsePreviouslySentPacket)(pSnapshot, entity, entSerialNumber); } DETOUR_DECL_MEMBER2(CFrameSnapshotManager_GetPreviouslySentPacket, PackedEntity*, int, entity, int, entSerialNumber) { - if (g_iCurrentClientIndexInLoop == -1 - || entity != g_iGameRulesProxyIndex) + if (g_iCurrentClientIndexInLoop == -1) { return DETOUR_MEMBER_CALL(CFrameSnapshotManager_GetPreviouslySentPacket)(entity, entSerialNumber); } @@ -167,14 +165,13 @@ DETOUR_DECL_MEMBER2(CFrameSnapshotManager_GetPreviouslySentPacket, PackedEntity* gamehelpers->TextMsg(g_iCurrentClientIndexInLoop+1, 3, buffer); #endif - framesnapshotmanager->m_pLastPackedData[entity] = g_PlayersPackedGameRules[g_iCurrentClientIndexInLoop]; + framesnapshotmanager->m_pLastPackedData[entity] = g_PlayersPackedEntities[g_iCurrentClientIndexInLoop][entity]; return DETOUR_MEMBER_CALL(CFrameSnapshotManager_GetPreviouslySentPacket)(entity, entSerialNumber); } DETOUR_DECL_MEMBER2(CFrameSnapshotManager_CreatePackedEntity, PackedEntity*, CFrameSnapshot*, pSnapshot, int, entity) { - if (g_iCurrentClientIndexInLoop == -1 - || entity != g_iGameRulesProxyIndex) + if (g_iCurrentClientIndexInLoop == -1) { return DETOUR_MEMBER_CALL(CFrameSnapshotManager_CreatePackedEntity)(pSnapshot, entity); } @@ -182,10 +179,10 @@ DETOUR_DECL_MEMBER2(CFrameSnapshotManager_CreatePackedEntity, PackedEntity*, CFr CFrameSnapshotManager *framesnapshotmanager = (CFrameSnapshotManager *)this; PackedEntityHandle_t origHandle = framesnapshotmanager->m_pLastPackedData[entity]; - if (g_PlayersPackedGameRules[g_iCurrentClientIndexInLoop] != INVALID_PACKED_ENTITY_HANDLE) - framesnapshotmanager->m_pLastPackedData[entity] = g_PlayersPackedGameRules[g_iCurrentClientIndexInLoop]; + if (g_PlayersPackedEntities[g_iCurrentClientIndexInLoop][entity] != INVALID_PACKED_ENTITY_HANDLE) + framesnapshotmanager->m_pLastPackedData[entity] = g_PlayersPackedEntities[g_iCurrentClientIndexInLoop][entity]; PackedEntity *result = DETOUR_MEMBER_CALL(CFrameSnapshotManager_CreatePackedEntity)(pSnapshot, entity); - g_PlayersPackedGameRules[g_iCurrentClientIndexInLoop] = framesnapshotmanager->m_pLastPackedData[entity]; + g_PlayersPackedEntities[g_iCurrentClientIndexInLoop][entity] = framesnapshotmanager->m_pLastPackedData[entity]; #ifdef DEBUG char buffer[128]; @@ -320,7 +317,10 @@ void Hook_ClientDisconnect(edict_t * pEnt) } if (gamehelpers->IndexOfEdict(pEnt) != -1) - g_PlayersPackedGameRules[gamehelpers->IndexOfEdict(pEnt)] = INVALID_PACKED_ENTITY_HANDLE; + { + for (int i = 0; i < g_iMaxPlayers; ++i) + g_PlayersPackedEntities[i][gamehelpers->IndexOfEdict(pEnt)] = INVALID_PACKED_ENTITY_HANDLE; + } RETURN_META(MRES_IGNORED); } @@ -621,10 +621,7 @@ void SendProxyManager::OnCoreMapEnd() void SendProxyManager::OnCoreMapStart(edict_t * pEdictList, int edictCount, int clientMax) { - for (int i = 0; i < (sizeof(g_PlayersPackedGameRules) / sizeof(g_PlayersPackedGameRules[0])); ++i) - { - g_PlayersPackedGameRules[i] = INVALID_PACKED_ENTITY_HANDLE; - } + memset(g_PlayersPackedEntities, INVALID_PACKED_ENTITY_HANDLE, sizeof(g_PlayersPackedEntities)); CBaseEntity *pGameRulesProxyEnt = FindEntityByServerClassname(0, g_szGameRulesProxy); if (!pGameRulesProxyEnt) diff --git a/extension/extension.h b/extension/extension.h index 9786c53..a0d9c36 100644 --- a/extension/extension.h +++ b/extension/extension.h @@ -255,6 +255,11 @@ extern CUtlVector g_ChangeHooks; extern CUtlVector g_ChangeHooksGamerules; extern const char * g_szGameRulesProxy; constexpr int g_iEdictCount = 2048; //default value, we do not need to get it manually cuz it is constant +#if SOURCE_ENGINE == SE_LEFT4DEAD || SOURCE_ENGINE == SE_LEFT4DEAD2 +constexpr int g_iMaxPlayers = 32; +#else +constexpr int g_iMaxPlayers = SM_MAXPLAYERS; +#endif extern ISDKTools * g_pSDKTools; extern void * g_pGameRules; From 699f394a625131145a64eabdb1e99c6af2623737 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Tue, 13 Aug 2024 22:36:59 +0800 Subject: [PATCH 07/73] oops --- extension/extension.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/extension.cpp b/extension/extension.cpp index 6f48da7..bb8eaa9 100644 --- a/extension/extension.cpp +++ b/extension/extension.cpp @@ -94,7 +94,7 @@ ConVar * sv_parallel_sendsnapshot = nullptr; edict_t * g_pGameRulesProxyEdict = nullptr; int g_iGameRulesProxyIndex = -1; -PackedEntityHandle_t g_PlayersPackedGameRules[g_iMaxPlayers][MAX_EDICTS] = {INVALID_PACKED_ENTITY_HANDLE}; +PackedEntityHandle_t g_PlayersPackedEntities[g_iMaxPlayers][MAX_EDICTS] = {INVALID_PACKED_ENTITY_HANDLE}; void * g_pGameRules = nullptr; bool g_bSendSnapshots = false; From 4c59343f6818d8bc10901c2fa624360f2ca6abf2 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Tue, 13 Aug 2024 22:39:06 +0800 Subject: [PATCH 08/73] oops2 --- extension/extension.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/extension.cpp b/extension/extension.cpp index bb8eaa9..e95f9ef 100644 --- a/extension/extension.cpp +++ b/extension/extension.cpp @@ -146,7 +146,7 @@ DETOUR_DECL_MEMBER3(CFrameSnapshotManager_UsePreviouslySentPacket, bool, CFrameS return false; CFrameSnapshotManager *framesnapshotmanager = (CFrameSnapshotManager *)this; - framesnapshotmanager->m_pLastPackedData[entity] = g_PlayersPackedEntities[g_iCurrentClientIndexInLoop]; + framesnapshotmanager->m_pLastPackedData[entity] = g_PlayersPackedEntities[g_iCurrentClientIndexInLoop][entity]; return DETOUR_MEMBER_CALL(CFrameSnapshotManager_UsePreviouslySentPacket)(pSnapshot, entity, entSerialNumber); } From 63e96e6eab0bc51b489cacfb68d0f17da3920cb4 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Wed, 14 Aug 2024 19:09:29 +0800 Subject: [PATCH 09/73] Fix mistake clearing packed entities --- extension/extension.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/extension/extension.cpp b/extension/extension.cpp index e95f9ef..8d1d13f 100644 --- a/extension/extension.cpp +++ b/extension/extension.cpp @@ -64,11 +64,8 @@ DECL_DETOUR(SV_ComputeClientPacks); class CGameClient; class CFrameSnapshot; -class CGlobalEntityList; -CGameClient * g_pCurrentGameClientPtr = nullptr; int g_iCurrentClientIndexInLoop = -1; //used for optimization -bool g_bCallingForNullClients = false; SendProxyManager g_SendProxyManager; SendProxyManagerInterfaceImpl * g_pMyInterface = nullptr; @@ -98,8 +95,6 @@ PackedEntityHandle_t g_PlayersPackedEntities[g_iMaxPlayers][MAX_EDICTS] = {INVAL void * g_pGameRules = nullptr; bool g_bSendSnapshots = false; -bool g_bEdictChanged[MAX_EDICTS] = {false}; - static CBaseEntity * FindEntityByServerClassname(int, const char *); static void CallChangeCallbacks(PropChangeHook * pInfo, void * pOldValue, void * pNewValue); static void CallChangeGamerulesCallbacks(PropChangeHookGamerules * pInfo, void * pOldValue, void * pNewValue); @@ -300,6 +295,12 @@ void SendProxyManager::OnEntityDestroyed(CBaseEntity* pEnt) if (g_ChangeHooks[i].objectID == idx) g_ChangeHooks.Remove(i--); } + + if (idx >= 0 && idx < MAX_EDICTS) + { + for (int i = 0; i < g_iMaxPlayers; ++i) + g_PlayersPackedEntities[i][idx] = INVALID_PACKED_ENTITY_HANDLE; + } } void Hook_ClientDisconnect(edict_t * pEnt) @@ -318,8 +319,8 @@ void Hook_ClientDisconnect(edict_t * pEnt) if (gamehelpers->IndexOfEdict(pEnt) != -1) { - for (int i = 0; i < g_iMaxPlayers; ++i) - g_PlayersPackedEntities[i][gamehelpers->IndexOfEdict(pEnt)] = INVALID_PACKED_ENTITY_HANDLE; + for (int i = 0; i < MAX_EDICTS; ++i) + g_PlayersPackedEntities[gamehelpers->IndexOfEdict(pEnt)][i] = INVALID_PACKED_ENTITY_HANDLE; } RETURN_META(MRES_IGNORED); From 0405b097fb8209c232d3a6ce371a6ca2c9ec922e Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Wed, 14 Aug 2024 22:03:10 +0800 Subject: [PATCH 10/73] Fix mistake clearing packed entities 2 --- extension/extension.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/extension.cpp b/extension/extension.cpp index 8d1d13f..2eba4e4 100644 --- a/extension/extension.cpp +++ b/extension/extension.cpp @@ -320,7 +320,7 @@ void Hook_ClientDisconnect(edict_t * pEnt) if (gamehelpers->IndexOfEdict(pEnt) != -1) { for (int i = 0; i < MAX_EDICTS; ++i) - g_PlayersPackedEntities[gamehelpers->IndexOfEdict(pEnt)][i] = INVALID_PACKED_ENTITY_HANDLE; + g_PlayersPackedEntities[gamehelpers->IndexOfEdict(pEnt)-1][i] = INVALID_PACKED_ENTITY_HANDLE; } RETURN_META(MRES_IGNORED); From 73d4e74bc6999bfad4a81353bf80c2ca99128260 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Wed, 14 Aug 2024 22:10:14 +0800 Subject: [PATCH 11/73] Fix mistake clearing packed entities 3 --- extension/extension.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/extension/extension.cpp b/extension/extension.cpp index 2eba4e4..5b0a0ca 100644 --- a/extension/extension.cpp +++ b/extension/extension.cpp @@ -295,12 +295,6 @@ void SendProxyManager::OnEntityDestroyed(CBaseEntity* pEnt) if (g_ChangeHooks[i].objectID == idx) g_ChangeHooks.Remove(i--); } - - if (idx >= 0 && idx < MAX_EDICTS) - { - for (int i = 0; i < g_iMaxPlayers; ++i) - g_PlayersPackedEntities[i][idx] = INVALID_PACKED_ENTITY_HANDLE; - } } void Hook_ClientDisconnect(edict_t * pEnt) @@ -317,12 +311,6 @@ void Hook_ClientDisconnect(edict_t * pEnt) g_ChangeHooks.Remove(i--); } - if (gamehelpers->IndexOfEdict(pEnt) != -1) - { - for (int i = 0; i < MAX_EDICTS; ++i) - g_PlayersPackedEntities[gamehelpers->IndexOfEdict(pEnt)-1][i] = INVALID_PACKED_ENTITY_HANDLE; - } - RETURN_META(MRES_IGNORED); } From 37624caa1cfe563f9ce4cd85f6c60bdc308a76ab Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Wed, 14 Aug 2024 22:33:00 +0800 Subject: [PATCH 12/73] Update extension.cpp --- extension/extension.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extension/extension.cpp b/extension/extension.cpp index 5b0a0ca..8d9ce1f 100644 --- a/extension/extension.cpp +++ b/extension/extension.cpp @@ -238,11 +238,11 @@ DETOUR_DECL_STATIC3(SV_ComputeClientPacks, void, int, iClientCount, CGameClient CFrameSnapshot *snap = framesnapshotmanager->TakeTickSnapshot(pSnapShot->m_nTickCount); - for (int i = 0; i < MAX_EDICTS; ++i) + for (int j = 0; j < MAX_EDICTS; ++j) { - if (bEdictChanged[i]) + if (bEdictChanged[j]) { - gamehelpers->EdictOfIndex(i)->m_fStateFlags |= FL_EDICT_CHANGED; + gamehelpers->EdictOfIndex(j)->m_fStateFlags |= FL_EDICT_CHANGED; } } From 2e3ac9417b508c15a18610968d1380cc80fec435 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Wed, 14 Aug 2024 23:42:58 +0800 Subject: [PATCH 13/73] Fix mistake clearing packed entities 4 --- extension/extension.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/extension/extension.cpp b/extension/extension.cpp index 8d9ce1f..6ae7934 100644 --- a/extension/extension.cpp +++ b/extension/extension.cpp @@ -92,6 +92,7 @@ ConVar * sv_parallel_sendsnapshot = nullptr; edict_t * g_pGameRulesProxyEdict = nullptr; int g_iGameRulesProxyIndex = -1; PackedEntityHandle_t g_PlayersPackedEntities[g_iMaxPlayers][MAX_EDICTS] = {INVALID_PACKED_ENTITY_HANDLE}; +int g_PlayersPackedSerial[g_iMaxPlayers][MAX_EDICTS]; void * g_pGameRules = nullptr; bool g_bSendSnapshots = false; @@ -142,6 +143,7 @@ DETOUR_DECL_MEMBER3(CFrameSnapshotManager_UsePreviouslySentPacket, bool, CFrameS CFrameSnapshotManager *framesnapshotmanager = (CFrameSnapshotManager *)this; framesnapshotmanager->m_pLastPackedData[entity] = g_PlayersPackedEntities[g_iCurrentClientIndexInLoop][entity]; + framesnapshotmanager->m_pSerialNumber[entity] = g_PlayersPackedSerial[g_iCurrentClientIndexInLoop][entity]; return DETOUR_MEMBER_CALL(CFrameSnapshotManager_UsePreviouslySentPacket)(pSnapshot, entity, entSerialNumber); } @@ -161,6 +163,7 @@ DETOUR_DECL_MEMBER2(CFrameSnapshotManager_GetPreviouslySentPacket, PackedEntity* #endif framesnapshotmanager->m_pLastPackedData[entity] = g_PlayersPackedEntities[g_iCurrentClientIndexInLoop][entity]; + framesnapshotmanager->m_pSerialNumber[entity] = g_PlayersPackedSerial[g_iCurrentClientIndexInLoop][entity]; return DETOUR_MEMBER_CALL(CFrameSnapshotManager_GetPreviouslySentPacket)(entity, entSerialNumber); } @@ -175,9 +178,15 @@ DETOUR_DECL_MEMBER2(CFrameSnapshotManager_CreatePackedEntity, PackedEntity*, CFr PackedEntityHandle_t origHandle = framesnapshotmanager->m_pLastPackedData[entity]; if (g_PlayersPackedEntities[g_iCurrentClientIndexInLoop][entity] != INVALID_PACKED_ENTITY_HANDLE) + { framesnapshotmanager->m_pLastPackedData[entity] = g_PlayersPackedEntities[g_iCurrentClientIndexInLoop][entity]; + framesnapshotmanager->m_pSerialNumber[entity] = g_PlayersPackedSerial[g_iCurrentClientIndexInLoop][entity]; + } + PackedEntity *result = DETOUR_MEMBER_CALL(CFrameSnapshotManager_CreatePackedEntity)(pSnapshot, entity); + g_PlayersPackedEntities[g_iCurrentClientIndexInLoop][entity] = framesnapshotmanager->m_pLastPackedData[entity]; + g_PlayersPackedSerial[g_iCurrentClientIndexInLoop][entity] = framesnapshotmanager->m_pSerialNumber[entity]; #ifdef DEBUG char buffer[128]; From 8037ff1697295fbda857de8a7bcd9db7e6eaa662 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Thu, 15 Aug 2024 00:18:14 +0800 Subject: [PATCH 14/73] Fix mistake clearing packed entities 5 --- extension/extension.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/extension/extension.cpp b/extension/extension.cpp index 6ae7934..622d9c2 100644 --- a/extension/extension.cpp +++ b/extension/extension.cpp @@ -162,9 +162,18 @@ DETOUR_DECL_MEMBER2(CFrameSnapshotManager_GetPreviouslySentPacket, PackedEntity* gamehelpers->TextMsg(g_iCurrentClientIndexInLoop+1, 3, buffer); #endif + PackedEntityHandle_t origHandle = framesnapshotmanager->m_pLastPackedData[entity]; + int serialNumber = framesnapshotmanager->m_pSerialNumber[entity]; + framesnapshotmanager->m_pLastPackedData[entity] = g_PlayersPackedEntities[g_iCurrentClientIndexInLoop][entity]; framesnapshotmanager->m_pSerialNumber[entity] = g_PlayersPackedSerial[g_iCurrentClientIndexInLoop][entity]; - return DETOUR_MEMBER_CALL(CFrameSnapshotManager_GetPreviouslySentPacket)(entity, entSerialNumber); + + PackedEntity *result = DETOUR_MEMBER_CALL(CFrameSnapshotManager_GetPreviouslySentPacket)(entity, entSerialNumber); + + framesnapshotmanager->m_pLastPackedData[entity] = origHandle; + framesnapshotmanager->m_pSerialNumber[entity] = serialNumber; + + return result; } DETOUR_DECL_MEMBER2(CFrameSnapshotManager_CreatePackedEntity, PackedEntity*, CFrameSnapshot*, pSnapshot, int, entity) @@ -246,6 +255,7 @@ DETOUR_DECL_STATIC3(SV_ComputeClientPacks, void, int, iClientCount, CGameClient g_iCurrentClientIndexInLoop = gamehelpers->IndexOfEdict(pClients[i]->GetEdict()) - 1; CFrameSnapshot *snap = framesnapshotmanager->TakeTickSnapshot(pSnapShot->m_nTickCount); + snap->m_iExplicitDeleteSlots.CopyArray(pSnapShot->m_iExplicitDeleteSlots.Base(), pSnapShot->m_iExplicitDeleteSlots.Count()); for (int j = 0; j < MAX_EDICTS; ++j) { From 87e6661a305baa1d4d459a2402ea99c3e68575cf Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Fri, 16 Aug 2024 03:32:32 +0800 Subject: [PATCH 15/73] Revert all packed entity changes --- extension/extension.cpp | 207 ++++++++++++++++++++++++++++------------ extension/extension.h | 2 + 2 files changed, 147 insertions(+), 62 deletions(-) diff --git a/extension/extension.cpp b/extension/extension.cpp index 622d9c2..c777a6f 100644 --- a/extension/extension.cpp +++ b/extension/extension.cpp @@ -91,8 +91,7 @@ ConVar * sv_parallel_sendsnapshot = nullptr; edict_t * g_pGameRulesProxyEdict = nullptr; int g_iGameRulesProxyIndex = -1; -PackedEntityHandle_t g_PlayersPackedEntities[g_iMaxPlayers][MAX_EDICTS] = {INVALID_PACKED_ENTITY_HANDLE}; -int g_PlayersPackedSerial[g_iMaxPlayers][MAX_EDICTS]; +PackedEntityHandle_t g_PlayersPackedEntities[g_iMaxPlayers]; void * g_pGameRules = nullptr; bool g_bSendSnapshots = false; @@ -109,6 +108,8 @@ int CGameClient::s_iOffs_edict = -1; void* CFrameSnapshot::s_pfnReleaseReference = nullptr; ICallWrapper *CFrameSnapshot::s_callReleaseReference = nullptr; +CUtlVector g_vHookedEdicts; + //detours /*Call stack: @@ -126,6 +127,21 @@ ICallWrapper *CFrameSnapshot::s_callReleaseReference = nullptr; // #define _FORCE_DEBUG #ifdef _FORCE_DEBUG +#include +#include +#include + +static int g_iOffset = 0; +static void *g_pPatch = nullptr; + +static bool g_bPrinted[SM_MAXPLAYERS] = {false}; +static ConVar left4sendproxy_debug("left4sendproxy_debug", "0", 0); + +void ChangeNoneMsg(const char *format, int edictIdx, const char *classname, const char *propname) +{ + Msg(" !!! %s : Entity %d (class '%s') reported ENTITY_CHANGE_NONE but '%s' changed.\n", g_iCurrentClientIndexInLoop == -1 ? "Nobody" : playerhelpers->GetGamePlayer(g_iCurrentClientIndexInLoop + 1)->GetName(), edictIdx, classname, propname); +} + #define DEBUG #endif @@ -133,52 +149,45 @@ ICallWrapper *CFrameSnapshot::s_callReleaseReference = nullptr; DETOUR_DECL_MEMBER3(CFrameSnapshotManager_UsePreviouslySentPacket, bool, CFrameSnapshot*, pSnapshot, int, entity, int, entSerialNumber) { - if (g_iCurrentClientIndexInLoop == -1) + if (g_iCurrentClientIndexInLoop == -1 + || entity != g_iGameRulesProxyIndex) { return DETOUR_MEMBER_CALL(CFrameSnapshotManager_UsePreviouslySentPacket)(pSnapshot, entity, entSerialNumber); } - if (g_PlayersPackedEntities[g_iCurrentClientIndexInLoop][entity] == INVALID_PACKED_ENTITY_HANDLE) + if (g_PlayersPackedEntities[g_iCurrentClientIndexInLoop] == INVALID_PACKED_ENTITY_HANDLE) return false; CFrameSnapshotManager *framesnapshotmanager = (CFrameSnapshotManager *)this; - framesnapshotmanager->m_pLastPackedData[entity] = g_PlayersPackedEntities[g_iCurrentClientIndexInLoop][entity]; - framesnapshotmanager->m_pSerialNumber[entity] = g_PlayersPackedSerial[g_iCurrentClientIndexInLoop][entity]; + framesnapshotmanager->m_pLastPackedData[entity] = g_PlayersPackedEntities[g_iCurrentClientIndexInLoop]; return DETOUR_MEMBER_CALL(CFrameSnapshotManager_UsePreviouslySentPacket)(pSnapshot, entity, entSerialNumber); } DETOUR_DECL_MEMBER2(CFrameSnapshotManager_GetPreviouslySentPacket, PackedEntity*, int, entity, int, entSerialNumber) { - if (g_iCurrentClientIndexInLoop == -1) + if (g_iCurrentClientIndexInLoop == -1 + || entity != g_iGameRulesProxyIndex) { return DETOUR_MEMBER_CALL(CFrameSnapshotManager_GetPreviouslySentPacket)(entity, entSerialNumber); } CFrameSnapshotManager *framesnapshotmanager = (CFrameSnapshotManager *)this; -#ifdef DEBUG - char buffer[128]; - smutils->Format(buffer, sizeof(buffer), "GetPreviouslySentPacket (%d / %d)", framesnapshotmanager->m_pLastPackedData[entity], g_PlayersPackedGameRules[g_iCurrentClientIndexInLoop]); - gamehelpers->TextMsg(g_iCurrentClientIndexInLoop+1, 3, buffer); -#endif - - PackedEntityHandle_t origHandle = framesnapshotmanager->m_pLastPackedData[entity]; - int serialNumber = framesnapshotmanager->m_pSerialNumber[entity]; - - framesnapshotmanager->m_pLastPackedData[entity] = g_PlayersPackedEntities[g_iCurrentClientIndexInLoop][entity]; - framesnapshotmanager->m_pSerialNumber[entity] = g_PlayersPackedSerial[g_iCurrentClientIndexInLoop][entity]; - - PackedEntity *result = DETOUR_MEMBER_CALL(CFrameSnapshotManager_GetPreviouslySentPacket)(entity, entSerialNumber); - - framesnapshotmanager->m_pLastPackedData[entity] = origHandle; - framesnapshotmanager->m_pSerialNumber[entity] = serialNumber; - - return result; +// #ifdef DEBUG +// char buffer[128]; +// smutils->Format(buffer, sizeof(buffer), "GetPreviouslySentPacket %d (0x%X / 0x%X)\n", entity, framesnapshotmanager->m_pLastPackedData[entity], g_PlayersPackedEntities[g_iCurrentClientIndexInLoop]); +// if (left4sendproxy_debug.GetBool() || !g_bPrinted[g_iCurrentClientIndexInLoop]) +// Msg("%s %s", playerhelpers->GetGamePlayer(g_iCurrentClientIndexInLoop+1)->GetName(), buffer); +// #endif + + framesnapshotmanager->m_pLastPackedData[entity] = g_PlayersPackedEntities[g_iCurrentClientIndexInLoop]; + return DETOUR_MEMBER_CALL(CFrameSnapshotManager_GetPreviouslySentPacket)(entity, entSerialNumber); } DETOUR_DECL_MEMBER2(CFrameSnapshotManager_CreatePackedEntity, PackedEntity*, CFrameSnapshot*, pSnapshot, int, entity) { - if (g_iCurrentClientIndexInLoop == -1) + if (g_iCurrentClientIndexInLoop == -1 + || entity != g_iGameRulesProxyIndex) { return DETOUR_MEMBER_CALL(CFrameSnapshotManager_CreatePackedEntity)(pSnapshot, entity); } @@ -186,30 +195,28 @@ DETOUR_DECL_MEMBER2(CFrameSnapshotManager_CreatePackedEntity, PackedEntity*, CFr CFrameSnapshotManager *framesnapshotmanager = (CFrameSnapshotManager *)this; PackedEntityHandle_t origHandle = framesnapshotmanager->m_pLastPackedData[entity]; - if (g_PlayersPackedEntities[g_iCurrentClientIndexInLoop][entity] != INVALID_PACKED_ENTITY_HANDLE) + if (g_PlayersPackedEntities[g_iCurrentClientIndexInLoop] != INVALID_PACKED_ENTITY_HANDLE) { - framesnapshotmanager->m_pLastPackedData[entity] = g_PlayersPackedEntities[g_iCurrentClientIndexInLoop][entity]; - framesnapshotmanager->m_pSerialNumber[entity] = g_PlayersPackedSerial[g_iCurrentClientIndexInLoop][entity]; + framesnapshotmanager->m_pLastPackedData[entity] = g_PlayersPackedEntities[g_iCurrentClientIndexInLoop]; } PackedEntity *result = DETOUR_MEMBER_CALL(CFrameSnapshotManager_CreatePackedEntity)(pSnapshot, entity); - g_PlayersPackedEntities[g_iCurrentClientIndexInLoop][entity] = framesnapshotmanager->m_pLastPackedData[entity]; - g_PlayersPackedSerial[g_iCurrentClientIndexInLoop][entity] = framesnapshotmanager->m_pSerialNumber[entity]; + g_PlayersPackedEntities[g_iCurrentClientIndexInLoop] = framesnapshotmanager->m_pLastPackedData[entity]; #ifdef DEBUG char buffer[128]; - smutils->Format(buffer, sizeof(buffer), "CreatePackedEntity (%d / %d / %d)", origHandle, g_PlayersPackedGameRules[g_iCurrentClientIndexInLoop], framesnapshotmanager->m_pLastPackedData[entity]); - gamehelpers->TextMsg(g_iCurrentClientIndexInLoop+1, 3, buffer); + smutils->Format(buffer, sizeof(buffer), "CreatePackedEntity %s (%d) (0x%X / 0x%X / 0x%X)\n", gamehelpers->GetEntityClassname(gamehelpers->EdictOfIndex(entity)), entity, origHandle, g_PlayersPackedEntities[g_iCurrentClientIndexInLoop], framesnapshotmanager->m_pLastPackedData[entity]); + if (left4sendproxy_debug.GetBool() || !g_bPrinted[g_iCurrentClientIndexInLoop]) + { + Msg("%s %s", playerhelpers->GetGamePlayer(g_iCurrentClientIndexInLoop+1)->GetName(), buffer); + g_bPrinted[g_iCurrentClientIndexInLoop] = true; + } #endif return result; } -#ifdef _FORCE_DEBUG -#undef DEBUG -#endif - #if defined __linux__ void __attribute__((__cdecl__)) SV_ComputeClientPacks_ActualCall(int iClientCount, CGameClient ** pClients, CFrameSnapshot * pSnapShot); #else @@ -226,46 +233,40 @@ DETOUR_DECL_STATIC3(SV_ComputeClientPacks, void, int, iClientCount, CGameClient __asm mov pSnapShot, ebx // @Forgetest: ???Why??? #endif - bool bEdictChanged[MAX_EDICTS]; - for (int i = 0; i < MAX_EDICTS; ++i) - { - bEdictChanged[i] = false; - - edict_t *edict = gamehelpers->EdictOfIndex(i); - if (!edict || !edict->GetUnknown() || edict->IsFree()) - continue; + g_iCurrentClientIndexInLoop = gamehelpers->IndexOfEdict(pClients[0]->GetEdict()) - 1; - if (i > 0 && i <= playerhelpers->GetMaxClients()) - { - if (!playerhelpers->GetGamePlayer(i)->IsInGame()) - continue; - } + for (int i = 0; i < g_vHookedEdicts.Count(); ++i) + g_vHookedEdicts[i]->m_fStateFlags |= FL_EDICT_CHANGED; - if (!edict->HasStateChanged()) - continue; + if (g_HooksGamerules.Count() && g_pGameRulesProxyEdict) + g_pGameRulesProxyEdict->m_fStateFlags |= FL_EDICT_CHANGED; - bEdictChanged[i] = true; + bool bEdictChanged[MAX_EDICTS] = {false}; + for (int i = 0; i < MAX_EDICTS; ++i) + { + bEdictChanged[i] = gamehelpers->EdictOfIndex(i)->HasStateChanged(); } - g_iCurrentClientIndexInLoop = gamehelpers->IndexOfEdict(pClients[0]->GetEdict()) - 1; + g_bIsEndOfLoop = iClientCount == 1; SV_ComputeClientPacks_ActualCall(1, &pClients[0], pSnapShot); - for (int i = 1; i < iClientCount; ++i) + for (int iClient = 1; iClient < iClientCount; ++iClient) { - g_iCurrentClientIndexInLoop = gamehelpers->IndexOfEdict(pClients[i]->GetEdict()) - 1; + g_iCurrentClientIndexInLoop = gamehelpers->IndexOfEdict(pClients[iClient]->GetEdict()) - 1; CFrameSnapshot *snap = framesnapshotmanager->TakeTickSnapshot(pSnapShot->m_nTickCount); snap->m_iExplicitDeleteSlots.CopyArray(pSnapShot->m_iExplicitDeleteSlots.Base(), pSnapShot->m_iExplicitDeleteSlots.Count()); - for (int j = 0; j < MAX_EDICTS; ++j) + for (int i = 0; i < snap->m_nValidEntities; ++i) { - if (bEdictChanged[j]) + unsigned short index = snap->m_pValidEntities[i]; + if (bEdictChanged[index]) { - gamehelpers->EdictOfIndex(j)->m_fStateFlags |= FL_EDICT_CHANGED; + gamehelpers->EdictOfIndex(index)->m_fStateFlags |= FL_EDICT_CHANGED; } } - SV_ComputeClientPacks_ActualCall(1, &pClients[i], snap); + SV_ComputeClientPacks_ActualCall(1, &pClients[iClient], snap); snap->ReleaseReference(); } @@ -273,6 +274,10 @@ DETOUR_DECL_STATIC3(SV_ComputeClientPacks, void, int, iClientCount, CGameClient g_iCurrentClientIndexInLoop = -1; } +#ifdef _FORCE_DEBUG +#undef DEBUG +#endif + #if defined _WIN32 && SOURCE_ENGINE == SE_CSGO __declspec(naked) void __cdecl SV_ComputeClientPacks_ActualCall(int iClientCount, CGameClient ** pClients, CFrameSnapshot * pSnapShot) { @@ -314,6 +319,9 @@ void SendProxyManager::OnEntityDestroyed(CBaseEntity* pEnt) if (g_ChangeHooks[i].objectID == idx) g_ChangeHooks.Remove(i--); } + + if (idx >= 0 && idx < MAX_EDICTS) + g_vHookedEdicts.FindAndRemove(gamehelpers->EdictOfIndex(idx)); } void Hook_ClientDisconnect(edict_t * pEnt) @@ -504,6 +512,30 @@ bool SendProxyManager::SDK_OnLoad(char *error, size_t maxlength, bool late) return false; } +#ifdef DEBUG + if (!g_pGameConf->GetAddress("PackEntities_Normal", (void**)&g_pPatch)) + { + if (conf_error[0]) + snprintf(error, maxlength, "Unable to find offset ""\"PackEntities_Normal\""" (%s)", conf_error); + return false; + } + + if (*(uint8_t*)g_pPatch != 0xE8) + { + if (conf_error[0]) + snprintf(error, maxlength, "Invalid g_pPatch (%s)", conf_error); + return false; + } + + SourceHook::SetMemAccess(g_pPatch, 5, SH_MEM_READ | SH_MEM_WRITE | SH_MEM_EXEC); + + void (*pDest)(const char *, int, const char *, const char *) = &ChangeNoneMsg; + g_iOffset = *(int *)((uint8_t *)g_pPatch + 1); + int offs = (intptr_t)pDest - (intptr_t)((uint8_t *)g_pPatch + 5); + Msg("(intptr_t *)&ChangeNoneMsg - (intptr_t *)((char *)g_pPatch + 5) = %X | %X | (%X / %X)\n", offs, g_iOffset, g_pPatch, pDest); + *(intptr_t *)((uint8_t *)g_pPatch + 1) = offs; +#endif + CDetourManager::Init(smutils->GetScriptingEngine(), g_pGameConf); bool bDetoursInited = false; @@ -531,6 +563,8 @@ bool SendProxyManager::SDK_OnLoad(char *error, size_t maxlength, bool late) sharesys->RegisterLibrary(myself, "sendproxy13"); plsys->AddPluginsListener(&g_SendProxyManager); + ConVar_Register(0, this); + return true; } @@ -584,6 +618,12 @@ bool SendProxyManager::QueryRunning(char* error, size_t maxlength) return true; } +bool SendProxyManager::RegisterConCommandBase(ConCommandBase* pVar) +{ + // Notify metamod of ownership + return META_REGCVAR(pVar); +} + void SendProxyManager::SDK_OnUnload() { for (int i = 0; i < g_Hooks.Count(); i++) @@ -613,6 +653,13 @@ void SendProxyManager::SDK_OnUnload() g_pSDKHooks->RemoveEntityListener(this); } delete g_pMyInterface; + +#ifdef DEBUG + if (g_iOffset) + *(intptr_t *)((uint8_t *)g_pPatch + 1) = g_iOffset; +#endif + + ConVar_Unregister(); } void SendProxyManager::OnCoreMapEnd() @@ -625,11 +672,15 @@ void SendProxyManager::OnCoreMapEnd() g_pGameRulesProxyEdict = nullptr; g_iGameRulesProxyIndex = -1; + + for (int i = 0; i < g_iMaxPlayers; ++i) + g_PlayersPackedEntities[i] = INVALID_PACKED_ENTITY_HANDLE; } void SendProxyManager::OnCoreMapStart(edict_t * pEdictList, int edictCount, int clientMax) { - memset(g_PlayersPackedEntities, INVALID_PACKED_ENTITY_HANDLE, sizeof(g_PlayersPackedEntities)); + for (int i = 0; i < g_iMaxPlayers; ++i) + g_PlayersPackedEntities[i] = INVALID_PACKED_ENTITY_HANDLE; CBaseEntity *pGameRulesProxyEnt = FindEntityByServerClassname(0, g_szGameRulesProxy); if (!pGameRulesProxyEnt) @@ -660,7 +711,7 @@ bool SendProxyManager::SDK_OnMetamodLoad(ISmmAPI *ismm, char *error, size_t maxl sv_parallel_packentities->SetValue(0); //If we don't do that the sendproxy extension will crash the server (Post ref: https://forums.alliedmods.net/showpost.php?p=2540106&postcount=324 ) GET_CONVAR(sv_parallel_sendsnapshot); sv_parallel_sendsnapshot->SetValue(0); //If we don't do that, sendproxy will not work correctly and may crash server. This affects all versions of sendproxy manager! - + return true; } @@ -733,6 +784,8 @@ bool SendProxyManager::AddHookToList(SendPropHook hook) } } g_Hooks.AddToTail(hook); + if (!bEdictHooked) + g_vHookedEdicts.AddToTail(hook.pEnt); return true; } @@ -850,6 +903,12 @@ void SendProxyManager::UnhookProxy(int i) return; } } + for (int j = 0; j < g_vHookedEdicts.Count(); j++) + if (g_vHookedEdicts[j] == g_Hooks[i].pEnt) + { + g_vHookedEdicts.Remove(j); + break; + } CallListenersForHookID(i); g_Hooks[i].pVar->SetProxyFn(g_Hooks[i].pRealProxy); g_Hooks.Remove(i); @@ -1061,6 +1120,9 @@ void CallChangeGamerulesCallbacks(PropChangeHookGamerules * pInfo, void * pOldVa bool CallInt(SendPropHook &hook, int *ret, int iElement) { + if (g_iCurrentClientIndexInLoop == -1) + return false; + AUTO_LOCK_FM(g_WorkMutex); if (!hook.pVar->IsInsideArray()) @@ -1104,6 +1166,9 @@ bool CallInt(SendPropHook &hook, int *ret, int iElement) bool CallIntGamerules(SendPropHookGamerules &hook, int *ret, int iElement) { + if (g_iCurrentClientIndexInLoop == -1) + return false; + AUTO_LOCK_FM(g_WorkMutex); if (!hook.pVar->IsInsideArray()) @@ -1146,6 +1211,9 @@ bool CallIntGamerules(SendPropHookGamerules &hook, int *ret, int iElement) bool CallFloat(SendPropHook &hook, float *ret, int iElement) { + if (g_iCurrentClientIndexInLoop == -1) + return false; + AUTO_LOCK_FM(g_WorkMutex); if (!hook.pVar->IsInsideArray()) @@ -1189,6 +1257,9 @@ bool CallFloat(SendPropHook &hook, float *ret, int iElement) bool CallFloatGamerules(SendPropHookGamerules &hook, float *ret, int iElement) { + if (g_iCurrentClientIndexInLoop == -1) + return false; + AUTO_LOCK_FM(g_WorkMutex); if (!hook.pVar->IsInsideArray()) @@ -1231,6 +1302,9 @@ bool CallFloatGamerules(SendPropHookGamerules &hook, float *ret, int iElement) bool CallString(SendPropHook &hook, char **ret, int iElement) { + if (g_iCurrentClientIndexInLoop == -1) + return false; + AUTO_LOCK_FM(g_WorkMutex); if (!hook.pVar->IsInsideArray()) @@ -1275,6 +1349,9 @@ bool CallString(SendPropHook &hook, char **ret, int iElement) bool CallStringGamerules(SendPropHookGamerules &hook, char **ret, int iElement) { + if (g_iCurrentClientIndexInLoop == -1) + return false; + AUTO_LOCK_FM(g_WorkMutex); if (!hook.pVar->IsInsideArray()) @@ -1327,6 +1404,9 @@ bool CallStringGamerules(SendPropHookGamerules &hook, char **ret, int iElement) bool CallVector(SendPropHook &hook, Vector &vec, int iElement) { + if (g_iCurrentClientIndexInLoop == -1) + return false; + AUTO_LOCK_FM(g_WorkMutex); if (!hook.pVar->IsInsideArray()) @@ -1379,6 +1459,9 @@ bool CallVector(SendPropHook &hook, Vector &vec, int iElement) bool CallVectorGamerules(SendPropHookGamerules &hook, Vector &vec, int iElement) { + if (g_iCurrentClientIndexInLoop == -1) + return false; + AUTO_LOCK_FM(g_WorkMutex); if (!hook.pVar->IsInsideArray()) diff --git a/extension/extension.h b/extension/extension.h index a0d9c36..1d7849d 100644 --- a/extension/extension.h +++ b/extension/extension.h @@ -175,12 +175,14 @@ struct PropChangeHookGamerules class SendProxyManager : public SDKExtension, public IPluginsListener, + public IConCommandBaseAccessor, public ISMEntityListener { public: //sm virtual bool SDK_OnLoad(char * error, size_t maxlength, bool late); virtual void SDK_OnUnload(); virtual void SDK_OnAllLoaded(); + bool RegisterConCommandBase(ConCommandBase* pVar) override; /** * @brief Asks the extension whether it's safe to remove an external From 0950f0be3885803066f9c3cb0d52c981cd766c03 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Fri, 16 Aug 2024 03:32:58 +0800 Subject: [PATCH 16/73] Remove outdated binaries --- .../extensions/sendproxy.ext.2.csgo.dll | Bin 156672 -> 0 bytes .../extensions/sendproxy.ext.2.csgo.so | Bin 247676 -> 0 bytes .../extensions/sendproxy.ext.2.tf2.dll | Bin 154112 -> 0 bytes .../extensions/sendproxy.ext.2.tf2.so | Bin 128620 -> 0 bytes 4 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 addons/sourcemod/extensions/sendproxy.ext.2.csgo.dll delete mode 100644 addons/sourcemod/extensions/sendproxy.ext.2.csgo.so delete mode 100644 addons/sourcemod/extensions/sendproxy.ext.2.tf2.dll delete mode 100644 addons/sourcemod/extensions/sendproxy.ext.2.tf2.so diff --git a/addons/sourcemod/extensions/sendproxy.ext.2.csgo.dll b/addons/sourcemod/extensions/sendproxy.ext.2.csgo.dll deleted file mode 100644 index 7b98a3cea8cc1ccb6707ff53b02fe8149f81a4b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 156672 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�vyOgE1 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 diff --git a/addons/sourcemod/extensions/sendproxy.ext.2.csgo.so b/addons/sourcemod/extensions/sendproxy.ext.2.csgo.so deleted file mode 100644 index 742425be4cc9accde4ad9f92e51092e5befee8a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 247676 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 z93�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
bMb}qwhHS+Oz4v*KF^mK1E&nGo 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

0)}o-5;UAC$hwaJh#}Jhm(TgfV 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 h3UJzuA-W!C`;``!V7@2=^oiAOE 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 U=&*wr9b0_3-~T`UkC(vz0&J=-00000 diff --git a/addons/sourcemod/extensions/sendproxy.ext.2.tf2.so b/addons/sourcemod/extensions/sendproxy.ext.2.tf2.so deleted file mode 100644 index 2a154c998331a28836de101a0b8206b5d33f3584..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 128620 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 z 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 oGx|F*d4FW`m~*)p8^R#SuxO3 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 Date: Fri, 16 Aug 2024 03:35:57 +0800 Subject: [PATCH 17/73] Remove leftover line --- extension/extension.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/extension/extension.cpp b/extension/extension.cpp index c777a6f..2540626 100644 --- a/extension/extension.cpp +++ b/extension/extension.cpp @@ -247,7 +247,6 @@ DETOUR_DECL_STATIC3(SV_ComputeClientPacks, void, int, iClientCount, CGameClient bEdictChanged[i] = gamehelpers->EdictOfIndex(i)->HasStateChanged(); } - g_bIsEndOfLoop = iClientCount == 1; SV_ComputeClientPacks_ActualCall(1, &pClients[0], pSnapShot); for (int iClient = 1; iClient < iClientCount; ++iClient) From 94211874474792ca0f68399b151c60a69a396f6f Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Fri, 16 Aug 2024 03:48:36 +0800 Subject: [PATCH 18/73] Update AMBuildScript --- extension/AMBuildScript | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extension/AMBuildScript b/extension/AMBuildScript index 3b30c89..10a3a55 100644 --- a/extension/AMBuildScript +++ b/extension/AMBuildScript @@ -341,6 +341,8 @@ class ExtensionConfig(object): compiler.defines.remove('_vsnprintf=vsnprintf') if compiler.like('msvc'): + if compiler.version >= 1900: + compiler.linkflags += ['legacy_stdio_definitions.lib'] compiler.defines += ['COMPILER_MSVC', 'COMPILER_MSVC32'] else: compiler.defines += ['COMPILER_GCC'] From 7f084a4bfd74be7aa28403cfac3f3d66facef6d7 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Sat, 21 Sep 2024 17:38:24 +0800 Subject: [PATCH 19/73] Update windows gamedata & Cleanup --- addons/sourcemod/gamedata/sendproxy.txt | 91 ++++--------------------- extension/extension.cpp | 14 ++-- extension/wrappers.h | 11 +-- 3 files changed, 19 insertions(+), 97 deletions(-) diff --git a/addons/sourcemod/gamedata/sendproxy.txt b/addons/sourcemod/gamedata/sendproxy.txt index ddcba47..75b92bb 100644 --- a/addons/sourcemod/gamedata/sendproxy.txt +++ b/addons/sourcemod/gamedata/sendproxy.txt @@ -1,21 +1,7 @@ "Games" { - "#default" + "left4dead2" { - "#supported" - { - "game" "tf" - "game" "left4dead2" - } - - "Offsets" - { - "CGameClient::edict" - { - "linux" "120656" - } - } - "Addresses" { "framesnapshotmanager" @@ -24,6 +10,11 @@ { "signature" "framesnapshotmanager" } + "windows" + { + "signature" "CFrameSnapshot::ReleaseReference" + "read" "7" + } "read" "0" } } @@ -36,102 +27,46 @@ "linux" "@framesnapshotmanager" } - "CGameClient::ShouldSendMessages" - { - "library" "engine" - "linux" "@_ZN11CGameClient18ShouldSendMessagesEv" - "windows" "\x55\x8B\xEC\x51\x56\x8B\xF1\x80\xBE\xBC\x00\x00\x00\x00" - } - "CGameServer::SendClientMessages" - { - "library" "engine" - "linux" "@_ZN11CGameServer18SendClientMessagesEb" - "windows" "\x55\x8B\xEC\x81\xEC\xB4\x00\x00\x00\xA1\x2A\x2A\x2A\x2A\x53" - } "SV_ComputeClientPacks" { "library" "engine" "linux" "@_Z21SV_ComputeClientPacksiPP11CGameClientP14CFrameSnapshot" "windows" "\x55\x8B\xEC\x83\xEC\x44\xA1\x2A\x2A\x2A\x2A\x53" } + "CFrameSnapshotManager::UsePreviouslySentPacket" { "library" "engine" "linux" "@_ZN21CFrameSnapshotManager23UsePreviouslySentPacketEP14CFrameSnapshotii" "windows" "\x55\x8B\xEC\x56\x8B\x75\x0C\x57\x8B\xBC\xB1\x9C\x00\x00\x00" } + "CFrameSnapshotManager::GetPreviouslySentPacket" { "library" "engine" "linux" "@_ZN21CFrameSnapshotManager23GetPreviouslySentPacketEii" "windows" "\x55\x8B\xEC\x8B\x55\x08\x8B\x84\x91\x9C\x00\x00\x00" } + "CFrameSnapshotManager::CreatePackedEntity" { "library" "engine" "linux" "@_ZN21CFrameSnapshotManager18CreatePackedEntityEP14CFrameSnapshoti" "windows" "\x55\x8B\xEC\x83\xEC\x0C\x53\x8B\xD9\x56" } - "CFrameSnapshotManager::RemoveEntityReference" - { - "library" "engine" - "linux" "@_ZN21CFrameSnapshotManager21RemoveEntityReferenceEi" - "windows" "\x55\x8B\xEC\x51\x8B\x45\x08\x53\x8B\x18" - } + "CFrameSnapshotManager::TakeTickSnapshot" { "library" "engine" "linux" "@_ZN21CFrameSnapshotManager16TakeTickSnapshotEi" + "windows" "\x55\x8B\xEC\xB8\x10\x10\x00\x00\xE8\x2A\x2A\x2A\x2A\xA1\x2A\x2A\x2A\x2A\x33\xC5\x89\x45\xFC\x8B\x15" } + "CFrameSnapshot::ReleaseReference" { "library" "engine" "linux" "@_ZN14CFrameSnapshot16ReleaseReferenceEv" - } - } - } - "tf" - { - "Signatures" - { - "CGameClient::ShouldSendMessages" - { - "library" "engine" - "windows" "\x55\x8B\xEC\x51\x56\x8B\xF1\x80\xBE\x94\x00\x00\x00\x00" - } - "CGameServer::SendClientMessages" - { - "library" "engine" - "windows" "\x55\x8B\xEC\x81\xEC\x30\x04\x00\x00\x53\x56\x57\x33\xDB" - } - "SV_ComputeClientPacks" - { - "library" "engine" - "windows" "\x55\x8B\xEC\x83\xEC\x38\x8B\x0D\x2A\x2A\x2A\x2A\x53\x33\xDB" - } - } - } - "csgo" - { - "Signatures" - { - "CGameClient::ShouldSendMessages" - { - "library" "engine" - "windows" "\x55\x8B\xEC\x51\x57\x8B\xF9\x80\xBF\xEC\x01\x00\x00\x00" - "linux" "\x55\x89\xE5\x83\xEC\x28\x89\x5D\xF8\x8B\x5D\x08\x89\x75\xFC\x80\xBB\xD8\x01\x00\x00\x00" - } - "CGameServer::SendClientMessages" - { - "library" "engine" - "windows" "\x55\x8B\xEC\x83\xE4\xF8\x81\xEC\xFC\x07\x00\x00" - "linux" "\x55\x89\xE5\x57\x56\x53\x81\xEC\x1C\x08\x00\x00" - } - "SV_ComputeClientPacks" - { - "library" "engine" - "windows" "\x55\x8B\xEC\x83\xEC\x10\x53\x8B\xD9\x89\x55\xFC" - "linux" "\x55\x89\xE5\x57\x56\x53\x83\xEC\x3C\x8B\x0D\x2A\x2A\x2A\x2A\x8B\x75\x0C" + "windows" "\x55\x8B\xEC\x51\x56\x8B\x35\x2A\x2A\x2A\x2A\x57" } } } diff --git a/extension/extension.cpp b/extension/extension.cpp index 2540626..e4c02f4 100644 --- a/extension/extension.cpp +++ b/extension/extension.cpp @@ -233,7 +233,9 @@ DETOUR_DECL_STATIC3(SV_ComputeClientPacks, void, int, iClientCount, CGameClient __asm mov pSnapShot, ebx // @Forgetest: ???Why??? #endif - g_iCurrentClientIndexInLoop = gamehelpers->IndexOfEdict(pClients[0]->GetEdict()) - 1; + IClient *pClient = reinterpret_cast((char *)pClients[0] + 4); + + g_iCurrentClientIndexInLoop = pClient->GetPlayerSlot(); for (int i = 0; i < g_vHookedEdicts.Count(); ++i) g_vHookedEdicts[i]->m_fStateFlags |= FL_EDICT_CHANGED; @@ -251,7 +253,8 @@ DETOUR_DECL_STATIC3(SV_ComputeClientPacks, void, int, iClientCount, CGameClient for (int iClient = 1; iClient < iClientCount; ++iClient) { - g_iCurrentClientIndexInLoop = gamehelpers->IndexOfEdict(pClients[iClient]->GetEdict()) - 1; + pClient = reinterpret_cast((char *)pClients[iClient] + 4); + g_iCurrentClientIndexInLoop = pClient->GetPlayerSlot(); CFrameSnapshot *snap = framesnapshotmanager->TakeTickSnapshot(pSnapShot->m_nTickCount); snap->m_iExplicitDeleteSlots.CopyArray(pSnapShot->m_iExplicitDeleteSlots.Base(), pSnapShot->m_iExplicitDeleteSlots.Count()); @@ -497,13 +500,6 @@ bool SendProxyManager::SDK_OnLoad(char *error, size_t maxlength, bool late) return false; } - if (!g_pGameConf->GetOffset("CGameClient::edict", &CGameClient::s_iOffs_edict)) - { - if (conf_error[0]) - snprintf(error, maxlength, "Unable to find offset ""\"CGameClient::edict\""" (%s)", conf_error); - return false; - } - if (!g_pGameConf->GetAddress("framesnapshotmanager", (void**)&framesnapshotmanager)) { if (conf_error[0]) diff --git a/extension/wrappers.h b/extension/wrappers.h index 235fa62..0847983 100644 --- a/extension/wrappers.h +++ b/extension/wrappers.h @@ -99,16 +99,7 @@ class CFrameSnapshotManager CUtlVector m_iExplicitDeleteSlots; }; -class CGameClient -{ -public: - static int s_iOffs_edict; - - edict_t* GetEdict() - { - return *(edict_t**)(reinterpret_cast(this) + s_iOffs_edict); - } -}; +class CGameClient; extern CFrameSnapshotManager* framesnapshotmanager; From 33b926eca918b79ead7840f9d3f18b9066a89619 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Sat, 21 Sep 2024 17:44:53 +0800 Subject: [PATCH 20/73] Remove leftover `CGameClient::s_iOffs_edict` --- extension/extension.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/extension/extension.cpp b/extension/extension.cpp index e4c02f4..e66ec7a 100644 --- a/extension/extension.cpp +++ b/extension/extension.cpp @@ -104,7 +104,6 @@ const char * g_szGameRulesProxy; CFrameSnapshotManager* framesnapshotmanager = nullptr; void* CFrameSnapshotManager::s_pfnTakeTickSnapshot = nullptr; ICallWrapper* CFrameSnapshotManager::s_callTakeTickSnapshot = nullptr; -int CGameClient::s_iOffs_edict = -1; void* CFrameSnapshot::s_pfnReleaseReference = nullptr; ICallWrapper *CFrameSnapshot::s_callReleaseReference = nullptr; From d36606c6a53ea436adb2a3537b05f529d6d0a6ed Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Mon, 27 Jan 2025 19:09:38 +0800 Subject: [PATCH 21/73] Fix unable to `UnhookArrayPropGamerules` Thanks to @IA-NanaNana for reporting. --- extension/natives.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/natives.cpp b/extension/natives.cpp index b019b1a..9893a95 100644 --- a/extension/natives.cpp +++ b/extension/natives.cpp @@ -661,7 +661,7 @@ static cell_t Native_UnhookArrayPropGamerules(IPluginContext * pContext, const c int iElement = params[2]; PropType iPropType = static_cast(params[3]); IPluginFunction * pFunction = pContext->GetFunctionById(params[4]); - for (int i = 0; i < g_Hooks.Count(); i++) + for (int i = 0; i < g_HooksGamerules.Count(); i++) { if (g_HooksGamerules[i].Element == iElement && g_HooksGamerules[i].sCallbackInfo.iCallbackType == CallBackType::Callback_PluginFunction && g_HooksGamerules[i].PropType == iPropType && g_HooksGamerules[i].sCallbackInfo.pCallback == (void *)pFunction && !strcmp(g_HooksGamerules[i].pVar->GetName(), propName)) { From b152c1136f986305edfa36aae1189985e3f2b8a7 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Tue, 8 Apr 2025 04:26:45 +0800 Subject: [PATCH 22/73] Update .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 259148f..f13b941 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,5 @@ *.exe *.out *.app + +.vscode/*.* \ No newline at end of file From 2530f0ffaf3c36ed739b644c7d775e3e62810648 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Tue, 8 Apr 2025 17:41:28 +0800 Subject: [PATCH 23/73] Attempt to address entity origin issue --- extension/extension.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extension/extension.cpp b/extension/extension.cpp index e66ec7a..2f561fd 100644 --- a/extension/extension.cpp +++ b/extension/extension.cpp @@ -249,6 +249,7 @@ DETOUR_DECL_STATIC3(SV_ComputeClientPacks, void, int, iClientCount, CGameClient } SV_ComputeClientPacks_ActualCall(1, &pClients[0], pSnapShot); + gameclients->PostClientMessagesSent(); for (int iClient = 1; iClient < iClientCount; ++iClient) { @@ -268,6 +269,7 @@ DETOUR_DECL_STATIC3(SV_ComputeClientPacks, void, int, iClientCount, CGameClient } SV_ComputeClientPacks_ActualCall(1, &pClients[iClient], snap); + gameclients->PostClientMessagesSent(); snap->ReleaseReference(); } From 85abebe143397c4c3778ca23ed3ef9c8bf5fcaae Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Tue, 8 Apr 2025 17:41:28 +0800 Subject: [PATCH 24/73] Attempt to address entity origin issue --- extension/extension.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extension/extension.cpp b/extension/extension.cpp index e66ec7a..2f561fd 100644 --- a/extension/extension.cpp +++ b/extension/extension.cpp @@ -249,6 +249,7 @@ DETOUR_DECL_STATIC3(SV_ComputeClientPacks, void, int, iClientCount, CGameClient } SV_ComputeClientPacks_ActualCall(1, &pClients[0], pSnapShot); + gameclients->PostClientMessagesSent(); for (int iClient = 1; iClient < iClientCount; ++iClient) { @@ -268,6 +269,7 @@ DETOUR_DECL_STATIC3(SV_ComputeClientPacks, void, int, iClientCount, CGameClient } SV_ComputeClientPacks_ActualCall(1, &pClients[iClient], snap); + gameclients->PostClientMessagesSent(); snap->ReleaseReference(); } From b5bbddb5b3f8f67a8fa50266ad045652807ff2ad Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Mon, 18 Aug 2025 01:00:20 +0800 Subject: [PATCH 25/73] Reworks --- addons/sourcemod/gamedata/sendproxy.txt | 28 +- .../sourcemod/scripting/include/sendproxy.inc | 115 +- extension/AMBuildScript | 16 +- extension/AMBuilder | 4 +- extension/CDetour/detours.h | 4 +- extension/ISendProxy.h | 376 +--- extension/PackageScript | 13 +- extension/clientpacks_detours.cpp | 290 +++ extension/clientpacks_detours.h | 17 + extension/extension.cpp | 1688 ++--------------- extension/extension.h | 186 +- extension/interfaceimpl.cpp | 803 -------- extension/interfaceimpl.h | 123 -- extension/natives.cpp | 1055 ++--------- extension/sdk/typeinfo.h | 2 + extension/sendprop_hookmanager.cpp | 267 +++ extension/sendprop_hookmanager.h | 91 + extension/sendproxy_callback.cpp | 57 + extension/sendproxy_callback.h | 14 + extension/sendproxy_variant.h | 17 + extension/util.cpp | 32 + extension/util.h | 71 + extension/wrappers.h | 213 ++- 23 files changed, 1433 insertions(+), 4049 deletions(-) create mode 100644 extension/clientpacks_detours.cpp create mode 100644 extension/clientpacks_detours.h delete mode 100644 extension/interfaceimpl.cpp delete mode 100644 extension/interfaceimpl.h create mode 100644 extension/sdk/typeinfo.h create mode 100644 extension/sendprop_hookmanager.cpp create mode 100644 extension/sendprop_hookmanager.h create mode 100644 extension/sendproxy_callback.cpp create mode 100644 extension/sendproxy_callback.h create mode 100644 extension/sendproxy_variant.h create mode 100644 extension/util.cpp create mode 100644 extension/util.h diff --git a/addons/sourcemod/gamedata/sendproxy.txt b/addons/sourcemod/gamedata/sendproxy.txt index 75b92bb..9f17b2c 100644 --- a/addons/sourcemod/gamedata/sendproxy.txt +++ b/addons/sourcemod/gamedata/sendproxy.txt @@ -32,6 +32,15 @@ "library" "engine" "linux" "@_Z21SV_ComputeClientPacksiPP11CGameClientP14CFrameSnapshot" "windows" "\x55\x8B\xEC\x83\xEC\x44\xA1\x2A\x2A\x2A\x2A\x53" + // 55 8B EC 83 EC 44 A1 ? ? ? ? 53 + } + + "PackEntities_Normal" + { + "library" "engine" + "linux" "@_Z19PackEntities_NormaliPP11CGameClientP14CFrameSnapshot" + "windows" "\x55\x8B\xEC\xB8\x48\x60\x00\x00" + // 55 8B EC B8 48 60 00 00 } "CFrameSnapshotManager::UsePreviouslySentPacket" @@ -39,6 +48,7 @@ "library" "engine" "linux" "@_ZN21CFrameSnapshotManager23UsePreviouslySentPacketEP14CFrameSnapshotii" "windows" "\x55\x8B\xEC\x56\x8B\x75\x0C\x57\x8B\xBC\xB1\x9C\x00\x00\x00" + // 55 8B EC 56 8B 75 0C 57 8B BC B1 9C 00 00 00 } "CFrameSnapshotManager::GetPreviouslySentPacket" @@ -53,13 +63,24 @@ "library" "engine" "linux" "@_ZN21CFrameSnapshotManager18CreatePackedEntityEP14CFrameSnapshoti" "windows" "\x55\x8B\xEC\x83\xEC\x0C\x53\x8B\xD9\x56" + // 55 8B EC 83 EC 0C 53 8B D9 56 + } + + "CFrameSnapshotManager::CreateEmptySnapshot" + { + "library" "engine" + "linux" "@_ZN21CFrameSnapshotManager19CreateEmptySnapshotEii" + "windows" "\x55\x8B\xEC\x83\xEC\x0C\x53\x56\x57\x89\x4D\xF8" + // 55 8B EC 83 EC 0C 53 56 57 89 4D F8 + // Search string "Sending full update to Client %s (%s)", the called function with arg2 is 2048 } - "CFrameSnapshotManager::TakeTickSnapshot" + "CFrameSnapshotManager::RemoveEntityReference" { "library" "engine" - "linux" "@_ZN21CFrameSnapshotManager16TakeTickSnapshotEi" - "windows" "\x55\x8B\xEC\xB8\x10\x10\x00\x00\xE8\x2A\x2A\x2A\x2A\xA1\x2A\x2A\x2A\x2A\x33\xC5\x89\x45\xFC\x8B\x15" + "linux" "@_ZN21CFrameSnapshotManager21RemoveEntityReferenceEi" + "windows" "\x55\x8B\xEC\x51\x8B\x45\x08\x53\x8B\x18" + // 55 8B EC 51 8B 45 08 53 8B 18 } "CFrameSnapshot::ReleaseReference" @@ -67,6 +88,7 @@ "library" "engine" "linux" "@_ZN14CFrameSnapshot16ReleaseReferenceEv" "windows" "\x55\x8B\xEC\x51\x56\x8B\x35\x2A\x2A\x2A\x2A\x57" + // 55 8B EC 51 56 8B 35 ? ? ? ? 57 } } } diff --git a/addons/sourcemod/scripting/include/sendproxy.inc b/addons/sourcemod/scripting/include/sendproxy.inc index 862d613..71c51dd 100644 --- a/addons/sourcemod/scripting/include/sendproxy.inc +++ b/addons/sourcemod/scripting/include/sendproxy.inc @@ -1,112 +1,55 @@ -#if !defined _SENDPROXYMANAGER_INC_ +#if defined _SENDPROXYMANAGER_INC_ + #endinput +#endif #define _SENDPROXYMANAGER_INC_ -#define SENDPROXY_LIB "sendproxy13" +#define SENDPROXY_LIB "sendproxy2" -enum SendPropType { +enum SendPropType +{ Prop_Int, Prop_Float, Prop_String, - Prop_Vector = 4, + Prop_Vector, + Prop_EHandle, Prop_Max }; typeset SendProxyCallback { - function Action (const int iEntity, const char[] cPropName, int &iValue, const int iElement, const int iClient); //Prop_Int - function Action (const int iEntity, const char[] cPropName, float &flValue, const int iElement, const int iClient); //Prop_Float - function Action (const int iEntity, const char[] cPropName, char cModifiedValue[4096], const int iElement, const int iClient); //Prop_String - function Action (const int iEntity, const char[] cPropName, float vecValues[3], const int iElement, const int iClient); //Prop_Vector + function Action (int entity, const char[] prop, int &value, int element, int client); //Prop_Int, Prop_EHandle + function Action (int entity, const char[] prop, float &value, int element, int client); //Prop_Float + function Action (int entity, const char[] prop, char[] value, int maxlength, int element, int client); //Prop_String + function Action (int entity, const char[] prop, float value[3], int element, int client); //Prop_Vector }; typeset SendProxyCallbackGamerules { - function Action (const char[] cPropName, int &iValue, const int iElement, const int iClient); //Prop_Int - function Action (const char[] cPropName, float &flValue, const int iElement, const int iClient); //Prop_Float - function Action (const char[] cPropName, char cModifiedValue[4096], const int iElement, const int iClient); //Prop_String - function Action (const char[] cPropName, float vecValues[3], const int iElement, const int iClient); //Prop_Vector -}; - -typeset PropChangedCallback -{ - function void(const int iEntity, const char[] cPropName, const int iOldValue, const int iNewValue, const int iElement); //Prop_Int - function void(const int iEntity, const char[] cPropName, const float flOldValue, const float flNewValue, const int iElement); //Prop_Int - function void(const int iEntity, const char[] cPropName, const char[] cOldValue, const char[] cNewValue, const int iElement); //Prop_String - function void(const int iEntity, const char[] cPropName, const float vecOldValue[3], const float vecNewValue[3], const int iElement); //Prop_Vector -}; - -typeset GameRulesPropChangedCallback -{ - function void(const char[] cPropName, const int iOldValue, const int iNewValue, const int iElement); //Prop_Int - function void(const char[] cPropName, const float flOldValue, const float flNewValue, const int iElement); //Prop_Int - function void(const char[] cPropName, const char[] cOldValue, const char[] cNewValue, const int iElement); //Prop_String - function void(const char[] cPropName, const float vecOldValue[3], const float vecNewValue[3], const int iElement); //Prop_Vector + function Action (const char[] prop, int &value, int element, int client); //Prop_Int, Prop_EHandle + function Action (const char[] prop, float &value, int element, int client); //Prop_Float + function Action (const char[] prop, char[] value, int maxlength, int element, int client); //Prop_String + function Action (const char[] prop, float value[3], int element, int client); //Prop_Vector }; //Returns true upon success, false upon failure -native bool SendProxy_Hook(const int iEntity, const char[] cPropName, const SendPropType stType, const SendProxyCallback pCallback); -native bool SendProxy_HookGameRules(const char[] cPropName, const SendPropType stType, const SendProxyCallbackGamerules pCallback); -native bool SendProxy_HookArrayProp(const int iEntity, const char[] cPropName, const int iElement, const SendPropType stType, const SendProxyCallback pCallback); -native bool SendProxy_UnhookArrayProp(const int iEntity, const char[] cPropName, const int iElement, const SendPropType stType, const SendProxyCallback pCallback); -native bool SendProxy_Unhook(const int iEntity, const char[] cPropName, const SendProxyCallback pCallback); -native bool SendProxy_UnhookGameRules(const char[] cPropName, const SendProxyCallbackGamerules pCallback); -native bool SendProxy_IsHooked(const int iEntity, const char[] cPropName); -native bool SendProxy_IsHookedGameRules(const char[] cPropName); -native bool SendProxy_HookArrayPropGamerules(const char[] cPropName, const int iElement, const SendPropType stType, const SendProxyCallbackGamerules pCallback); -native bool SendProxy_UnhookArrayPropGamerules(const char[] cPropName, const int iElement, const SendPropType stType, const SendProxyCallbackGamerules pCallback); -native bool SendProxy_IsHookedArrayProp(const int iEntity, const char[] cPropName, const int iElement); -native bool SendProxy_IsHookedArrayPropGamerules(const char[] cPropName, const int iElement); +native bool SendProxy_HookEntity(int entity, const char[] prop, SendPropType type, SendProxyCallback callback, int element = 0); +native bool SendProxy_UnhookEntity(int entity, const char[] prop, SendProxyCallback callback, int element = 0); +native bool SendProxy_IsHookedEntity(int entity, const char[] prop, SendProxyCallback callback, int element = 0); +native bool SendProxy_HookGameRules(const char[] prop, SendPropType type, SendProxyCallback callback, int element = 0); +native bool SendProxy_UnhookGameRules(const char[] prop, SendProxyCallback callback, int element = 0); +native bool SendProxy_IsHookedGameRules(const char[] prop, SendProxyCallback callback, int element = 0); -//Deprecated functions -//here SendPropType is autodetected, this may be unsafe now -#pragma deprecated Use SendProxy_HookPropChangeSafe instead. -native bool SendProxy_HookPropChange(const int iEntity, const char[] cPropName, const PropChangedCallback pCallback); -#pragma deprecated Use SendProxy_HookPropChangeGameRulesSafe instead. -native bool SendProxy_HookPropChangeGameRules(const char[] cPropName, const GameRulesPropChangedCallback pCallback); - -native bool SendProxy_HookPropChangeSafe(const int iEntity, const char[] cPropName, const SendPropType stType, const PropChangedCallback pCallback); -native bool SendProxy_HookPropChangeGameRulesSafe(const char[] cPropName, const SendPropType stType, const GameRulesPropChangedCallback pCallback); -native bool SendProxy_HookPropChangeArray(const int iEntity, const char[] cPropName, const int iElement, const SendPropType stType, const PropChangedCallback pCallback); -native bool SendProxy_HookPropChangeArrayGameRules(const char[] cPropName, const int iElement, const SendPropType stType, const PropChangedCallback pCallback); -native bool SendProxy_IsPropChangeHooked(const int iEntity, const char[] cPropName); -native bool SendProxy_IsPropChangeHookedGameRules(const char[] cPropName); -native bool SendProxy_IsPropChangeArrayHooked(const int iEntity, const char[] cPropName, const int iElement); -native bool SendProxy_IsPropChangeArrayHookedGameRules(const char[] cPropName, const int iElement); -//these functions returns always true and because they are "void", so, we don't care about value they return because it always same -native void SendProxy_UnhookPropChangeArray(const int iEntity, const char[] cPropName, const int iElement, const PropChangedCallback pCallback); -native void SendProxy_UnhookPropChangeArrayGameRules(const char[] cPropName, const int iElement, const PropChangedCallback pCallback); - -#if !defined REQUIRE_EXTENSIONS public __ext_sendproxymanager_SetNTVOptional() { - MarkNativeAsOptional("SendProxy_Hook"); +#if !defined REQUIRE_EXTENSIONS + MarkNativeAsOptional("SendProxy_HookEntity"); + MarkNativeAsOptional("SendProxy_UnhookEntity"); + MarkNativeAsOptional("SendProxy_IsHookedEntity"); MarkNativeAsOptional("SendProxy_HookGameRules"); - MarkNativeAsOptional("SendProxy_HookArrayProp"); - MarkNativeAsOptional("SendProxy_UnhookArrayProp"); - MarkNativeAsOptional("SendProxy_Unhook"); MarkNativeAsOptional("SendProxy_UnhookGameRules"); - MarkNativeAsOptional("SendProxy_IsHooked"); MarkNativeAsOptional("SendProxy_IsHookedGameRules"); - MarkNativeAsOptional("SendProxy_HookPropChange"); - MarkNativeAsOptional("SendProxy_HookPropChangeGameRules"); - MarkNativeAsOptional("SendProxy_UnhookPropChange"); - MarkNativeAsOptional("SendProxy_UnhookPropChangeGameRules"); - MarkNativeAsOptional("SendProxy_HookArrayPropGamerules"); - MarkNativeAsOptional("SendProxy_UnhookArrayPropGamerules"); - MarkNativeAsOptional("SendProxy_IsHookedArrayProp"); - MarkNativeAsOptional("SendProxy_IsHookedArrayPropGamerules"); - MarkNativeAsOptional("SendProxy_HookPropChangeArray"); - MarkNativeAsOptional("SendProxy_UnhookPropChangeArray"); - MarkNativeAsOptional("SendProxy_HookPropChangeArrayGameRules"); - MarkNativeAsOptional("SendProxy_UnhookPropChangeArrayGameRules"); - MarkNativeAsOptional("SendProxy_IsPropChangeHooked"); - MarkNativeAsOptional("SendProxy_IsPropChangeHookedGameRules"); - MarkNativeAsOptional("SendProxy_IsPropChangeArrayHooked"); - MarkNativeAsOptional("SendProxy_IsPropChangeArrayHookedGameRules"); - MarkNativeAsOptional("SendProxy_HookPropChangeSafe"); - MarkNativeAsOptional("SendProxy_HookPropChangeGameRulesSafe"); -} #endif +} public Extension __ext_sendproxymanager = { @@ -122,6 +65,4 @@ public Extension __ext_sendproxymanager = #else required = 0, #endif -}; - -#endif \ No newline at end of file +}; \ No newline at end of file diff --git a/extension/AMBuildScript b/extension/AMBuildScript index 10a3a55..4648e0b 100644 --- a/extension/AMBuildScript +++ b/extension/AMBuildScript @@ -177,6 +177,7 @@ class ExtensionConfig(object): '-m32', '-fvisibility=hidden', '-Wno-implicit-int-float-conversion', + '-fno-omit-frame-pointer', ] cxx.cxxflags += [ '-fno-exceptions', @@ -190,7 +191,7 @@ class ExtensionConfig(object): if cxx.version >= 'clang-5': cxx.cxxflags += ['-Wno-register','-std=c++17'] else: - cxx.cxxflags += ['-std=c++14'] + cxx.cxxflags += ['-std=c++17'] have_gcc = cxx.vendor == 'gcc' have_clang = cxx.vendor == 'clang' @@ -236,9 +237,9 @@ class ExtensionConfig(object): '/EHsc', '/GR-', '/TP', + '/std:c++17', ] cxx.linkflags += [ - '/MACHINE:X86', 'kernel32.lib', 'user32.lib', 'gdi32.lib', @@ -265,12 +266,13 @@ class ExtensionConfig(object): cxx.cflags += ['/Oy-'] def configure_linux(self, cxx): - cxx.defines += ['_LINUX', 'POSIX'] - cxx.linkflags += ['-Wl,--exclude-libs,ALL', '-lm'] + cxx.defines += ['LINUX', '_LINUX', 'POSIX', '_FILE_OFFSET_BITS=64'] + cxx.linkflags += ['-lm'] if cxx.vendor == 'gcc': cxx.linkflags += ['-static-libgcc'] elif cxx.vendor == 'clang': cxx.linkflags += ['-lgcc_eh'] + cxx.linkflags += ['-static-libstdc++'] def configure_mac(self, cxx): cxx.defines += ['OSX', '_OSX', 'POSIX'] @@ -445,11 +447,7 @@ Extension.configure() # Add additional buildscripts here BuildScripts = [ 'AMBuilder', + 'PackageScript', ] -if builder.backend == 'amb2': - BuildScripts += [ - 'PackageScript', - ] - builder.RunBuildScripts(BuildScripts, { 'Extension': Extension}) diff --git a/extension/AMBuilder b/extension/AMBuilder index 59aa5cc..a9053eb 100644 --- a/extension/AMBuilder +++ b/extension/AMBuilder @@ -9,7 +9,9 @@ sourceFiles = [ 'CDetour/detours.cpp', 'asm/asm.c', 'natives.cpp', - 'interfaceimpl.cpp' + 'clientpacks_detours.cpp', + 'sendproxy_callback.cpp', + 'sendprop_hookmanager.cpp', ] ############### diff --git a/extension/CDetour/detours.h b/extension/CDetour/detours.h index 2131dcf..c7c371b 100644 --- a/extension/CDetour/detours.h +++ b/extension/CDetour/detours.h @@ -408,7 +408,7 @@ class CDetourManager if (name##_Detour != NULL) \ { \ name##_Detour->EnableDetour(); \ - var = true; \ + var &= true; \ } else { \ g_pSM->LogError(myself, "Failed to create " signname " detour, check error log.\n"); \ var = false; \ @@ -419,7 +419,7 @@ class CDetourManager if (name##_Detour != NULL) \ { \ name##_Detour->EnableDetour(); \ - var = true; \ + var &= true; \ } else { \ g_pSM->LogError(myself, "Failed to create " signname " detour, check error log.\n"); \ var = false; \ diff --git a/extension/ISendProxy.h b/extension/ISendProxy.h index ca83217..f49cb12 100644 --- a/extension/ISendProxy.h +++ b/extension/ISendProxy.h @@ -32,10 +32,6 @@ #ifndef _INCLUDE_ISENDPROXY_ #define _INCLUDE_ISENDPROXY_ -//WARNING! Interface not tested yet, but you can test it by yourself and report about any errors to github: https://github.com/TheByKotik/sendproxy - -#include -#include #include "dt_send.h" #include "server_class.h" @@ -53,378 +49,16 @@ enum class PropType : uint8_t Prop_Int = 0, Prop_Float, Prop_String, - Prop_Vector = 4, + Prop_Vector, + Prop_EHandle, Prop_Max }; -enum class CallBackType : uint8_t -{ - Callback_PluginFunction = 1, - Callback_CPPCallbackInterface //see ISendProxyCallbacks & ISendProxyChangeCallbacks -}; - -class ISendProxyUnhookListener -{ -public: - /* - * Calls when hook of the entity prop is removed - * - * @param pEntity Pointer to CBaseEntity object that was hooked - * @param pProp Pointer to SendProp that was hooked - * @param iType PropType of the prop - * @param iCallbackType Type of callback - * @param pCallback Pointer to callback function / class - * - * @noreturn - */ - virtual void OnEntityPropHookRemoved(const CBaseEntity * pEntity, const SendProp * pProp, const PropType iType, const CallBackType iCallbackType, const void * pCallback) = 0; - /* - * Calls when hook of the gamerules prop is removed - * - * @param pProp Pointer to SendProp that was hooked - * @param iType PropType of the prop - * @param iCallbackType Type of callback - * @param pCallback Pointer to callback function / class - * - * @noreturn - */ - virtual void OnGamerulesPropHookRemoved(const SendProp * pProp, const PropType iType, const CallBackType iCallbackType, const void * pCallback) = 0; -}; - -class ISendProxyCallbacks -{ -public: - /* - * Calls when proxy function of entity prop is called - * - * @param pEntity Pointer to CBaseEntity object that hooked - * @param pProp Pointer to SendProp that hooked - * @param pPlayer Pointer to CBasePlayer object of the client that should receive the changed value - * @param pValue Pointer to value of prop - * @param iType PropType of the prop - * @param iElement Element number - * - * @return true, to use changed value, false, to use original - */ - virtual bool OnEntityPropProxyFunctionCalls(const CBaseEntity * pEntity, const SendProp * pProp, const CBasePlayer * pPlayer, void * pValue, const PropType iType, const int iElement) = 0; - /* - * Calls when proxy function of gamerules prop is called - * - * @param pProp Pointer to SendProp that hooked - * @param pPlayer Pointer to CBasePlayer object of the client that should receive the changed value - * @param pValue Pointer to value of prop - * @param iType PropType of the prop - * @param iElement Element number - * - * @return true, to use changed value, false, to use original - */ - virtual bool OnGamerulesPropProxyFunctionCalls(const SendProp * pProp, const CBasePlayer * pPlayer, void * pValue, const PropType iType, const int iElement) = 0; -}; - -class ISendProxyChangeCallbacks +enum class CallbackType : uint8_t { -public: - /* - * Calls when prop of entity is changed - * - * @param pEntity Pointer to CBaseEntity object that hooked - * @param pProp Pointer to SendProp that hooked - * @param pNewValue Pointer to new value of prop - * @param pOldValue Pointer to old value of prop - * @param iType PropType of the prop - * @param iElement Element number - * - * @noreturn - */ - virtual void OnEntityPropChange(const CBaseEntity * pEntity, const SendProp * pProp, const void * pNewValue, const void * pOldValue, const PropType iType, const int iElement) = 0; - /* - * Calls when prop of gamerules is changed - * - * @param pProp Pointer to SendProp that hooked - * @param pNewValue Pointer to new value of prop - * @param pOldValue Pointer to old value of prop - * @param iType PropType of the prop - * @param iElement Element number - * - * @noreturn - */ - virtual void OnGamerulesPropChange(const SendProp * pProp, const void * pNewValue, const void * pOldValue, const PropType iType, const int iElement) = 0; + Callback_OnChanged = 0, // Callback only when the edict is marked changed + Callback_Constant, // Callback on every frame }; -class ISendProxyManager : public SMInterface -{ -public: //SMInterface - virtual const char * GetInterfaceName() = 0; - virtual unsigned int GetInterfaceVersion() = 0; - -public: //ISendProxyManager - /* - * Hooks SendProp of entity, this hook removes automatically when extension in unloaded. - * - * @param pMyself Pointer to IExtension interface of current extension, must be valid! - * @param pProp Pointer to SendProp / name of the prop that should be hooked - * @param pEntity Pointer to CBaseEntity object that should be hooked - * @param iType PropType of the prop - * @param iCallbackType Type of callback - * @param pCallback Pointer to callback function / class - * - * @return true, if prop hooked, false otherwise - */ - virtual bool HookProxy(IExtension * pMyself, SendProp * pProp, CBaseEntity * pEntity, PropType iType, CallBackType iCallbackType, void * pCallback) = 0; - virtual bool HookProxy(IExtension * pMyself, const char * pProp, CBaseEntity * pEntity, PropType iType, CallBackType iCallbackType, void * pCallback) = 0; - /* - * Hooks gamerules SendProp, this hook removes automatically when extension in unloaded. - * - * @param pMyself Pointer to IExtension interface of current extension, must be valid! - * @param pProp Pointer to SendProp / name of the prop that should be hooked - * @param iType PropType of the prop - * @param iCallbackType Type of callback - * @param pCallback Pointer to callback function / class - * - * @return true, if prop hooked, false otherwise - */ - virtual bool HookProxyGamerules(IExtension * pMyself, SendProp * pProp, PropType iType, CallBackType iCallbackType, void * pCallback) = 0; - virtual bool HookProxyGamerules(IExtension * pMyself, const char * pProp, PropType iType, CallBackType iCallbackType, void * pCallback) = 0; - /* - * Unhooks SendProp of entity - * - * @param pMyself Pointer to IExtension interface of current extension, must be valid! - * @param pProp Pointer to SendProp / name of the prop that should be unhooked - * @param pEntity Pointer to CBaseEntity object that should be unhooked - * @param iCallbackType Type of callback - * @param pCallback Pointer to callback function / class - * - * @return true, if prop unhooked, false otherwise - * P.S. This function will trigger unhook listeners - */ - virtual bool UnhookProxy(IExtension * pMyself, SendProp * pProp, CBaseEntity * pEntity, CallBackType iCallbackType, void * pCallback) = 0; - virtual bool UnhookProxy(IExtension * pMyself, const char * pProp, CBaseEntity * pEntity, CallBackType iCallbackType, void * pCallback) = 0; - /* - * Unhooks gamerules SendProp - * - * @param pMyself Pointer to IExtension interface of current extension, must be valid! - * @param pProp Pointer to SendProp / name of the prop that should be unhooked - * @param iCallbackType Type of callback - * @param pCallback Pointer to callback function / class - * - * @return true, if prop unhooked, false otherwise - * P.S. This function will trigger unhook listeners - */ - virtual bool UnhookProxyGamerules(IExtension * pMyself, SendProp * pProp, CallBackType iCallbackType, void * pCallback) = 0; - virtual bool UnhookProxyGamerules(IExtension * pMyself, const char * pProp, CallBackType iCallbackType, void * pCallback) = 0; - /* - * Adds unhook listener to entity hook, so, when hook will be removed listener callback is called. This listener removes automatically when extension in unloaded. - * - * @param pMyself Pointer to IExtension interface of current extension, must be valid! - * @param pProp Pointer to SendProp / name of the prop that should be listen - * @param pEntity Pointer to CBaseEntity object that should be listen - * @param iCallbackType Type of callback of entity hook - * @param pCallback Pointer to callback function / class of entity hook - * @param pListener Pointer to listener callback - * - * @return true, if listener installed, false otherwise - */ - virtual bool AddUnhookListener(IExtension * pMyself, SendProp * pProp, CBaseEntity * pEntity, CallBackType iCallbackType, void * pCallback, ISendProxyUnhookListener * pListener) = 0; - virtual bool AddUnhookListener(IExtension * pMyself, const char * pProp, CBaseEntity * pEntity, CallBackType iCallbackType, void * pCallback, ISendProxyUnhookListener * pListener) = 0; - /* - * Adds unhook listener to gamerules hook, so, when hook will removed listener callback is called. This listener removes automatically when extension in unloaded. - * - * @param pMyself Pointer to IExtension interface of current extension, must be valid! - * @param pProp Pointer to SendProp / name of the prop that should be listen - * @param iCallbackType Type of callback of gamerules hook - * @param pCallback Pointer to callback function / class of gamerules hook - * @param pListener Pointer to listener callback - * - * @return true, if listener installed, false otherwise - */ - virtual bool AddUnhookListenerGamerules(IExtension * pMyself, SendProp * pProp, CallBackType iCallbackType, void * pCallback, ISendProxyUnhookListener * pListener) = 0; - virtual bool AddUnhookListenerGamerules(IExtension * pMyself, const char * pProp, CallBackType iCallbackType, void * pCallback, ISendProxyUnhookListener * pListener) = 0; - /* - * Removes unhook listener from entity hook - * - * @param pMyself Pointer to IExtension interface of current extension, must be valid! - * @param pProp Pointer to SendProp / name of the prop that is listening - * @param pEntity Pointer to CBaseEntity object that is listening - * @param iCallbackType Type of callback of entity hook - * @param pCallback Pointer to callback function / class of entity hook - * @param pListener Pointer to listener callback - * - * @return true, if listener removed, false otherwise - */ - virtual bool RemoveUnhookListener(IExtension * pMyself, SendProp * pProp, CBaseEntity * pEntity, CallBackType iCallbackType, void * pCallback, ISendProxyUnhookListener * pListener) = 0; - virtual bool RemoveUnhookListener(IExtension * pMyself, const char * pProp, CBaseEntity * pEntity, CallBackType iCallbackType, void * pCallback, ISendProxyUnhookListener * pListener) = 0; - /* - * Removes unhook listener from gamerules hook - * - * @param pMyself Pointer to IExtension interface of current extension, must be valid! - * @param pProp Pointer to SendProp / name of the prop that is listening - * @param iCallbackType Type of callback of gamerules hook - * @param pCallback Pointer to callback function / class of gamerules hook - * @param pListener Pointer to listener callback - * - * @return true, if listener removed, false otherwise - */ - virtual bool RemoveUnhookListenerGamerules(IExtension * pMyself, SendProp * pProp, CallBackType iCallbackType, void * pCallback, ISendProxyUnhookListener * pListener) = 0; - virtual bool RemoveUnhookListenerGamerules(IExtension * pMyself, const char * pProp, CallBackType iCallbackType, void * pCallback, ISendProxyUnhookListener * pListener) = 0; - /* - * Hooks element of SendProp array of entity, this hook removes automatically when extension in unloaded. - * - * @param pMyself Pointer to IExtension interface of current extension, must be valid! - * @param pProp Pointer to SendProp / name of the prop that should be hooked - * @param pEntity Pointer to CBaseEntity object that should be hooked - * @param iType PropType of the prop - * @param iElement Element number - * @param iCallbackType Type of callback - * @param pCallback Pointer to callback function / class - * - * @return true, if prop hooked, false otherwise - */ - virtual bool HookProxyArray(IExtension * pMyself, SendProp * pProp, CBaseEntity * pEntity, PropType iType, int iElement, CallBackType iCallbackType, void * pCallback) = 0; - virtual bool HookProxyArray(IExtension * pMyself, const char * pProp, CBaseEntity * pEntity, PropType iType, int iElement, CallBackType iCallbackType, void * pCallback) = 0; - - /* - * Unhooks element of SendProp array of entity - * - * @param pMyself Pointer to IExtension interface of current extension, must be valid! - * @param pProp Pointer to SendProp / name of the prop that should be unhooked - * @param pEntity Pointer to CBaseEntity object that should be unhooked - * @param iElement Element number - * @param iCallbackType Type of callback - * @param pCallback Pointer to callback function / class - * - * @return true, if prop unhooked, false otherwise - * P.S. This function will trigger unhook listeners - */ - virtual bool UnhookProxyArray(IExtension * pMyself, SendProp * pProp, CBaseEntity * pEntity, int iElement, CallBackType iCallbackType, void * pCallback) = 0; - virtual bool UnhookProxyArray(IExtension * pMyself, const char * pProp, CBaseEntity * pEntity, int iElement, CallBackType iCallbackType, void * pCallback) = 0; - /* - * Hooks element of gamerules SendProp array, this hook removes automatically when extension in unloaded. - * - * @param pMyself Pointer to IExtension interface of current extension, must be valid! - * @param pProp Pointer to SendProp / name of the prop that should be hooked - * @param iType PropType of the prop - * @param iElement Element number - * @param iCallbackType Type of callback - * @param pCallback Pointer to callback function / class - * - * @return true, if prop hooked, false otherwise - */ - virtual bool HookProxyArrayGamerules(IExtension * pMyself, SendProp * pProp, PropType iType, int iElement, CallBackType iCallbackType, void * pCallback) = 0; - virtual bool HookProxyArrayGamerules(IExtension * pMyself, const char * pProp, PropType iType, int iElement, CallBackType iCallbackType, void * pCallback) = 0; - - /* - * Unhooks element of gamerules SendProp array - * - * @param pMyself Pointer to IExtension interface of current extension, must be valid! - * @param pProp Pointer to SendProp / name of the prop that should be unhooked - * @param iElement Element number - * @param iCallbackType Type of callback - * @param pCallback Pointer to callback function / class - * - * @return true, if prop unhooked, false otherwise - * P.S. This function will trigger unhook listeners - */ - virtual bool UnhookProxyArrayGamerules(IExtension * pMyself, SendProp * pProp, int iElement, CallBackType iCallbackType, void * pCallback) = 0; - virtual bool UnhookProxyArrayGamerules(IExtension * pMyself, const char * pProp, int iElement, CallBackType iCallbackType, void * pCallback) = 0; - - /* - * Adds unhook listener to entity array hook, so, when hook will be removed listener callback is called. This listener removes automatically when extension in unloaded. - * - * @param pMyself Pointer to IExtension interface of current extension, must be valid! - * @param pProp Pointer to SendProp / name of the prop that should be listen - * @param pEntity Pointer to CBaseEntity object that should be listen - * @param iElement Element number - * @param iCallbackType Type of callback of entity hook - * @param pCallback Pointer to callback function / class of entity hook - * @param pListener Pointer to listener callback - * - * @return true, if listener installed, false otherwise - */ - virtual bool AddUnhookListenerArray(IExtension * pMyself, SendProp * pProp, CBaseEntity * pEntity, int iElement, CallBackType iCallbackType, void * pCallback, ISendProxyUnhookListener * pListener) = 0; - virtual bool AddUnhookListenerArray(IExtension * pMyself, const char * pProp, CBaseEntity * pEntity, int iElement, CallBackType iCallbackType, void * pCallback, ISendProxyUnhookListener * pListener) = 0; - /* - * Adds unhook listener to gamerules array hook, so, when hook will removed listener callback is called. This listener removes automatically when extension in unloaded. - * - * @param pMyself Pointer to IExtension interface of current extension, must be valid! - * @param pProp Pointer to SendProp / name of the prop that should be listen - * @param iElement Element number - * @param iCallbackType Type of callback of gamerules hook - * @param pCallback Pointer to callback function / class of gamerules hook - * @param pListener Pointer to listener callback - * - * @return true, if listener installed, false otherwise - */ - virtual bool AddUnhookListenerArrayGamerules(IExtension * pMyself, SendProp * pProp, int iElement, CallBackType iCallbackType, void * pCallback, ISendProxyUnhookListener * pListener) = 0; - virtual bool AddUnhookListenerArrayGamerules(IExtension * pMyself, const char * pProp, int iElement, CallBackType iCallbackType, void * pCallback, ISendProxyUnhookListener * pListener) = 0; - /* - * Removes unhook listener from entity array hook - * - * @param pMyself Pointer to IExtension interface of current extension, must be valid! - * @param pProp Pointer to SendProp / name of the prop that is listening - * @param pEntity Pointer to CBaseEntity object that is listening - * @param iElement Element number - * @param iCallbackType Type of callback of entity hook - * @param pCallback Pointer to callback function / class of entity hook - * @param pListener Pointer to listener callback - * - * @return true, if listener removed, false otherwise - */ - virtual bool RemoveUnhookListenerArray(IExtension * pMyself, SendProp * pProp, CBaseEntity * pEntity, int iElement, CallBackType iCallbackType, void * pCallback, ISendProxyUnhookListener * pListener) = 0; - virtual bool RemoveUnhookListenerArray(IExtension * pMyself, const char * pProp, CBaseEntity * pEntity, int iElement, CallBackType iCallbackType, void * pCallback, ISendProxyUnhookListener * pListener) = 0; - /* - * Removes unhook listener from gamerules array hook - * - * @param pMyself Pointer to IExtension interface of current extension, must be valid! - * @param pProp Pointer to SendProp / name of the prop that is listening - * @param iElement Element number - * @param iCallbackType Type of callback of gamerules hook - * @param pCallback Pointer to callback function / class of gamerules hook - * @param pListener Pointer to listener callback - * - * @return true, if listener removed, false otherwise - */ - virtual bool RemoveUnhookListenerArrayGamerules(IExtension * pMyself, SendProp * pProp, int iElement, CallBackType iCallbackType, void * pCallback, ISendProxyUnhookListener * pListener) = 0; - virtual bool RemoveUnhookListenerArrayGamerules(IExtension * pMyself, const char * pProp, int iElement, CallBackType iCallbackType, void * pCallback, ISendProxyUnhookListener * pListener) = 0; - /* - * Checks if proxy is hooked - * - * @param pProp Pointer to SendProp / name of the prop that should be checked - * @param pEntity Pointer to CBaseEntity object that should be checked - * - * @return true, if is hooked, false otherwise - */ - virtual bool IsProxyHooked(IExtension * pMyself, SendProp * pProp, CBaseEntity * pEntity) const = 0; - virtual bool IsProxyHooked(IExtension * pMyself, const char * pProp, CBaseEntity * pEntity) const = 0; - /* - * Checks if gamerules proxy is hooked - * - * @param pProp Pointer to SendProp / name of the prop that should be checked - * - * @return true, if is hooked, false otherwise - */ - virtual bool IsProxyHookedGamerules(IExtension * pMyself, SendProp * pProp) const = 0; - virtual bool IsProxyHookedGamerules(IExtension * pMyself, const char * pProp) const = 0; - /* - * Checks if proxy array is hooked - * - * @param pProp Pointer to SendProp / name of the prop that should be checked - * @param pEntity Pointer to CBaseEntity object that should be checked - * @param iElement Element number - * - * @return true, if is hooked, false otherwise - */ - virtual bool IsProxyHookedArray(IExtension * pMyself, SendProp * pProp, CBaseEntity * pEntity, int iElement) const = 0; - virtual bool IsProxyHookedArray(IExtension * pMyself, const char * pProp, CBaseEntity * pEntity, int iElement) const = 0; - /* - * Checks if gamerules proxy is hooked - * - * @param pProp Pointer to SendProp / name of the prop that should be checked - * @param iElement Element number - * - * @return true, if is hooked, false otherwise - */ - virtual bool IsProxyHookedArrayGamerules(IExtension * pMyself, SendProp * pProp, int iElement) const = 0; - virtual bool IsProxyHookedArrayGamerules(IExtension * pMyself, const char * pProp, int iElement) const = 0; -}; #endif \ No newline at end of file diff --git a/extension/PackageScript b/extension/PackageScript index 6fdf9d5..3b05fb0 100644 --- a/extension/PackageScript +++ b/extension/PackageScript @@ -8,6 +8,7 @@ builder.SetBuildFolder('package') # Add any folders you need to this list folder_list = [ 'addons/sourcemod/extensions', + 'addons/sourcemod/gamedata', 'addons/sourcemod/scripting/include', ] @@ -27,10 +28,14 @@ def CopyFiles(src, dest, files): builder.AddCopy(source_path, dest_entry) # GameData files -#CopyFiles('gamedata', 'addons/sourcemod/gamedata', -# [ '../../addons/sourcemod/gamedata/sendproxy.txt', -# ] -#) +CopyFiles('gamedata', 'addons/sourcemod/gamedata', + [ '../../addons/sourcemod/gamedata/sendproxy.txt', + ] +) +CopyFiles('include', 'addons/sourcemod/scripting/include', + [ '../../addons/sourcemod/scripting/include/sendproxy.inc', + ] +) # Config Files #CopyFiles('configs', 'addons/sourcemod/configs', diff --git a/extension/clientpacks_detours.cpp b/extension/clientpacks_detours.cpp new file mode 100644 index 0000000..18befee --- /dev/null +++ b/extension/clientpacks_detours.cpp @@ -0,0 +1,290 @@ +#include "clientpacks_detours.h" +#include "sendprop_hookmanager.h" +#include "CDetour/detours.h" +#include "wrappers.h" +#include "iclient.h" +#include "util.h" +#include +#include + +DECL_DETOUR(CFrameSnapshotManager_UsePreviouslySentPacket); +DECL_DETOUR(CFrameSnapshotManager_GetPreviouslySentPacket); +DECL_DETOUR(CFrameSnapshotManager_CreatePackedEntity); +DECL_DETOUR(PackEntities_Normal); +DECL_DETOUR(SV_ComputeClientPacks); + +volatile int g_iCurrentClientIndexInLoop = -1; //used for optimization + +using PackedEntityArray = std::array; +std::unordered_map g_EntityPackMap; + +ConVar ext_sendproxy_frame_callback("ext_sendproxy_frame_callback", "0", FCVAR_NONE, "Invoke hooked proxy every frame."); + +/*Call stack: + ... + 1. CGameServer::SendClientMessages //function we hooking to send props individually for each client + 2. SV_ComputeClientPacks //function we hooking to set edicts state and to know, need we call callbacks or not, but not in csgo + 3. PackEntities_Normal //if we in multiplayer + 4. SV_PackEntity //also we can hook this instead hooking ProxyFn, but there no reason to do that + 5. SendTable_Encode + 6. SendTable_EncodeProp //here the ProxyFn will be called + 7. ProxyFn //here our callbacks is called +*/ + +DETOUR_DECL_MEMBER3(CFrameSnapshotManager_UsePreviouslySentPacket, bool, CFrameSnapshot*, pSnapshot, int, entity, int, entSerialNumber) +{ + if (g_iCurrentClientIndexInLoop == -1) + return DETOUR_MEMBER_CALL(CFrameSnapshotManager_UsePreviouslySentPacket)(pSnapshot, entity, entSerialNumber); + + if (g_EntityPackMap[entity][g_iCurrentClientIndexInLoop] == INVALID_PACKED_ENTITY_HANDLE) + return false; + + if (framesnapshotmanager->m_pLastPackedData[entity] != INVALID_PACKED_ENTITY_HANDLE) + { + PackedEntity *newpe = framesnapshotmanager->m_PackedEntities[ framesnapshotmanager->m_pLastPackedData[entity] ]; + PackedEntity *oldpe = framesnapshotmanager->m_PackedEntities[ g_EntityPackMap[entity][g_iCurrentClientIndexInLoop] ]; + + if (newpe && oldpe && newpe->m_nSnapshotCreationTick > oldpe->m_nSnapshotCreationTick) + { + gamehelpers->EdictOfIndex(entity)->m_fStateFlags |= FL_EDICT_CHANGED; + return false; + } + } + + framesnapshotmanager->m_pLastPackedData[entity] = g_EntityPackMap[entity][g_iCurrentClientIndexInLoop]; + return DETOUR_MEMBER_CALL(CFrameSnapshotManager_UsePreviouslySentPacket)(pSnapshot, entity, entSerialNumber); +} + +DETOUR_DECL_MEMBER2(CFrameSnapshotManager_GetPreviouslySentPacket, PackedEntity*, int, entity, int, entSerialNumber) +{ + if (g_iCurrentClientIndexInLoop == -1) + { + return DETOUR_MEMBER_CALL(CFrameSnapshotManager_GetPreviouslySentPacket)(entity, entSerialNumber); + } + + framesnapshotmanager->m_pLastPackedData[entity] = g_EntityPackMap[entity][g_iCurrentClientIndexInLoop]; + return DETOUR_MEMBER_CALL(CFrameSnapshotManager_GetPreviouslySentPacket)(entity, entSerialNumber); +} + +DETOUR_DECL_MEMBER2(CFrameSnapshotManager_CreatePackedEntity, PackedEntity*, CFrameSnapshot*, pSnapshot, int, entity) +{ + if (g_iCurrentClientIndexInLoop == -1) + { + return DETOUR_MEMBER_CALL(CFrameSnapshotManager_CreatePackedEntity)(pSnapshot, entity); + } + + PackedEntityHandle_t origHandle = framesnapshotmanager->m_pLastPackedData[entity]; + + if (g_EntityPackMap[entity][g_iCurrentClientIndexInLoop] != INVALID_PACKED_ENTITY_HANDLE) + { + framesnapshotmanager->m_pLastPackedData[entity] = g_EntityPackMap[entity][g_iCurrentClientIndexInLoop]; + } + + PackedEntity *result = DETOUR_MEMBER_CALL(CFrameSnapshotManager_CreatePackedEntity)(pSnapshot, entity); + + g_EntityPackMap[entity][g_iCurrentClientIndexInLoop] = framesnapshotmanager->m_pLastPackedData[entity]; + + return result; +} + +static void CopyFrameSnapshot(CFrameSnapshot *dest, const CFrameSnapshot *src); +static void CopyPackedEntities(CFrameSnapshot *dest, const CFrameSnapshot *src); +static bool g_bSetupClientPacks = false; + +DETOUR_DECL_STATIC3(PackEntities_Normal, void, int, iClientCount, CGameClient **, pClients, CFrameSnapshot *, pSnapShot) +{ + if (g_bSetupClientPacks) + return; + + return DETOUR_STATIC_CALL(PackEntities_Normal)(iClientCount, pClients, pSnapShot); +} + +DETOUR_DECL_STATIC3(SV_ComputeClientPacks, void, int, iClientCount, CGameClient **, pClients, CFrameSnapshot *, pSnapShot) +{ + if (playerhelpers->GetMaxClients() <= 1 +#ifndef DEBUG + || iClientCount <= 1 +#endif + || !g_pSendPropHookManager->IsAnyEntityHooked()) + { + return DETOUR_STATIC_CALL(SV_ComputeClientPacks)(iClientCount, pClients, pSnapShot); + } + + const int numEntities = pSnapShot->m_nValidEntities; + int numHooked = 0; + + // Move all hooked entities to the back + for (int i = numEntities-1; i >= 0; --i) + { + const auto entindex = pSnapShot->m_pValidEntities[i]; + if (g_pSendPropHookManager->IsEntityHooked(entindex)) + { + const auto tail = pSnapShot->m_nValidEntities - numHooked - 1; + std::swap(pSnapShot->m_pValidEntities[i], pSnapShot->m_pValidEntities[tail]); + numHooked++; + } + } + + // Make snapshots for each client + CUtlVector clientSnapshots(0, iClientCount); + clientSnapshots[0] = pSnapShot; + for (int i = 1; i < iClientCount; ++i) + { + clientSnapshots[i] = framesnapshotmanager->CreateEmptySnapshot(pSnapShot->m_nTickCount, pSnapShot->m_nNumEntities); + CopyFrameSnapshot(clientSnapshots[i], pSnapShot); + } + + // Setup transmit infos + g_bSetupClientPacks = true; + for (int i = 0; i < iClientCount; ++i) + { + CGameClient *client = pClients[i]; + CFrameSnapshot *snapshot = clientSnapshots[i]; + + DETOUR_STATIC_CALL(SV_ComputeClientPacks)(1, &client, snapshot); + } + g_bSetupClientPacks = false; + + // Pack all unhooked entities + { + g_iCurrentClientIndexInLoop = -1; + + pSnapShot->m_nValidEntities = numEntities - numHooked; + DETOUR_STATIC_CALL(PackEntities_Normal)(iClientCount, pClients, pSnapShot); + pSnapShot->m_nValidEntities = numEntities; + + for (int i = 1; i < iClientCount; ++i) + { + CopyPackedEntities(clientSnapshots[i], pSnapShot); + } + } + + // Pack hooked entities for each client + { + ConVarSaveSet linearpack(sv_parallel_packentities, "0"); + + for (int i = 0; i < iClientCount; ++i) + { + CGameClient *client = pClients[i]; + CFrameSnapshot *snapshot = clientSnapshots[i]; + + g_iCurrentClientIndexInLoop = client->GetPlayerSlot(); + + snapshot->m_pValidEntities += numEntities - numHooked; + snapshot->m_nValidEntities = numHooked; + + if (ext_sendproxy_frame_callback.GetBool()) + std::for_each_n( snapshot->m_pValidEntities, snapshot->m_nValidEntities, [](int edictidx){ gamehelpers->EdictOfIndex(edictidx)->m_fStateFlags |= FL_EDICT_CHANGED; }); + + DETOUR_STATIC_CALL(PackEntities_Normal)(1, &client, snapshot); + + snapshot->m_nValidEntities = numEntities; + snapshot->m_pValidEntities -= numEntities - numHooked; + } + } + + g_iCurrentClientIndexInLoop = -1; +} + +int ClientPacksDetour::GetCurrentClientIndex() +{ + if (g_iCurrentClientIndexInLoop == -1) + return -1; + + return g_iCurrentClientIndexInLoop + 1; +} + +void ClientPacksDetour::OnEntityHooked(int index) +{ + if (g_EntityPackMap.find(index) == g_EntityPackMap.end()) + { + PackedEntityArray arr; + arr.fill(INVALID_PACKED_ENTITY_HANDLE); + g_EntityPackMap.emplace(index, std::move(arr)); + } +} + +void ClientPacksDetour::OnEntityUnhooked(int index) +{ + if (g_EntityPackMap.find(index) == g_EntityPackMap.end()) + return; + + for (PackedEntityHandle_t pack : g_EntityPackMap[index]) + { + if (pack != INVALID_PACKED_ENTITY_HANDLE + && pack != framesnapshotmanager->m_pLastPackedData[index]) + { + framesnapshotmanager->RemoveEntityReference(pack); + } + } + + g_EntityPackMap.erase(index); +} + +bool ClientPacksDetour::Init(IGameConfig *gc) +{ + CDetourManager::Init(smutils->GetScriptingEngine(), gc); + + bool bDetoursInited = true; + CREATE_DETOUR(CFrameSnapshotManager_UsePreviouslySentPacket, "CFrameSnapshotManager::UsePreviouslySentPacket", bDetoursInited); + CREATE_DETOUR(CFrameSnapshotManager_GetPreviouslySentPacket, "CFrameSnapshotManager::GetPreviouslySentPacket", bDetoursInited); + CREATE_DETOUR(CFrameSnapshotManager_CreatePackedEntity, "CFrameSnapshotManager::CreatePackedEntity", bDetoursInited); + CREATE_DETOUR_STATIC(PackEntities_Normal, "PackEntities_Normal", bDetoursInited); + CREATE_DETOUR_STATIC(SV_ComputeClientPacks, "SV_ComputeClientPacks", bDetoursInited); + + if (!bDetoursInited) + return false; + + return true; +} + +void ClientPacksDetour::Shutdown() +{ + DESTROY_DETOUR(CFrameSnapshotManager_UsePreviouslySentPacket); + DESTROY_DETOUR(CFrameSnapshotManager_GetPreviouslySentPacket); + DESTROY_DETOUR(CFrameSnapshotManager_CreatePackedEntity); + DESTROY_DETOUR(PackEntities_Normal); + DESTROY_DETOUR(SV_ComputeClientPacks); +} + +void ClientPacksDetour::Clear() +{ + g_EntityPackMap.clear(); +} + +static void CopyFrameSnapshot(CFrameSnapshot *dest, const CFrameSnapshot *src) +{ + Assert(dest->m_nNumEntities == src->m_nNumEntities); + Q_memcpy(dest->m_pEntities, src->m_pEntities, dest->m_nNumEntities * sizeof(CFrameSnapshotEntry)); + + dest->m_nValidEntities = src->m_nValidEntities; + dest->m_pValidEntities = new unsigned short[dest->m_nValidEntities]; + Q_memcpy(dest->m_pValidEntities, src->m_pValidEntities, dest->m_nValidEntities * sizeof(unsigned short)); + + if (src->m_pHLTVEntityData != NULL) + { + Assert(dest->m_pHLTVEntityData == NULL); + dest->m_pHLTVEntityData = new CHLTVEntityData[dest->m_nValidEntities]; + Q_memset( dest->m_pHLTVEntityData, 0, dest->m_nValidEntities * sizeof(CHLTVEntityData) ); + } + + dest->m_iExplicitDeleteSlots = src->m_iExplicitDeleteSlots; + + // FIXME: Copy temp entity data +} + +static void CopyPackedEntities(CFrameSnapshot *dest, const CFrameSnapshot *src) +{ + int numEntities = dest->m_nNumEntities; + for (int i = 0; i < numEntities; ++i) + { + auto data = src->m_pEntities[i].m_pPackedData; + dest->m_pEntities[i].m_pPackedData = data; + framesnapshotmanager->AddEntityReference(data); + } + + if (dest->m_pHLTVEntityData && src->m_pHLTVEntityData) + { + Q_memcpy( dest->m_pHLTVEntityData, src->m_pHLTVEntityData, dest->m_nValidEntities * sizeof(CHLTVEntityData) ); + } +} diff --git a/extension/clientpacks_detours.h b/extension/clientpacks_detours.h new file mode 100644 index 0000000..bf2209b --- /dev/null +++ b/extension/clientpacks_detours.h @@ -0,0 +1,17 @@ +#ifndef _CLIENTPACKS_DETOURS_H +#define _CLIENTPACKS_DETOURS_H + +#include "extension.h" + +class ClientPacksDetour +{ +public: + static bool Init(IGameConfig *pGameConf); + static void Shutdown(); + static void Clear(); + static int GetCurrentClientIndex(); + static void OnEntityHooked(int index); + static void OnEntityUnhooked(int index); +}; + +#endif \ No newline at end of file diff --git a/extension/extension.cpp b/extension/extension.cpp index 2f561fd..937573e 100644 --- a/extension/extension.cpp +++ b/extension/extension.cpp @@ -29,536 +29,120 @@ * Version: $Id$ */ -#ifdef _WIN32 -#undef GetProp -#ifdef _WIN64 - #define PLATFORM_x64 -#else - #define PLATFORM_x32 -#endif -#elif defined __linux__ - #if defined __x86_64__ - #define PLATFORM_x64 - #else - #define PLATFORM_x32 - #endif -#endif - -#include "CDetour/detours.h" #include "extension.h" -#include "interfaceimpl.h" #include "natives.h" -#include "wrappers.h" - -//path: hl2sdk-/public/.h, "../public/" included to prevent compile errors due wrong directory scanning by compiler on my computer, and I'm too lazy to find where I can change that =D -#include <../public/iserver.h> -#include <../public/iclient.h> - -SH_DECL_HOOK1_void(IServerGameClients, ClientDisconnect, SH_NOATTRIB, false, edict_t *); -SH_DECL_HOOK1_void(IServerGameDLL, GameFrame, SH_NOATTRIB, false, bool); - -DECL_DETOUR(CFrameSnapshotManager_UsePreviouslySentPacket); -DECL_DETOUR(CFrameSnapshotManager_GetPreviouslySentPacket); -DECL_DETOUR(CFrameSnapshotManager_CreatePackedEntity); -DECL_DETOUR(SV_ComputeClientPacks); - -class CGameClient; -class CFrameSnapshot; - -int g_iCurrentClientIndexInLoop = -1; //used for optimization +#include "clientpacks_detours.h" +#include "sendprop_hookmanager.h" +#include "util.h" +#include +#include SendProxyManager g_SendProxyManager; -SendProxyManagerInterfaceImpl * g_pMyInterface = nullptr; SMEXT_LINK(&g_SendProxyManager); -CThreadFastMutex g_WorkMutex; - -CUtlVector g_Hooks; -CUtlVector g_HooksGamerules; -CUtlVector g_ChangeHooks; -CUtlVector g_ChangeHooksGamerules; +static SendPropHookManager s_SendPropHookManager; +SendPropHookManager *g_pSendPropHookManager = &s_SendPropHookManager; +std::string g_szGameRulesProxy; IServerGameEnts * gameents = nullptr; IServerGameClients * gameclients = nullptr; +CGlobalVars *gpGlobals = NULL; IBinTools* bintools = NULL; -ISDKTools * g_pSDKTools = nullptr; -ISDKHooks * g_pSDKHooks = nullptr; -IGameConfig * g_pGameConf = nullptr; -IGameConfig * g_pGameConfSDKTools = nullptr; - -ConVar * sv_parallel_packentities = nullptr; -ConVar * sv_parallel_sendsnapshot = nullptr; - -edict_t * g_pGameRulesProxyEdict = nullptr; -int g_iGameRulesProxyIndex = -1; -PackedEntityHandle_t g_PlayersPackedEntities[g_iMaxPlayers]; -void * g_pGameRules = nullptr; -bool g_bSendSnapshots = false; - -static CBaseEntity * FindEntityByServerClassname(int, const char *); -static void CallChangeCallbacks(PropChangeHook * pInfo, void * pOldValue, void * pNewValue); -static void CallChangeGamerulesCallbacks(PropChangeHookGamerules * pInfo, void * pOldValue, void * pNewValue); - -const char * g_szGameRulesProxy; - -CFrameSnapshotManager* framesnapshotmanager = nullptr; -void* CFrameSnapshotManager::s_pfnTakeTickSnapshot = nullptr; -ICallWrapper* CFrameSnapshotManager::s_callTakeTickSnapshot = nullptr; -void* CFrameSnapshot::s_pfnReleaseReference = nullptr; -ICallWrapper *CFrameSnapshot::s_callReleaseReference = nullptr; - -CUtlVector g_vHookedEdicts; - -//detours - -/*Call stack: - ... - 1. CGameServer::SendClientMessages //function we hooking to send props individually for each client - 2. SV_ComputeClientPacks //function we hooking to set edicts state and to know, need we call callbacks or not, but not in csgo - 3. PackEntities_Normal //if we in multiplayer - 4. SV_PackEntity //also we can hook this instead hooking ProxyFn, but there no reason to do that - 5. SendTable_Encode - 6. SendTable_EncodeProp //here the ProxyFn will be called - 7. ProxyFn //here our callbacks is called -*/ - -#ifndef DEBUG -// #define _FORCE_DEBUG - -#ifdef _FORCE_DEBUG -#include -#include -#include - -static int g_iOffset = 0; -static void *g_pPatch = nullptr; - -static bool g_bPrinted[SM_MAXPLAYERS] = {false}; -static ConVar left4sendproxy_debug("left4sendproxy_debug", "0", 0); - -void ChangeNoneMsg(const char *format, int edictIdx, const char *classname, const char *propname) -{ - Msg(" !!! %s : Entity %d (class '%s') reported ENTITY_CHANGE_NONE but '%s' changed.\n", g_iCurrentClientIndexInLoop == -1 ? "Nobody" : playerhelpers->GetGamePlayer(g_iCurrentClientIndexInLoop + 1)->GetName(), edictIdx, classname, propname); -} - -#define DEBUG -#endif - -#endif // #ifndef DEBUG +ISDKHooks * sdkhooks = nullptr; +ConVar *sv_parallel_packentities = nullptr; -DETOUR_DECL_MEMBER3(CFrameSnapshotManager_UsePreviouslySentPacket, bool, CFrameSnapshot*, pSnapshot, int, entity, int, entSerialNumber) +class AutoSMGameConfig { - if (g_iCurrentClientIndexInLoop == -1 - || entity != g_iGameRulesProxyIndex) +public: + static std::optional Load(const char* name) { - return DETOUR_MEMBER_CALL(CFrameSnapshotManager_UsePreviouslySentPacket)(pSnapshot, entity, entSerialNumber); - } - - if (g_PlayersPackedEntities[g_iCurrentClientIndexInLoop] == INVALID_PACKED_ENTITY_HANDLE) - return false; - - CFrameSnapshotManager *framesnapshotmanager = (CFrameSnapshotManager *)this; - framesnapshotmanager->m_pLastPackedData[entity] = g_PlayersPackedEntities[g_iCurrentClientIndexInLoop]; - return DETOUR_MEMBER_CALL(CFrameSnapshotManager_UsePreviouslySentPacket)(pSnapshot, entity, entSerialNumber); -} - -DETOUR_DECL_MEMBER2(CFrameSnapshotManager_GetPreviouslySentPacket, PackedEntity*, int, entity, int, entSerialNumber) -{ - if (g_iCurrentClientIndexInLoop == -1 - || entity != g_iGameRulesProxyIndex) - { - return DETOUR_MEMBER_CALL(CFrameSnapshotManager_GetPreviouslySentPacket)(entity, entSerialNumber); - } - - CFrameSnapshotManager *framesnapshotmanager = (CFrameSnapshotManager *)this; - -// #ifdef DEBUG -// char buffer[128]; -// smutils->Format(buffer, sizeof(buffer), "GetPreviouslySentPacket %d (0x%X / 0x%X)\n", entity, framesnapshotmanager->m_pLastPackedData[entity], g_PlayersPackedEntities[g_iCurrentClientIndexInLoop]); -// if (left4sendproxy_debug.GetBool() || !g_bPrinted[g_iCurrentClientIndexInLoop]) -// Msg("%s %s", playerhelpers->GetGamePlayer(g_iCurrentClientIndexInLoop+1)->GetName(), buffer); -// #endif - - framesnapshotmanager->m_pLastPackedData[entity] = g_PlayersPackedEntities[g_iCurrentClientIndexInLoop]; - return DETOUR_MEMBER_CALL(CFrameSnapshotManager_GetPreviouslySentPacket)(entity, entSerialNumber); -} - -DETOUR_DECL_MEMBER2(CFrameSnapshotManager_CreatePackedEntity, PackedEntity*, CFrameSnapshot*, pSnapshot, int, entity) -{ - if (g_iCurrentClientIndexInLoop == -1 - || entity != g_iGameRulesProxyIndex) - { - return DETOUR_MEMBER_CALL(CFrameSnapshotManager_CreatePackedEntity)(pSnapshot, entity); - } - - CFrameSnapshotManager *framesnapshotmanager = (CFrameSnapshotManager *)this; - PackedEntityHandle_t origHandle = framesnapshotmanager->m_pLastPackedData[entity]; - - if (g_PlayersPackedEntities[g_iCurrentClientIndexInLoop] != INVALID_PACKED_ENTITY_HANDLE) - { - framesnapshotmanager->m_pLastPackedData[entity] = g_PlayersPackedEntities[g_iCurrentClientIndexInLoop]; - } - - PackedEntity *result = DETOUR_MEMBER_CALL(CFrameSnapshotManager_CreatePackedEntity)(pSnapshot, entity); - - g_PlayersPackedEntities[g_iCurrentClientIndexInLoop] = framesnapshotmanager->m_pLastPackedData[entity]; - -#ifdef DEBUG - char buffer[128]; - smutils->Format(buffer, sizeof(buffer), "CreatePackedEntity %s (%d) (0x%X / 0x%X / 0x%X)\n", gamehelpers->GetEntityClassname(gamehelpers->EdictOfIndex(entity)), entity, origHandle, g_PlayersPackedEntities[g_iCurrentClientIndexInLoop], framesnapshotmanager->m_pLastPackedData[entity]); - if (left4sendproxy_debug.GetBool() || !g_bPrinted[g_iCurrentClientIndexInLoop]) - { - Msg("%s %s", playerhelpers->GetGamePlayer(g_iCurrentClientIndexInLoop+1)->GetName(), buffer); - g_bPrinted[g_iCurrentClientIndexInLoop] = true; - } -#endif - - return result; -} - -#if defined __linux__ -void __attribute__((__cdecl__)) SV_ComputeClientPacks_ActualCall(int iClientCount, CGameClient ** pClients, CFrameSnapshot * pSnapShot); -#else -void __cdecl SV_ComputeClientPacks_ActualCall(int iClientCount, CGameClient ** pClients, CFrameSnapshot * pSnapShot); -#endif - -//the better idea rewrite it with __declspec(naked) for csgo or use __stdcall function as main callback instead of this -DETOUR_DECL_STATIC3(SV_ComputeClientPacks, void, int, iClientCount, CGameClient **, pClients, CFrameSnapshot *, pSnapShot) -{ -#if defined _WIN32 && SOURCE_ENGINE == SE_CSGO - //so, here it is __userpurge call, we need manually get our arguments - __asm mov iClientCount, ecx - __asm mov pClients, edx - __asm mov pSnapShot, ebx // @Forgetest: ???Why??? -#endif - - IClient *pClient = reinterpret_cast((char *)pClients[0] + 4); - - g_iCurrentClientIndexInLoop = pClient->GetPlayerSlot(); - - for (int i = 0; i < g_vHookedEdicts.Count(); ++i) - g_vHookedEdicts[i]->m_fStateFlags |= FL_EDICT_CHANGED; - - if (g_HooksGamerules.Count() && g_pGameRulesProxyEdict) - g_pGameRulesProxyEdict->m_fStateFlags |= FL_EDICT_CHANGED; - - bool bEdictChanged[MAX_EDICTS] = {false}; - for (int i = 0; i < MAX_EDICTS; ++i) - { - bEdictChanged[i] = gamehelpers->EdictOfIndex(i)->HasStateChanged(); - } - - SV_ComputeClientPacks_ActualCall(1, &pClients[0], pSnapShot); - gameclients->PostClientMessagesSent(); - - for (int iClient = 1; iClient < iClientCount; ++iClient) - { - pClient = reinterpret_cast((char *)pClients[iClient] + 4); - g_iCurrentClientIndexInLoop = pClient->GetPlayerSlot(); - - CFrameSnapshot *snap = framesnapshotmanager->TakeTickSnapshot(pSnapShot->m_nTickCount); - snap->m_iExplicitDeleteSlots.CopyArray(pSnapShot->m_iExplicitDeleteSlots.Base(), pSnapShot->m_iExplicitDeleteSlots.Count()); - - for (int i = 0; i < snap->m_nValidEntities; ++i) - { - unsigned short index = snap->m_pValidEntities[i]; - if (bEdictChanged[index]) - { - gamehelpers->EdictOfIndex(index)->m_fStateFlags |= FL_EDICT_CHANGED; - } - } - - SV_ComputeClientPacks_ActualCall(1, &pClients[iClient], snap); - gameclients->PostClientMessagesSent(); - - snap->ReleaseReference(); - } - - g_iCurrentClientIndexInLoop = -1; -} - -#ifdef _FORCE_DEBUG -#undef DEBUG -#endif - -#if defined _WIN32 && SOURCE_ENGINE == SE_CSGO -__declspec(naked) void __cdecl SV_ComputeClientPacks_ActualCall(int iClientCount, CGameClient ** pClients, CFrameSnapshot * pSnapShot) -{ - //we do not use ebp here - __asm mov edx, pClients //we don't care about values in edx & ecx - __asm mov ecx, iClientCount - __asm mov ebx, pSnapShot - __asm push ebx // @Forgetest: ???Why??? - __asm call SV_ComputeClientPacks_Actual - __asm add esp, 0x4 //restore our stack - __asm retn -} -#else -#ifdef __linux__ -void __attribute__((__cdecl__)) SV_ComputeClientPacks_ActualCall(int iClientCount, CGameClient ** pClients, CFrameSnapshot * pSnapShot) -#else -void __cdecl SV_ComputeClientPacks_ActualCall(int iClientCount, CGameClient ** pClients, CFrameSnapshot * pSnapShot) -#endif -{ - return DETOUR_STATIC_CALL(SV_ComputeClientPacks)(iClientCount, pClients, pSnapShot); -} -#endif - -//hooks - -void SendProxyManager::OnEntityDestroyed(CBaseEntity* pEnt) -{ - int idx = gamehelpers->EntityToBCompatRef(pEnt); - for (int i = 0; i < g_Hooks.Count(); i++) - { - if (g_Hooks[i].objectID == idx) + char error[256]; + IGameConfig *gc = nullptr; + if (!gameconfs->LoadGameConfigFile(name, &gc, error, sizeof(error))) { - g_SendProxyManager.UnhookProxy(i); + smutils->LogError(myself, "Could not read config file sdktools.games.txt: %s", error); + return {}; } + return gc; } - for (int i = 0; i < g_ChangeHooks.Count(); i++) +public: + AutoSMGameConfig() noexcept: gc_(nullptr) { } + AutoSMGameConfig(IGameConfig *gc) noexcept: gc_(gc) { } + ~AutoSMGameConfig() { - if (g_ChangeHooks[i].objectID == idx) - g_ChangeHooks.Remove(i--); + if (gc_ != nullptr) + gameconfs->CloseGameConfigFile(gc_); } - - if (idx >= 0 && idx < MAX_EDICTS) - g_vHookedEdicts.FindAndRemove(gamehelpers->EdictOfIndex(idx)); -} - -void Hook_ClientDisconnect(edict_t * pEnt) -{ - for (int i = 0; i < g_Hooks.Count(); i++) + operator IGameConfig*() const noexcept { - if (g_Hooks[i].objectID == gamehelpers->IndexOfEdict(pEnt)) - g_SendProxyManager.UnhookProxy(i); + return gc_; } - - for (int i = 0; i < g_ChangeHooks.Count(); i++) + IGameConfig* operator->() const noexcept { - if (g_ChangeHooks[i].objectID == gamehelpers->IndexOfEdict(pEnt)) - g_ChangeHooks.Remove(i--); + return gc_; } - RETURN_META(MRES_IGNORED); -} - -void Hook_GameFrame(bool simulating) -{ - if (simulating) - { - for (int i = 0; i < g_ChangeHooks.Count(); i++) - { - switch(g_ChangeHooks[i].PropType) - { - case PropType::Prop_Int: - { - edict_t * pEnt = gamehelpers->EdictOfIndex(g_ChangeHooks[i].objectID); - CBaseEntity * pEntity = gameents->EdictToBaseEntity(pEnt); - int iCurrent = *(int *)((unsigned char *)pEntity + g_ChangeHooks[i].Offset); - if (iCurrent != g_ChangeHooks[i].iLastValue) - { - CallChangeCallbacks(&g_ChangeHooks[i], (void *)&g_ChangeHooks[i].iLastValue, (void *)&iCurrent); - g_ChangeHooks[i].iLastValue = iCurrent; - } - break; - } - case PropType::Prop_Float: - { - edict_t * pEnt = gamehelpers->EdictOfIndex(g_ChangeHooks[i].objectID); - CBaseEntity * pEntity = gameents->EdictToBaseEntity(pEnt); - float flCurrent = *(float *)((unsigned char *)pEntity + g_ChangeHooks[i].Offset); - if (flCurrent != g_ChangeHooks[i].flLastValue) - { - CallChangeCallbacks(&g_ChangeHooks[i], (void *)&g_ChangeHooks[i].flLastValue, (void *)&flCurrent); - g_ChangeHooks[i].flLastValue = flCurrent; - } - break; - } - case PropType::Prop_String: - { - edict_t * pEnt = gamehelpers->EdictOfIndex(g_ChangeHooks[i].objectID); - CBaseEntity * pEntity = gameents->EdictToBaseEntity(pEnt); - const char * szCurrent = (const char *)((unsigned char *)pEntity + g_ChangeHooks[i].Offset); - if (strcmp(szCurrent, g_ChangeHooks[i].cLastValue) != 0) - { - CallChangeCallbacks(&g_ChangeHooks[i], (void *)g_ChangeHooks[i].cLastValue, (void *)szCurrent); - memset(g_ChangeHooks[i].cLastValue, 0, sizeof(g_ChangeHooks[i].cLastValue)); - strncpynull(g_ChangeHooks[i].cLastValue, szCurrent, sizeof(g_ChangeHooks[i].cLastValue)); - } - break; - } - case PropType::Prop_Vector: - { - edict_t * pEnt = gamehelpers->EdictOfIndex(g_ChangeHooks[i].objectID); - CBaseEntity * pEntity = gameents->EdictToBaseEntity(pEnt); - Vector * pVec = (Vector *)((unsigned char *)pEntity + g_ChangeHooks[i].Offset); - if (*pVec != g_ChangeHooks[i].vecLastValue) - { - CallChangeCallbacks(&g_ChangeHooks[i], (void *)&g_ChangeHooks[i].vecLastValue, (void *)pVec); - g_ChangeHooks[i].vecLastValue = *pVec; - } - break; - } - default: rootconsole->ConsolePrint("%s: SendProxy report: Unknown prop type (%s).", __func__, g_ChangeHooks[i].pVar->GetName()); - } - } - if (!g_pGameRules && g_pSDKTools) - { - g_pGameRules = g_pSDKTools->GetGameRules(); - if (!g_pGameRules) - { - g_pSM->LogError(myself, "CRITICAL ERROR: Could not get gamerules pointer!"); - return; - } - } - //Gamerules hooks - for (int i = 0; i < g_ChangeHooksGamerules.Count(); i++) - { - switch(g_ChangeHooksGamerules[i].PropType) - { - case PropType::Prop_Int: - { - int iCurrent = *(int *)((unsigned char *)g_pGameRules + g_ChangeHooksGamerules[i].Offset); - if (iCurrent != g_ChangeHooksGamerules[i].iLastValue) - { - CallChangeGamerulesCallbacks(&g_ChangeHooksGamerules[i], (void *)&g_ChangeHooksGamerules[i].iLastValue, (void *)&iCurrent); - g_ChangeHooksGamerules[i].iLastValue = iCurrent; - } - break; - } - case PropType::Prop_Float: - { - float flCurrent = *(float *)((unsigned char *)g_pGameRules + g_ChangeHooksGamerules[i].Offset); - if (flCurrent != g_ChangeHooksGamerules[i].flLastValue) - { - CallChangeGamerulesCallbacks(&g_ChangeHooksGamerules[i], (void *)&g_ChangeHooksGamerules[i].flLastValue, (void *)&flCurrent); - g_ChangeHooksGamerules[i].flLastValue = flCurrent; - } - break; - } - case PropType::Prop_String: - { - const char * szCurrent = (const char *)((unsigned char *)g_pGameRules + g_ChangeHooksGamerules[i].Offset); - if (strcmp(szCurrent, g_ChangeHooksGamerules[i].cLastValue) != 0) - { - CallChangeGamerulesCallbacks(&g_ChangeHooksGamerules[i], (void *)g_ChangeHooksGamerules[i].cLastValue, (void *)szCurrent); - memset(g_ChangeHooks[i].cLastValue, 0, sizeof(g_ChangeHooks[i].cLastValue)); - strncpynull(g_ChangeHooks[i].cLastValue, szCurrent, sizeof(g_ChangeHooks[i].cLastValue)); - } - break; - } - case PropType::Prop_Vector: - { - Vector * pVec = (Vector *)((unsigned char *)g_pGameRules + g_ChangeHooksGamerules[i].Offset); - if (*pVec != g_ChangeHooksGamerules[i].vecLastValue) - { - CallChangeGamerulesCallbacks(&g_ChangeHooksGamerules[i], (void *)&g_ChangeHooksGamerules[i].vecLastValue, (void *)pVec); - g_ChangeHooksGamerules[i].vecLastValue = *pVec; - } - break; - } - default: rootconsole->ConsolePrint("%s: SendProxy report: Unknown prop type (%s).", __func__, g_ChangeHooksGamerules[i].pVar->GetName()); - } - } - } - RETURN_META(MRES_IGNORED); -} +private: + IGameConfig *gc_; +}; -//main sm class implementation +CFrameSnapshotManager* framesnapshotmanager = nullptr; +void* CFrameSnapshotManager::s_pfnCreateEmptySnapshot = nullptr; +ICallWrapper* CFrameSnapshotManager::s_callCreateEmptySnapshot = nullptr; +void* CFrameSnapshotManager::s_pfnRemoveEntityReference = nullptr; +ICallWrapper* CFrameSnapshotManager::s_callRemoveEntityReference = nullptr; +void* CFrameSnapshot::s_pfnReleaseReference = nullptr; +ICallWrapper *CFrameSnapshot::s_callReleaseReference = nullptr; bool SendProxyManager::SDK_OnLoad(char *error, size_t maxlength, bool late) { - char conf_error[255]; - if (!gameconfs->LoadGameConfigFile("sdktools.games", &g_pGameConfSDKTools, conf_error, sizeof(conf_error))) - { - if (conf_error[0]) - snprintf(error, maxlength, "Could not read config file sdktools.games.txt: %s", conf_error); + auto gc_sdktools = AutoSMGameConfig::Load("sdktools.games"); + auto gc = AutoSMGameConfig::Load("sendproxy"); + if (!gc_sdktools || !gc) return false; - } - - g_szGameRulesProxy = g_pGameConfSDKTools->GetKeyValue("GameRulesProxy"); - - if (!gameconfs->LoadGameConfigFile("sendproxy", &g_pGameConf, conf_error, sizeof(conf_error))) - { - if (conf_error[0]) - snprintf(error, maxlength, "Could not read config file sendproxy.txt: %s", conf_error); - return false; - } - if (!g_pGameConf->GetMemSig("CFrameSnapshotManager::TakeTickSnapshot", &CFrameSnapshotManager::s_pfnTakeTickSnapshot)) - { - if (conf_error[0]) - snprintf(error, maxlength, "Unable to find signature address ""\"CFrameSnapshotManager::TakeTickSnapshot\""" (%s)", conf_error); - return false; - } + g_szGameRulesProxy = gc_sdktools.value()->GetKeyValue("GameRulesProxy"); - if (!g_pGameConf->GetMemSig("CFrameSnapshot::ReleaseReference", &CFrameSnapshot::s_pfnReleaseReference)) + if (!gc.value()->GetMemSig("CFrameSnapshotManager::CreateEmptySnapshot", &CFrameSnapshotManager::s_pfnCreateEmptySnapshot)) { - if (conf_error[0]) - snprintf(error, maxlength, "Unable to find signature address ""\"CFrameSnapshot::ReleaseReference\""" (%s)", conf_error); + ke::SafeSprintf(error, maxlength, "Unable to find signature address ""\"CFrameSnapshotManager::CreateEmptySnapshot\""""); return false; } - if (!g_pGameConf->GetAddress("framesnapshotmanager", (void**)&framesnapshotmanager)) + if (!gc.value()->GetMemSig("CFrameSnapshotManager::RemoveEntityReference", &CFrameSnapshotManager::s_pfnRemoveEntityReference)) { - if (conf_error[0]) - snprintf(error, maxlength, "Unable to find offset ""\"CGameClient::edict\""" (%s)", conf_error); + ke::SafeSprintf(error, maxlength, "Unable to find signature address ""\"CFrameSnapshotManager::RemoveEntityReference\""""); return false; } -#ifdef DEBUG - if (!g_pGameConf->GetAddress("PackEntities_Normal", (void**)&g_pPatch)) + if (!gc.value()->GetMemSig("CFrameSnapshot::ReleaseReference", &CFrameSnapshot::s_pfnReleaseReference)) { - if (conf_error[0]) - snprintf(error, maxlength, "Unable to find offset ""\"PackEntities_Normal\""" (%s)", conf_error); + ke::SafeSprintf(error, maxlength, "Unable to find signature address ""\"CFrameSnapshot::ReleaseReference\""""); return false; } - if (*(uint8_t*)g_pPatch != 0xE8) + if (!gc.value()->GetAddress("framesnapshotmanager", (void**)&framesnapshotmanager)) { - if (conf_error[0]) - snprintf(error, maxlength, "Invalid g_pPatch (%s)", conf_error); + ke::SafeSprintf(error, maxlength, "Unable to find address (framesnapshotmanager)"); return false; } - SourceHook::SetMemAccess(g_pPatch, 5, SH_MEM_READ | SH_MEM_WRITE | SH_MEM_EXEC); - - void (*pDest)(const char *, int, const char *, const char *) = &ChangeNoneMsg; - g_iOffset = *(int *)((uint8_t *)g_pPatch + 1); - int offs = (intptr_t)pDest - (intptr_t)((uint8_t *)g_pPatch + 5); - Msg("(intptr_t *)&ChangeNoneMsg - (intptr_t *)((char *)g_pPatch + 5) = %X | %X | (%X / %X)\n", offs, g_iOffset, g_pPatch, pDest); - *(intptr_t *)((uint8_t *)g_pPatch + 1) = offs; -#endif - - CDetourManager::Init(smutils->GetScriptingEngine(), g_pGameConf); - - bool bDetoursInited = false; - CREATE_DETOUR(CFrameSnapshotManager_UsePreviouslySentPacket, "CFrameSnapshotManager::UsePreviouslySentPacket", bDetoursInited); - CREATE_DETOUR(CFrameSnapshotManager_GetPreviouslySentPacket, "CFrameSnapshotManager::GetPreviouslySentPacket", bDetoursInited); - CREATE_DETOUR(CFrameSnapshotManager_CreatePackedEntity, "CFrameSnapshotManager::CreatePackedEntity", bDetoursInited); - CREATE_DETOUR_STATIC(SV_ComputeClientPacks, "SV_ComputeClientPacks", bDetoursInited); - - if (!bDetoursInited) + if (!ClientPacksDetour::Init(gc.value())) { - snprintf(error, maxlength, "Could not create detours, see error log!"); return false; } if (late) //if we loaded late, we need manually to call that OnCoreMapStart(nullptr, 0, 0); - sharesys->AddDependency(myself, "sdktools.ext", true, true); sharesys->AddDependency(myself, "sdkhooks.ext", true, true); sharesys->AddDependency(myself, "bintools.ext", true, true); - g_pMyInterface = new SendProxyManagerInterfaceImpl(); - sharesys->AddInterface(myself, g_pMyInterface); - //we should not maintain compatibility with old plugins which uses earlier versions of sendproxy (< 1.3) - sharesys->RegisterLibrary(myself, "sendproxy13"); - plsys->AddPluginsListener(&g_SendProxyManager); + sharesys->RegisterLibrary(myself, "sendproxy2"); + plsys->AddPluginsListener(this); + playerhelpers->AddClientListener(this); ConVar_Register(0, this); return true; @@ -566,40 +150,51 @@ bool SendProxyManager::SDK_OnLoad(char *error, size_t maxlength, bool late) void SendProxyManager::SDK_OnAllLoaded() { - sharesys->AddNatives(myself, g_MyNatives); - SM_GET_LATE_IFACE(SDKTOOLS, g_pSDKTools); - SM_GET_LATE_IFACE(SDKHOOKS, g_pSDKHooks); + SM_GET_LATE_IFACE(SDKHOOKS, sdkhooks); SM_GET_LATE_IFACE(BINTOOLS, bintools); - if (g_pSDKHooks) + if (sdkhooks) { - g_pSDKHooks->AddEntityListener(this); + sdkhooks->AddEntityListener(this); } if (bintools) { SourceMod::PassInfo params[] = { { PassType_Basic, PASSFLAG_BYVAL, sizeof(int), NULL, 0 }, - { PassType_Basic, PASSFLAG_BYVAL, sizeof(CFrameSnapshot*), NULL, 0 } + { PassType_Basic, PASSFLAG_BYVAL, sizeof(int), NULL, 0 }, + { PassType_Basic, PASSFLAG_BYVAL, sizeof(CFrameSnapshot*), NULL, 0 }, + { PassType_Basic, PASSFLAG_BYVAL, sizeof(PackedEntityHandle_t), NULL, 0 } }; CFrameSnapshot::s_callReleaseReference = bintools->CreateCall(CFrameSnapshot::s_pfnReleaseReference, CallConv_ThisCall, NULL, params, 0); if (CFrameSnapshot::s_callReleaseReference == NULL) { - smutils->LogError(myself, "Unable to create ICallWrapper for \"CFrameSnapshot::s_callReleaseReference\"!"); + smutils->LogError(myself, "Unable to create ICallWrapper for \"CFrameSnapshot::ReleaseReference\"!"); return; } - CFrameSnapshotManager::s_callTakeTickSnapshot = bintools->CreateCall(CFrameSnapshotManager::s_pfnTakeTickSnapshot, CallConv_ThisCall, ¶ms[1], ¶ms[0], 1); - if (CFrameSnapshotManager::s_callTakeTickSnapshot == NULL) { - smutils->LogError(myself, "Unable to create ICallWrapper for \"CFrameSnapshotManager::TakeTickSnapshot\"!"); + CFrameSnapshotManager::s_callCreateEmptySnapshot = bintools->CreateCall(CFrameSnapshotManager::s_pfnCreateEmptySnapshot, CallConv_ThisCall, ¶ms[2], ¶ms[0], 2); + if (CFrameSnapshotManager::s_callCreateEmptySnapshot == NULL) { + smutils->LogError(myself, "Unable to create ICallWrapper for \"CFrameSnapshotManager::CreateEmptySnapshot\"!"); + return; + } + + CFrameSnapshotManager::s_callRemoveEntityReference = bintools->CreateCall(CFrameSnapshotManager::s_pfnRemoveEntityReference, CallConv_ThisCall, NULL, ¶ms[3], 1); + if (CFrameSnapshotManager::s_callRemoveEntityReference == NULL) { + smutils->LogError(myself, "Unable to create ICallWrapper for \"CFrameSnapshotManager::RemoveEntityReference\"!"); return; } } + + sharesys->AddNatives(myself, g_MyNatives); } bool SendProxyManager::QueryInterfaceDrop(SMInterface* pInterface) { - return pInterface != bintools; + if (pInterface == sdkhooks || pInterface == bintools) + return false; + + return true; } void SendProxyManager::NotifyInterfaceDrop(SMInterface* pInterface) @@ -609,6 +204,7 @@ void SendProxyManager::NotifyInterfaceDrop(SMInterface* pInterface) bool SendProxyManager::QueryRunning(char* error, size_t maxlength) { + SM_CHECK_IFACE(SDKHOOKS, sdkhooks); SM_CHECK_IFACE(BINTOOLS, bintools); return true; @@ -622,1136 +218,92 @@ bool SendProxyManager::RegisterConCommandBase(ConCommandBase* pVar) void SendProxyManager::SDK_OnUnload() { - for (int i = 0; i < g_Hooks.Count(); i++) - { - g_Hooks[i].pVar->SetProxyFn(g_Hooks[i].pRealProxy); - } - - for (int i = 0; i < g_HooksGamerules.Count(); i++) - { - g_HooksGamerules[i].pVar->SetProxyFn(g_HooksGamerules[i].pRealProxy); - } - - SH_REMOVE_HOOK(IServerGameClients, ClientDisconnect, gameclients, SH_STATIC(Hook_ClientDisconnect), false); - SH_REMOVE_HOOK(IServerGameDLL, GameFrame, gamedll, SH_STATIC(Hook_GameFrame), false); + plsys->RemovePluginsListener(this); + playerhelpers->RemoveClientListener(this); - DESTROY_DETOUR(CFrameSnapshotManager_UsePreviouslySentPacket); - DESTROY_DETOUR(CFrameSnapshotManager_GetPreviouslySentPacket); - DESTROY_DETOUR(CFrameSnapshotManager_CreatePackedEntity); - DESTROY_DETOUR(SV_ComputeClientPacks); - - gameconfs->CloseGameConfigFile(g_pGameConf); - gameconfs->CloseGameConfigFile(g_pGameConfSDKTools); - - plsys->RemovePluginsListener(&g_SendProxyManager); - if( g_pSDKHooks ) + if (sdkhooks) { - g_pSDKHooks->RemoveEntityListener(this); + sdkhooks->RemoveEntityListener(this); } - delete g_pMyInterface; - -#ifdef DEBUG - if (g_iOffset) - *(intptr_t *)((uint8_t *)g_pPatch + 1) = g_iOffset; -#endif ConVar_Unregister(); } -void SendProxyManager::OnCoreMapEnd() -{ - for (int i = 0; i < g_HooksGamerules.Count(); i++) - { - UnhookProxyGamerules(i); - i--; - } - - g_pGameRulesProxyEdict = nullptr; - g_iGameRulesProxyIndex = -1; - - for (int i = 0; i < g_iMaxPlayers; ++i) - g_PlayersPackedEntities[i] = INVALID_PACKED_ENTITY_HANDLE; -} - -void SendProxyManager::OnCoreMapStart(edict_t * pEdictList, int edictCount, int clientMax) -{ - for (int i = 0; i < g_iMaxPlayers; ++i) - g_PlayersPackedEntities[i] = INVALID_PACKED_ENTITY_HANDLE; - - CBaseEntity *pGameRulesProxyEnt = FindEntityByServerClassname(0, g_szGameRulesProxy); - if (!pGameRulesProxyEnt) - { - smutils->LogError(myself, "Unable to get gamerules proxy ent (1)!"); - return; - } - g_pGameRulesProxyEdict = gameents->BaseEntityToEdict(pGameRulesProxyEnt); - if (!g_pGameRulesProxyEdict) - smutils->LogError(myself, "Unable to get gamerules proxy ent (2)!"); - - if (g_pGameRulesProxyEdict) - { - g_iGameRulesProxyIndex = gamehelpers->IndexOfEdict(g_pGameRulesProxyEdict); - } -} - -bool SendProxyManager::SDK_OnMetamodLoad(ISmmAPI *ismm, char *error, size_t maxlen, bool late) -{ - GET_V_IFACE_ANY(GetServerFactory, gameents, IServerGameEnts, INTERFACEVERSION_SERVERGAMEENTS); - GET_V_IFACE_ANY(GetServerFactory, gameclients, IServerGameClients, INTERFACEVERSION_SERVERGAMECLIENTS); - GET_V_IFACE_ANY(GetEngineFactory, g_pCVar, ICvar, CVAR_INTERFACE_VERSION); - - SH_ADD_HOOK(IServerGameDLL, GameFrame, gamedll, SH_STATIC(Hook_GameFrame), false); - SH_ADD_HOOK(IServerGameClients, ClientDisconnect, gameclients, SH_STATIC(Hook_ClientDisconnect), false); - - GET_CONVAR(sv_parallel_packentities); - sv_parallel_packentities->SetValue(0); //If we don't do that the sendproxy extension will crash the server (Post ref: https://forums.alliedmods.net/showpost.php?p=2540106&postcount=324 ) - GET_CONVAR(sv_parallel_sendsnapshot); - sv_parallel_sendsnapshot->SetValue(0); //If we don't do that, sendproxy will not work correctly and may crash server. This affects all versions of sendproxy manager! - - return true; -} - -void SendProxyManager::OnPluginUnloaded(IPlugin * plugin) -{ - IPluginContext * pCtx = plugin->GetBaseContext(); - for (int i = 0; i < g_Hooks.Count(); i++) - { - if (g_Hooks[i].sCallbackInfo.iCallbackType == CallBackType::Callback_PluginFunction && ((IPluginFunction *)g_Hooks[i].sCallbackInfo.pCallback)->GetParentContext() == pCtx) - { - UnhookProxy(i); - i--; - } - } - for (int i = 0; i < g_HooksGamerules.Count(); i++) - { - if (g_HooksGamerules[i].sCallbackInfo.iCallbackType == CallBackType::Callback_PluginFunction && ((IPluginFunction *)g_HooksGamerules[i].sCallbackInfo.pCallback)->GetParentContext() == pCtx) - { - UnhookProxyGamerules(i); - i--; - } - } - for (int i = 0; i < g_ChangeHooks.Count(); i++) - { - auto pCallbacks = g_ChangeHooks[i].vCallbacksInfo; - if (pCallbacks->Count()) - { - for (int j = 0; j < pCallbacks->Count(); j++) - if ((*pCallbacks)[j].iCallbackType == CallBackType::Callback_PluginFunction && (IPluginContext *)(*pCallbacks)[j].pOwner == pCtx) - { - pCallbacks->Remove(j--); - } - } - //else do not needed here - if (!pCallbacks->Count()) - g_ChangeHooks.Remove(i); - } - for (int i = 0; i < g_ChangeHooksGamerules.Count(); i++) - { - auto pCallbacks = g_ChangeHooksGamerules[i].vCallbacksInfo; - if (pCallbacks->Count()) - { - for (int j = 0; j < pCallbacks->Count(); j++) - if ((*pCallbacks)[j].iCallbackType == CallBackType::Callback_PluginFunction && (IPluginContext *)(*pCallbacks)[j].pOwner == pCtx) - { - pCallbacks->Remove(j--); - } - } - //else do not needed here - if (!pCallbacks->Count()) - g_ChangeHooksGamerules.Remove(i); - } -} - -//functions - -bool SendProxyManager::AddHookToList(SendPropHook hook) -{ - //Need to make sure this prop isn't already hooked for this entity - we don't care anymore - bool bEdictHooked = false; - for (int i = 0; i < g_Hooks.Count(); i++) - { - if (g_Hooks[i].objectID == hook.objectID) - { - //we don't care anymore - //if (g_Hooks[i].pVar == hook.pVar) - // return false; - //else - bEdictHooked = true; - } - } - g_Hooks.AddToTail(hook); - if (!bEdictHooked) - g_vHookedEdicts.AddToTail(hook.pEnt); - return true; -} - -bool SendProxyManager::AddHookToListGamerules(SendPropHookGamerules hook) -{ - //Need to make sure this prop isn't already hooked for this entity - we don't care anymore - /*for (int i = 0; i < g_HooksGamerules.Count(); i++) - { - if (g_HooksGamerules[i].pVar == hook.pVar) - return false; - }*/ - g_HooksGamerules.AddToTail(hook); - return true; -} - -bool SendProxyManager::AddChangeHookToList(PropChangeHook sHook, CallBackInfo * pInfo) -{ - decltype(&g_ChangeHooks[0]) pHookInfo = nullptr; - for (int i = 0; i < g_ChangeHooks.Count(); i++) - { - if (g_ChangeHooks[i].pVar == sHook.pVar) - { - pHookInfo = &g_ChangeHooks[i]; - break; - } - } - if (pHookInfo) - { - //just validate it - switch (sHook.PropType) - { - case PropType::Prop_Int: - case PropType::Prop_Float: - case PropType::Prop_String: - case PropType::Prop_Vector: - break; - default: return false; - } - pHookInfo->vCallbacksInfo->AddToTail(*pInfo); - } - else - { - edict_t * pEnt = gamehelpers->EdictOfIndex(sHook.objectID); - if (!pEnt || pEnt->IsFree()) return false; //should never happen - CBaseEntity * pEntity = gameents->EdictToBaseEntity(pEnt); - if (!pEntity) return false; //should never happen - switch (sHook.PropType) - { - case PropType::Prop_Int: sHook.iLastValue = *(int *)((unsigned char *)pEntity + sHook.Offset); break; - case PropType::Prop_Float: sHook.flLastValue = *(float *)((unsigned char*)pEntity + sHook.Offset); break; - case PropType::Prop_String: strncpynull(sHook.cLastValue, (const char *)((unsigned char *)pEntity + sHook.Offset), sizeof(sHook.cLastValue)); break; - case PropType::Prop_Vector: sHook.vecLastValue = *(Vector *)((unsigned char *)pEntity + sHook.Offset); break; - default: return false; - } - - CallBackInfo sCallInfo = *pInfo; - sHook.vCallbacksInfo->AddToTail(sCallInfo); - g_ChangeHooks.AddToTail(sHook); - } - return true; -} - -bool SendProxyManager::AddChangeHookToListGamerules(PropChangeHookGamerules sHook, CallBackInfo * pInfo) -{ - decltype(&g_ChangeHooksGamerules[0]) pHookInfo = nullptr; - for (int i = 0; i < g_ChangeHooksGamerules.Count(); i++) - { - if (g_ChangeHooksGamerules[i].pVar == sHook.pVar) - { - pHookInfo = &g_ChangeHooksGamerules[i]; - break; - } - } - if (pHookInfo) - { - //just validate it - switch (sHook.PropType) - { - case PropType::Prop_Int: - case PropType::Prop_Float: - case PropType::Prop_String: - case PropType::Prop_Vector: - break; - default: return false; - } - pHookInfo->vCallbacksInfo->AddToTail(*pInfo); - } - else - { - switch (sHook.PropType) - { - case PropType::Prop_Int: sHook.iLastValue = *(int *)((unsigned char *)g_pGameRules + sHook.Offset); break; - case PropType::Prop_Float: sHook.flLastValue = *(float *)((unsigned char*)g_pGameRules + sHook.Offset); break; - case PropType::Prop_String: strncpynull(sHook.cLastValue, (const char *)((unsigned char *)g_pGameRules + sHook.Offset), sizeof(sHook.cLastValue)); break; - case PropType::Prop_Vector: sHook.vecLastValue = *(Vector *)((unsigned char *)g_pGameRules + sHook.Offset); break; - default: return false; - } - - CallBackInfo sCallInfo = *pInfo; - sHook.vCallbacksInfo->AddToTail(sCallInfo); - g_ChangeHooksGamerules.AddToTail(sHook); - } - return true; -} - -void SendProxyManager::UnhookProxy(int i) -{ - //if there are other hooks for this prop, don't change the proxy, just remove it from our list - for (int j = 0; j < g_Hooks.Count(); j++) - { - if (g_Hooks[j].pVar == g_Hooks[i].pVar && i != j) - { - CallListenersForHookID(i); - g_Hooks.Remove(i); //for others: this not a mistake - return; - } - } - for (int j = 0; j < g_vHookedEdicts.Count(); j++) - if (g_vHookedEdicts[j] == g_Hooks[i].pEnt) - { - g_vHookedEdicts.Remove(j); - break; - } - CallListenersForHookID(i); - g_Hooks[i].pVar->SetProxyFn(g_Hooks[i].pRealProxy); - g_Hooks.Remove(i); -} - -void SendProxyManager::UnhookProxyGamerules(int i) -{ - //if there are other hooks for this prop, don't change the proxy, just remove it from our list - for (int j = 0; j < g_HooksGamerules.Count(); j++) - { - if (g_HooksGamerules[j].pVar == g_HooksGamerules[i].pVar && i != j) - { - CallListenersForHookIDGamerules(i); - g_HooksGamerules.Remove(i); - return; - } - } - CallListenersForHookIDGamerules(i); - g_HooksGamerules[i].pVar->SetProxyFn(g_HooksGamerules[i].pRealProxy); - g_HooksGamerules.Remove(i); -} - -void SendProxyManager::UnhookChange(int i, CallBackInfo * pInfo) -{ - if (i < 0 || i >= g_ChangeHooks.Count()) - return; - auto pCallbacks = g_ChangeHooks[i].vCallbacksInfo; - if (pCallbacks->Count()) - { - for (int j = 0; j < pCallbacks->Count(); j++) - if ((*pCallbacks)[j].iCallbackType == pInfo->iCallbackType && (*pCallbacks)[j].pCallback == (void *)pInfo->pCallback) - { - pCallbacks->Remove(j--); - } - } - //if there no any callbacks anymore, then remove all info about this hook - if (!pCallbacks->Count()) - g_ChangeHooks.Remove(i); -} - -void SendProxyManager::UnhookChangeGamerules(int i, CallBackInfo * pInfo) -{ - if (i < 0 || i >= g_ChangeHooksGamerules.Count()) - return; - auto pCallbacks = g_ChangeHooksGamerules[i].vCallbacksInfo; - if (pCallbacks->Count()) - { - for (int j = 0; j < pCallbacks->Count(); j++) - if ((*pCallbacks)[j].iCallbackType == pInfo->iCallbackType && (*pCallbacks)[j].pCallback == (void *)pInfo->pCallback) - { - pCallbacks->Remove(j--); - } - } - //if there no any callbacks anymore, then remove all info about this hook - if (!pCallbacks->Count()) - g_ChangeHooksGamerules.Remove(i); -} - -//callbacks - -//Change - -void CallChangeCallbacks(PropChangeHook * pInfo, void * pOldValue, void * pNewValue) +void SendProxyManager::OnEntityDestroyed(CBaseEntity* pEnt) { - for (int i = 0; i < pInfo->vCallbacksInfo->Count(); i++) - { - auto sCallback = (*pInfo->vCallbacksInfo)[i]; - switch (sCallback.iCallbackType) - { - case CallBackType::Callback_CPPCallbackInterface: - { - edict_t * pEnt = gamehelpers->EdictOfIndex(pInfo->objectID); - if (!pEnt) - break; //??? - ISendProxyChangeCallbacks * pCallbacks = (ISendProxyChangeCallbacks *)sCallback.pCallback; - pCallbacks->OnEntityPropChange(gameents->EdictToBaseEntity(pEnt), pInfo->pVar, pNewValue, pOldValue, pInfo->PropType, pInfo->Element); - } - break; - case CallBackType::Callback_PluginFunction: - { - IPluginFunction * pCallBack = (IPluginFunction *)sCallback.pCallback; - switch (pInfo->PropType) - { - case PropType::Prop_Int: - { - pCallBack->PushCell(pInfo->objectID); - pCallBack->PushString(pInfo->pVar->GetName()); - pCallBack->PushCell(pInfo->iLastValue); - pCallBack->PushCell(*(int *)pNewValue); - pCallBack->PushCell(pInfo->Element); - pCallBack->Execute(0); - } - break; - case PropType::Prop_Float: - { - pCallBack->PushCell(pInfo->objectID); - pCallBack->PushString(pInfo->pVar->GetName()); - pCallBack->PushFloat(pInfo->flLastValue); - pCallBack->PushFloat(*(float *)pNewValue); - pCallBack->PushCell(pInfo->Element); - pCallBack->Execute(0); - } - break; - case PropType::Prop_String: - { - pCallBack->PushCell(pInfo->objectID); - pCallBack->PushString(pInfo->pVar->GetName()); - pCallBack->PushString(pInfo->cLastValue); - pCallBack->PushString((char *)pNewValue); - pCallBack->PushCell(pInfo->Element); - pCallBack->Execute(0); - } - break; - case PropType::Prop_Vector: - { - cell_t vector[2][3]; - Vector * pVec = (Vector *)pNewValue; - vector[0][0] = sp_ftoc(pVec->x); - vector[0][1] = sp_ftoc(pVec->y); - vector[0][2] = sp_ftoc(pVec->z); - vector[1][0] = sp_ftoc(pInfo->vecLastValue.x); - vector[1][1] = sp_ftoc(pInfo->vecLastValue.y); - vector[1][2] = sp_ftoc(pInfo->vecLastValue.z); - pCallBack->PushCell(pInfo->objectID); - pCallBack->PushString(pInfo->pVar->GetName()); - pCallBack->PushArray(vector[1], 3); - pCallBack->PushArray(vector[0], 3); - pCallBack->PushCell(pInfo->Element); - pCallBack->Execute(0); - } - break; - } - } - break; - } - } + int index = gamehelpers->EntityToBCompatRef(pEnt); + g_pSendPropHookManager->UnhookEntityAll(index); } -void CallChangeGamerulesCallbacks(PropChangeHookGamerules * pInfo, void * pOldValue, void * pNewValue) +void SendProxyManager::OnClientDisconnected(int client) { - for (int i = 0; i < pInfo->vCallbacksInfo->Count(); i++) - { - auto sCallback = (*pInfo->vCallbacksInfo)[i]; - switch (sCallback.iCallbackType) - { - case CallBackType::Callback_CPPCallbackInterface: - { - ISendProxyChangeCallbacks * pCallbacks = (ISendProxyChangeCallbacks *)sCallback.pCallback; - pCallbacks->OnGamerulesPropChange(pInfo->pVar, pNewValue, pOldValue, pInfo->PropType, pInfo->Element); - } - break; - case CallBackType::Callback_PluginFunction: - { - IPluginFunction * pCallBack = (IPluginFunction *)sCallback.pCallback; - switch (pInfo->PropType) - { - case PropType::Prop_Int: - { - pCallBack->PushString(pInfo->pVar->GetName()); - pCallBack->PushCell(pInfo->iLastValue); - pCallBack->PushCell(*(int *)pNewValue); - pCallBack->PushCell(pInfo->Element); - pCallBack->Execute(0); - } - break; - case PropType::Prop_Float: - { - pCallBack->PushString(pInfo->pVar->GetName()); - pCallBack->PushFloat(pInfo->flLastValue); - pCallBack->PushFloat(*(float *)pNewValue); - pCallBack->PushCell(pInfo->Element); - pCallBack->Execute(0); - } - break; - case PropType::Prop_String: - { - pCallBack->PushString(pInfo->pVar->GetName()); - pCallBack->PushString(pInfo->cLastValue); - pCallBack->PushString((char *)pNewValue); - pCallBack->PushCell(pInfo->Element); - pCallBack->Execute(0); - } - break; - case PropType::Prop_Vector: - { - cell_t vector[2][3]; - Vector * pVec = (Vector *)pNewValue; - vector[0][0] = sp_ftoc(pVec->x); - vector[0][1] = sp_ftoc(pVec->y); - vector[0][2] = sp_ftoc(pVec->z); - vector[1][0] = sp_ftoc(pInfo->vecLastValue.x); - vector[1][1] = sp_ftoc(pInfo->vecLastValue.y); - vector[1][2] = sp_ftoc(pInfo->vecLastValue.z); - pCallBack->PushString(pInfo->pVar->GetName()); - pCallBack->PushArray(vector[1], 3); - pCallBack->PushArray(vector[0], 3); - pCallBack->PushCell(pInfo->Element); - pCallBack->Execute(0); - } - break; - } - } - break; - } - } + g_pSendPropHookManager->UnhookEntityAll(client); } -//Proxy - -bool CallInt(SendPropHook &hook, int *ret, int iElement) +void SendProxyManager::OnCoreMapStart(edict_t * pEdictList, int edictCount, int clientMax) { - if (g_iCurrentClientIndexInLoop == -1) - return false; - - AUTO_LOCK_FM(g_WorkMutex); - - if (!hook.pVar->IsInsideArray()) - iElement = hook.Element; - - switch (hook.sCallbackInfo.iCallbackType) - { - case CallBackType::Callback_PluginFunction: - { - IPluginFunction *callback = (IPluginFunction *)hook.sCallbackInfo.pCallback; - cell_t value = *ret; - cell_t result = Pl_Continue; - callback->PushCell(hook.objectID); - callback->PushString(hook.pVar->GetName()); - callback->PushCellByRef(&value); - callback->PushCell(iElement); - callback->PushCell(g_iCurrentClientIndexInLoop + 1); - callback->Execute(&result); - if (result == Pl_Changed) - { - *ret = value; - return true; - } - break; - } - case CallBackType::Callback_CPPCallbackInterface: - { - ISendProxyCallbacks * pCallbacks = (ISendProxyCallbacks *)hook.sCallbackInfo.pCallback; - int iValue = *ret; - bool bChange = pCallbacks->OnEntityPropProxyFunctionCalls(gameents->EdictToBaseEntity(hook.pEnt), hook.pVar, (CBasePlayer *)gamehelpers->ReferenceToEntity(g_iCurrentClientIndexInLoop + 1), (void *)&iValue, hook.PropType, iElement); - if (bChange) - { - *ret = iValue; - return true; - } - break; - } - } - return false; } -bool CallIntGamerules(SendPropHookGamerules &hook, int *ret, int iElement) +void SendProxyManager::OnCoreMapEnd() { - if (g_iCurrentClientIndexInLoop == -1) - return false; - - AUTO_LOCK_FM(g_WorkMutex); - - if (!hook.pVar->IsInsideArray()) - iElement = hook.Element; - - switch (hook.sCallbackInfo.iCallbackType) - { - case CallBackType::Callback_PluginFunction: - { - IPluginFunction *callback = (IPluginFunction *)hook.sCallbackInfo.pCallback; - cell_t value = *ret; - cell_t result = Pl_Continue; - callback->PushString(hook.pVar->GetName()); - callback->PushCellByRef(&value); - callback->PushCell(iElement); - callback->PushCell(g_iCurrentClientIndexInLoop + 1); - callback->Execute(&result); - if (result == Pl_Changed) - { - *ret = value; - return true; - } - break; - } - case CallBackType::Callback_CPPCallbackInterface: - { - ISendProxyCallbacks * pCallbacks = (ISendProxyCallbacks *)hook.sCallbackInfo.pCallback; - int iValue = *ret; - bool bChange = pCallbacks->OnGamerulesPropProxyFunctionCalls(hook.pVar, (CBasePlayer *)gamehelpers->ReferenceToEntity(g_iCurrentClientIndexInLoop + 1), (void *)&iValue, hook.PropType, iElement); - if (bChange) - { - *ret = iValue; - return true; - } - break; - } - } - return false; + g_pSendPropHookManager->Clear(); } -bool CallFloat(SendPropHook &hook, float *ret, int iElement) +bool SendProxyManager::SDK_OnMetamodLoad(ISmmAPI *ismm, char *error, size_t maxlen, bool late) { - if (g_iCurrentClientIndexInLoop == -1) - return false; + GET_V_IFACE_ANY(GetEngineFactory, g_pCVar, ICvar, CVAR_INTERFACE_VERSION); - AUTO_LOCK_FM(g_WorkMutex); + gpGlobals = ismm->GetCGlobals(); - if (!hook.pVar->IsInsideArray()) - iElement = hook.Element; - - switch (hook.sCallbackInfo.iCallbackType) - { - case CallBackType::Callback_PluginFunction: - { - IPluginFunction *callback = (IPluginFunction *)hook.sCallbackInfo.pCallback; - float value = *ret; - cell_t result = Pl_Continue; - callback->PushCell(hook.objectID); - callback->PushString(hook.pVar->GetName()); - callback->PushFloatByRef(&value); - callback->PushCell(iElement); - callback->PushCell(g_iCurrentClientIndexInLoop + 1); - callback->Execute(&result); - if (result == Pl_Changed) - { - *ret = value; - return true; - } - break; - } - case CallBackType::Callback_CPPCallbackInterface: - { - ISendProxyCallbacks * pCallbacks = (ISendProxyCallbacks *)hook.sCallbackInfo.pCallback; - float flValue = *ret; - bool bChange = pCallbacks->OnEntityPropProxyFunctionCalls(gameents->EdictToBaseEntity(hook.pEnt), hook.pVar, (CBasePlayer *)gamehelpers->ReferenceToEntity(g_iCurrentClientIndexInLoop + 1), (void *)&flValue, hook.PropType, iElement); - if (bChange) - { - *ret = flValue; - return true; - } - break; - } - } - return false; -} - -bool CallFloatGamerules(SendPropHookGamerules &hook, float *ret, int iElement) -{ - if (g_iCurrentClientIndexInLoop == -1) - return false; - - AUTO_LOCK_FM(g_WorkMutex); - - if (!hook.pVar->IsInsideArray()) - iElement = hook.Element; - - switch (hook.sCallbackInfo.iCallbackType) - { - case CallBackType::Callback_PluginFunction: - { - IPluginFunction *callback = (IPluginFunction *)hook.sCallbackInfo.pCallback; - float value = *ret; - cell_t result = Pl_Continue; - callback->PushString(hook.pVar->GetName()); - callback->PushFloatByRef(&value); - callback->PushCell(iElement); - callback->PushCell(g_iCurrentClientIndexInLoop + 1); - callback->Execute(&result); - if (result == Pl_Changed) - { - *ret = value; - return true; - } - break; - } - case CallBackType::Callback_CPPCallbackInterface: - { - ISendProxyCallbacks * pCallbacks = (ISendProxyCallbacks *)hook.sCallbackInfo.pCallback; - float flValue = *ret; - bool bChange = pCallbacks->OnGamerulesPropProxyFunctionCalls(hook.pVar, (CBasePlayer *)gamehelpers->ReferenceToEntity(g_iCurrentClientIndexInLoop + 1), (void *)&flValue, hook.PropType, iElement); - if (bChange) - { - *ret = flValue; - return true; - } - break; - } - } - return false; -} - -bool CallString(SendPropHook &hook, char **ret, int iElement) -{ - if (g_iCurrentClientIndexInLoop == -1) - return false; - - AUTO_LOCK_FM(g_WorkMutex); - - if (!hook.pVar->IsInsideArray()) - iElement = hook.Element; - - static char value[4096]; - switch (hook.sCallbackInfo.iCallbackType) - { - case CallBackType::Callback_PluginFunction: - { - IPluginFunction *callback = (IPluginFunction *)hook.sCallbackInfo.pCallback; - strncpynull(value, *ret, 4096); - cell_t result = Pl_Continue; - callback->PushCell(hook.objectID); - callback->PushString(hook.pVar->GetName()); - callback->PushStringEx(value, 4096, SM_PARAM_STRING_UTF8 | SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK); - callback->PushCell(iElement); - callback->PushCell(g_iCurrentClientIndexInLoop + 1); - callback->Execute(&result); - if (result == Pl_Changed) - { - *ret = value; - return true; - } - break; - } - case CallBackType::Callback_CPPCallbackInterface: - { - ISendProxyCallbacks * pCallbacks = (ISendProxyCallbacks *)hook.sCallbackInfo.pCallback; - strncpynull(value, *ret, 4096); - bool bChange = pCallbacks->OnEntityPropProxyFunctionCalls(gameents->EdictToBaseEntity(hook.pEnt), hook.pVar, (CBasePlayer *)gamehelpers->ReferenceToEntity(g_iCurrentClientIndexInLoop + 1), (void *)value, hook.PropType, iElement); - if (bChange) - { - *ret = value; - return true; - } - break; - } - } - return false; -} - -bool CallStringGamerules(SendPropHookGamerules &hook, char **ret, int iElement) -{ - if (g_iCurrentClientIndexInLoop == -1) + sv_parallel_packentities = cvar->FindVar("sv_parallel_packentities"); + if (sv_parallel_packentities == nullptr) return false; - AUTO_LOCK_FM(g_WorkMutex); - - if (!hook.pVar->IsInsideArray()) - iElement = hook.Element; - - static char value[4096]; - switch (hook.sCallbackInfo.iCallbackType) - { - case CallBackType::Callback_PluginFunction: - { - void *pGamerules = g_pSDKTools->GetGameRules(); - if(!pGamerules) - { - g_pSM->LogError(myself, "CRITICAL ERROR: Could not get gamerules pointer!"); - } - - IPluginFunction *callback = (IPluginFunction *)hook.sCallbackInfo.pCallback; - strncpynull(value, *ret, 4096); - cell_t result = Pl_Continue; - callback->PushString(hook.pVar->GetName()); - callback->PushStringEx(value, 4096, SM_PARAM_STRING_UTF8 | SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK); - callback->PushCell(iElement); - callback->PushCell(g_iCurrentClientIndexInLoop + 1); - callback->Execute(&result); - if (result == Pl_Changed) - { - *ret = value; - return true; - } - break; - } - case CallBackType::Callback_CPPCallbackInterface: - { - void * pGamerules = g_pSDKTools->GetGameRules(); - if(!pGamerules) - return false; - ISendProxyCallbacks * pCallbacks = (ISendProxyCallbacks *)hook.sCallbackInfo.pCallback; - strncpynull(value, *ret, 4096); - bool bChange = pCallbacks->OnGamerulesPropProxyFunctionCalls(hook.pVar, (CBasePlayer *)gamehelpers->ReferenceToEntity(g_iCurrentClientIndexInLoop + 1), (void *)value, hook.PropType, iElement); - if (bChange) - { - *ret = value; - return true; - } - break; - } - } - return false; + return true; } -bool CallVector(SendPropHook &hook, Vector &vec, int iElement) +void SendProxyManager::OnPluginUnloaded(IPlugin * plugin) { - if (g_iCurrentClientIndexInLoop == -1) - return false; - - AUTO_LOCK_FM(g_WorkMutex); - - if (!hook.pVar->IsInsideArray()) - iElement = hook.Element; - - switch (hook.sCallbackInfo.iCallbackType) - { - case CallBackType::Callback_PluginFunction: - { - IPluginFunction *callback = (IPluginFunction *)hook.sCallbackInfo.pCallback; - - cell_t vector[3]; - vector[0] = sp_ftoc(vec.x); - vector[1] = sp_ftoc(vec.y); - vector[2] = sp_ftoc(vec.z); - - cell_t result = Pl_Continue; - callback->PushCell(hook.objectID); - callback->PushString(hook.pVar->GetName()); - callback->PushArray(vector, 3, SM_PARAM_COPYBACK); - callback->PushCell(iElement); - callback->PushCell(g_iCurrentClientIndexInLoop + 1); - callback->Execute(&result); - if (result == Pl_Changed) - { - vec.x = sp_ctof(vector[0]); - vec.y = sp_ctof(vector[1]); - vec.z = sp_ctof(vector[2]); - return true; - } - break; - } - case CallBackType::Callback_CPPCallbackInterface: - { - ISendProxyCallbacks * pCallbacks = (ISendProxyCallbacks *)hook.sCallbackInfo.pCallback; - Vector vNewVec(vec.x, vec.y, vec.z); - bool bChange = pCallbacks->OnGamerulesPropProxyFunctionCalls(hook.pVar, (CBasePlayer *)gamehelpers->ReferenceToEntity(g_iCurrentClientIndexInLoop + 1), (void *)&vNewVec, hook.PropType, iElement); - if (bChange) - { - vec.x = vNewVec.x; - vec.y = vNewVec.y; - vec.z = vNewVec.z; - return true; - } - break; - } - } - return false; + g_pSendPropHookManager->OnPluginUnloaded(plugin); } -bool CallVectorGamerules(SendPropHookGamerules &hook, Vector &vec, int iElement) +static CBaseEntity *FindEntityByNetClass(int start, const char *classname) { - if (g_iCurrentClientIndexInLoop == -1) - return false; - - AUTO_LOCK_FM(g_WorkMutex); - - if (!hook.pVar->IsInsideArray()) - iElement = hook.Element; - - switch (hook.sCallbackInfo.iCallbackType) + int maxEntities = gpGlobals->maxEntities; + for (int i = start; i < maxEntities; i++) { - case CallBackType::Callback_PluginFunction: + CBaseEntity *pEntity = gamehelpers->ReferenceToEntity(i); + if (pEntity == nullptr) { - IPluginFunction *callback = (IPluginFunction *)hook.sCallbackInfo.pCallback; - - cell_t vector[3]; - vector[0] = sp_ftoc(vec.x); - vector[1] = sp_ftoc(vec.y); - vector[2] = sp_ftoc(vec.z); - - cell_t result = Pl_Continue; - callback->PushString(hook.pVar->GetName()); - callback->PushArray(vector, 3, SM_PARAM_COPYBACK); - callback->PushCell(iElement); - callback->PushCell(g_iCurrentClientIndexInLoop + 1); - callback->Execute(&result); - if (result == Pl_Changed) - { - vec.x = sp_ctof(vector[0]); - vec.y = sp_ctof(vector[1]); - vec.z = sp_ctof(vector[2]); - return true; - } - break; - } - case CallBackType::Callback_CPPCallbackInterface: - { - ISendProxyCallbacks * pCallbacks = (ISendProxyCallbacks *)hook.sCallbackInfo.pCallback; - Vector vNewVec(vec.x, vec.y, vec.z); - bool bChange = pCallbacks->OnGamerulesPropProxyFunctionCalls(hook.pVar, (CBasePlayer *)gamehelpers->ReferenceToEntity(g_iCurrentClientIndexInLoop + 1), (void *)&vNewVec, hook.PropType, iElement); - if (bChange) - { - vec.x = vNewVec.x; - vec.y = vNewVec.y; - vec.z = vNewVec.z; - return true; - } - break; + continue; } - } - return false; -} -void GlobalProxy(const SendProp *pProp, const void *pStructBase, const void * pData, DVariant *pOut, int iElement, int objectID) -{ - edict_t * pEnt = gamehelpers->EdictOfIndex(objectID); - bool bHandled = false; - for (int i = 0; i < g_Hooks.Count(); i++) - { - if (g_Hooks[i].objectID == objectID && g_Hooks[i].pVar == pProp && pEnt == g_Hooks[i].pEnt && (!pProp->IsInsideArray() || g_Hooks[i].Element == iElement)) + ServerClass *pServerClass = gamehelpers->FindEntityServerClass(pEntity); + if (pServerClass == nullptr) { - switch (g_Hooks[i].PropType) - { - case PropType::Prop_Int: - { - int result = *(int *)pData; - - if (CallInt(g_Hooks[i], &result, iElement)) - { - long data = result; - g_Hooks[i].pRealProxy(pProp, pStructBase, &data, pOut, iElement, objectID); - return; // If somebody already handled this call, do not call other hooks for this entity & prop - } - else - { - g_Hooks[i].pRealProxy(pProp, pStructBase, pData, pOut, iElement, objectID); - } - bHandled = true; - continue; - } - case PropType::Prop_Float: - { - float result = *(float *)pData; - - if (CallFloat(g_Hooks[i], &result, iElement)) - { - g_Hooks[i].pRealProxy(pProp, pStructBase, &result, pOut, iElement, objectID); - return; // If somebody already handled this call, do not call other hooks for this entity & prop - } - else - { - g_Hooks[i].pRealProxy(pProp, pStructBase, pData, pOut, iElement, objectID); - } - bHandled = true; - continue; - } - case PropType::Prop_String: - { - const char * result = (char*)pData; - if (!result) //there can be null; - result = ""; - - if (CallString(g_Hooks[i], const_cast(&result), iElement)) - { - g_Hooks[i].pRealProxy(pProp, pStructBase, result, pOut, iElement, objectID); - return; // If somebody already handled this call, do not call other hooks for this entity & prop - } - else - { - g_Hooks[i].pRealProxy(pProp, pStructBase, pData, pOut, iElement, objectID); - } - bHandled = true; - continue; - } - case PropType::Prop_Vector: - { - Vector result = *(Vector *)pData; - - if (CallVector(g_Hooks[i], result, iElement)) - { - g_Hooks[i].pRealProxy(pProp, pStructBase, &result, pOut, iElement, objectID); - return; // If somebody already handled this call, do not call other hooks for this entity & prop - } - else - { - g_Hooks[i].pRealProxy(pProp, pStructBase, pData, pOut, iElement, objectID); - } - bHandled = true; - continue; - } - default: rootconsole->ConsolePrint("%s: SendProxy report: Unknown prop type (%s).", __func__, g_Hooks[i].pVar->GetName()); - } - } - } - if (!bHandled) - { - //perhaps we aren't hooked, but we can still find the real proxy for this prop - for (int i = 0; i < g_Hooks.Count(); i++) - { - if (g_Hooks[i].pVar == pProp) - { - g_Hooks[i].pRealProxy(pProp, pStructBase, pData, pOut, iElement, objectID); - return; - } + continue; } - g_pSM->LogError(myself, "CRITICAL: Proxy for unmanaged entity %d called for prop %s", objectID, pProp->GetName()); - } -} -void GlobalProxyGamerules(const SendProp *pProp, const void *pStructBase, const void * pData, DVariant *pOut, int iElement, int objectID) -{ - bool bHandled = false; - for (int i = 0; i < g_HooksGamerules.Count(); i++) - { - if (g_HooksGamerules[i].pVar == pProp && (!pProp->IsInsideArray() || g_HooksGamerules[i].Element == iElement)) + const char *name = pServerClass->GetName(); + if (!strcmp(name, classname)) { - switch (g_HooksGamerules[i].PropType) - { - case PropType::Prop_Int: - { - int result = *(int *)pData; - - if (CallIntGamerules(g_HooksGamerules[i], &result, iElement)) - { - long data = result; - g_HooksGamerules[i].pRealProxy(pProp, pStructBase, &data, pOut, iElement, objectID); - return; // If somebody already handled this call, do not call other hooks for this entity & prop - } - else - { - g_HooksGamerules[i].pRealProxy(pProp, pStructBase, pData, pOut, iElement, objectID); - } - bHandled = true; - continue; - } - case PropType::Prop_Float: - { - float result = *(float *)pData; - - if (CallFloatGamerules(g_HooksGamerules[i], &result, iElement)) - { - g_HooksGamerules[i].pRealProxy(pProp, pStructBase, &result, pOut, iElement, objectID); - return; // If somebody already handled this call, do not call other hooks for this entity & prop - } - else - { - g_HooksGamerules[i].pRealProxy(pProp, pStructBase, pData, pOut, iElement, objectID); - } - bHandled = true; - continue; - } - case PropType::Prop_String: - { - const char *result = (char*)pData; //We need to use const because of C++11 restriction - if (!result) //there can be null; - result = ""; - - if (CallStringGamerules(g_HooksGamerules[i], const_cast(&result), iElement)) - { - g_HooksGamerules[i].pRealProxy(pProp, pStructBase, result, pOut, iElement, objectID); - return; // If somebody already handled this call, do not call other hooks for this entity & prop - } - else - { - g_HooksGamerules[i].pRealProxy(pProp, pStructBase, pData, pOut, iElement, objectID); - } - bHandled = true; - continue; - } - case PropType::Prop_Vector: - { - Vector result = *(Vector *)pData; - - if (CallVectorGamerules(g_HooksGamerules[i], result, iElement)) - { - g_HooksGamerules[i].pRealProxy(pProp, pStructBase, &result, pOut, iElement, objectID); - return; // If somebody already handled this call, do not call other hooks for this entity & prop - } - else - { - g_HooksGamerules[i].pRealProxy(pProp, pStructBase, pData, pOut, iElement, objectID); - } - bHandled = true; - continue; - } - default: rootconsole->ConsolePrint("%s: SendProxy report: Unknown prop type (%s).", __func__, g_HooksGamerules[i].pVar->GetName()); - } + return pEntity; } } - if (!bHandled) - { - //perhaps we aren't hooked, but we can still find the real proxy for this prop - for (int i = 0; i < g_HooksGamerules.Count(); i++) - { - if (g_HooksGamerules[i].pVar == pProp) - { - g_HooksGamerules[i].pRealProxy(pProp, pStructBase, pData, pOut, iElement, objectID); - return; - } - } - g_pSM->LogError(myself, "CRITICAL: Proxy for unmanaged gamerules called for prop %s", pProp->GetName()); - } -} - -//help -CBaseEntity * FindEntityByServerClassname(int iStart, const char * pServerClassName) -{ - if (iStart >= g_iEdictCount) - return nullptr; - for (int i = iStart; i < g_iEdictCount; i++) - { - CBaseEntity * pEnt = gamehelpers->ReferenceToEntity(i); - if (!pEnt) - continue; - IServerNetworkable * pNetworkable = ((IServerUnknown *)pEnt)->GetNetworkable(); - if (!pNetworkable) - continue; - const char * pName = pNetworkable->GetServerClass()->GetName(); - if (pName && !strcmp(pName, pServerClassName)) - return pEnt; - } return nullptr; } -bool IsPropValid(SendProp * pProp, PropType iType) +CBaseEntity* GetGameRulesProxyEnt() { - switch (iType) + static cell_t proxyEntRef = -1; + CBaseEntity *pProxy; + if (proxyEntRef == -1 || (pProxy = gamehelpers->ReferenceToEntity(proxyEntRef)) == NULL) { - case PropType::Prop_Int: - if (pProp->GetType() != DPT_Int) - return false; - return true; - case PropType::Prop_Float: - { - if (pProp->GetType() != DPT_Float) - return false; - return true; - } - case PropType::Prop_String: - { - if (pProp->GetType() != DPT_String) - return false; - return true; - } - case PropType::Prop_Vector: - { - if (pProp->GetType() != DPT_Vector) - return false; - return true; - } + pProxy = FindEntityByNetClass(playerhelpers->GetMaxClients(), g_szGameRulesProxy.c_str()); + if (pProxy) + proxyEntRef = gamehelpers->EntityToReference(pProxy); } - return false; + + return pProxy; } - -char * strncpynull(char * pDestination, const char * pSource, size_t szCount) -{ - strncpy(pDestination, pSource, szCount); - pDestination[szCount - 1] = 0; - return pDestination; -} \ No newline at end of file diff --git a/extension/extension.h b/extension/extension.h index 1d7849d..695f7c2 100644 --- a/extension/extension.h +++ b/extension/extension.h @@ -32,13 +32,6 @@ #ifndef _EXTENSION_H_INC_ #define _EXTENSION_H_INC_ - /* - TODO: - Implement interface: - Add common function for prop change hooks - Add remove listeners for prop change hooks - */ - #include "smsdk_ext.h" #include #include @@ -51,138 +44,17 @@ #include #include "wrappers.h" -#define GET_CONVAR(name) \ - name = g_pCVar->FindVar(#name); \ - if (name == nullptr) { \ - if (error != nullptr && maxlen != 0) { \ - ismm->Format(error, maxlen, "Could not find ConVar: " #name); \ - } \ - return false; \ - } - -class IServerGameEnts; - -void GlobalProxy(const SendProp *pProp, const void *pStructBase, const void* pData, DVariant *pOut, int iElement, int objectID); -void GlobalProxyGamerules(const SendProp *pProp, const void *pStructBase, const void* pData, DVariant *pOut, int iElement, int objectID); -bool IsPropValid(SendProp *, PropType); -char * strncpynull(char * pDestination, const char * pSource, size_t szCount); - -struct ListenerCallbackInfo -{ - IExtension * m_pExt; - IExtensionInterface * m_pExtAPI; - ISendProxyUnhookListener * m_pCallBack; -}; - -struct CallBackInfo -{ - CallBackInfo() { memset(this, 0, sizeof(CallBackInfo)); } - CallBackInfo(const CallBackInfo & rObj) { memcpy(this, &rObj, sizeof(CallBackInfo)); } - const CallBackInfo & operator=(const CallBackInfo & rObj) { return *(new CallBackInfo(rObj)); } - void * pOwner; //Pointer to plugin context or IExtension * - void * pCallback; - CallBackType iCallbackType; -}; - -struct SendPropHook -{ - SendPropHook() { vListeners = new CUtlVector(); } - SendPropHook(const SendPropHook & rObj) - { - memcpy(this, &rObj, sizeof(SendPropHook)); - vListeners = new CUtlVector(); - *vListeners = *rObj.vListeners; - } - ~SendPropHook() { delete vListeners; } - CallBackInfo sCallbackInfo; - SendProp * pVar; - edict_t * pEnt; - SendVarProxyFn pRealProxy; - int objectID; - PropType PropType; - int Offset; - int Element{0}; - CUtlVector * vListeners; -}; - -struct SendPropHookGamerules -{ - SendPropHookGamerules() { vListeners = new CUtlVector(); } - SendPropHookGamerules(const SendPropHookGamerules & rObj) - { - memcpy(this, &rObj, sizeof(SendPropHookGamerules)); - vListeners = new CUtlVector(); - *vListeners = *rObj.vListeners; - } - ~SendPropHookGamerules() { delete vListeners; } - CallBackInfo sCallbackInfo; - SendProp * pVar; - SendVarProxyFn pRealProxy; - PropType PropType; - int Element{0}; - CUtlVector * vListeners; -}; - -struct PropChangeHook -{ - PropChangeHook() { vCallbacksInfo = new CUtlVector(); } - PropChangeHook(const PropChangeHook & rObj) - { - memcpy(this, &rObj, sizeof(PropChangeHook)); - vCallbacksInfo = new CUtlVector(); - *vCallbacksInfo = *rObj.vCallbacksInfo; - } - ~PropChangeHook() { delete vCallbacksInfo; } - union //unfortunately we MUST use union instead of std::variant cuz we should prevent libstdc++ linking in linux =| - { - int iLastValue; - float flLastValue; - Vector vecLastValue; - char cLastValue[4096]; - }; - SendProp * pVar; - PropType PropType; - unsigned int Offset; - int objectID; - int Element{0}; - CUtlVector * vCallbacksInfo; -}; - -struct PropChangeHookGamerules -{ - PropChangeHookGamerules() { vCallbacksInfo = new CUtlVector(); } - PropChangeHookGamerules(const PropChangeHookGamerules & rObj) - { - memcpy(this, &rObj, sizeof(PropChangeHookGamerules)); - vCallbacksInfo = new CUtlVector(); - *vCallbacksInfo = *rObj.vCallbacksInfo; - } - ~PropChangeHookGamerules() { delete vCallbacksInfo; } - union //unfortunately we MUST use union instead of std::variant cuz we should prevent libstdc++ linking in linux =| - { - int iLastValue; - float flLastValue; - Vector vecLastValue; - char cLastValue[4096]; - }; - SendProp * pVar; - PropType PropType; - unsigned int Offset; - int Element{0}; - CUtlVector * vCallbacksInfo; -}; - class SendProxyManager : public SDKExtension, public IPluginsListener, public IConCommandBaseAccessor, + public IClientListener, public ISMEntityListener { -public: //sm +public: virtual bool SDK_OnLoad(char * error, size_t maxlength, bool late); virtual void SDK_OnUnload(); virtual void SDK_OnAllLoaded(); - bool RegisterConCommandBase(ConCommandBase* pVar) override; /** * @brief Asks the extension whether it's safe to remove an external @@ -221,48 +93,34 @@ class SendProxyManager : */ bool QueryRunning(char* error, size_t maxlength) override; - virtual void OnCoreMapEnd(); - virtual void OnCoreMapStart(edict_t *, int, int); - -public: //other - virtual void OnPluginUnloaded(IPlugin * plugin); - //returns true upon success - bool AddHookToList(SendPropHook hook); - bool AddHookToListGamerules(SendPropHookGamerules hook); - - //returns false if prop type not supported or entity is invalid - bool AddChangeHookToList(PropChangeHook sHook, CallBackInfo * pInfo); - bool AddChangeHookToListGamerules(PropChangeHookGamerules sHook, CallBackInfo * pInfo); - - void UnhookProxy(int i); - void UnhookProxyGamerules(int i); - - void UnhookChange(int i, CallBackInfo * pInfo); - void UnhookChangeGamerules(int i, CallBackInfo * pInfo); -public: // ISMEntityListener - virtual void OnEntityDestroyed(CBaseEntity * pEntity); public: #if defined SMEXT_CONF_METAMOD virtual bool SDK_OnMetamodLoad(ISmmAPI * ismm, char * error, size_t maxlen, bool late); //virtual bool SDK_OnMetamodUnload(char *error, size_t maxlength); //virtual bool SDK_OnMetamodPauseChange(bool paused, char *error, size_t maxlength); #endif + +public: //SDKExtension + void OnCoreMapStart(edict_t *, int, int) override; + void OnCoreMapEnd() override; + +public: //IPluginsListener + void OnPluginUnloaded(IPlugin * plugin) override; + +public: //IClientListener + void OnClientDisconnected(int client) override; + +public: //ISMEntityListener + void OnEntityDestroyed(CBaseEntity *pEntity) override; + +public: //IConCommandBaseAccessor + bool RegisterConCommandBase(ConCommandBase* pVar) override; }; extern SendProxyManager g_SendProxyManager; -extern IServerGameEnts * gameents; -extern CUtlVector g_Hooks; -extern CUtlVector g_HooksGamerules; -extern CUtlVector g_ChangeHooks; -extern CUtlVector g_ChangeHooksGamerules; -extern const char * g_szGameRulesProxy; -constexpr int g_iEdictCount = 2048; //default value, we do not need to get it manually cuz it is constant -#if SOURCE_ENGINE == SE_LEFT4DEAD || SOURCE_ENGINE == SE_LEFT4DEAD2 -constexpr int g_iMaxPlayers = 32; -#else -constexpr int g_iMaxPlayers = SM_MAXPLAYERS; -#endif -extern ISDKTools * g_pSDKTools; -extern void * g_pGameRules; +extern ConVar *sv_parallel_packentities; +extern CFrameSnapshotManager *framesnapshotmanager; + +CBaseEntity *GetGameRulesProxyEnt(); #endif // _EXTENSION_H_INC_ diff --git a/extension/interfaceimpl.cpp b/extension/interfaceimpl.cpp deleted file mode 100644 index 3f34731..0000000 --- a/extension/interfaceimpl.cpp +++ /dev/null @@ -1,803 +0,0 @@ -/** - * vim: set ts=4 : - * ============================================================================= - * SendVar Proxy Manager - * Copyright (C) 2011-2019 Afronanny & AlliedModders community. All rights reserved. - * ============================================================================= - * - * 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 . - * - * As a special exception, AlliedModders LLC gives you permission to link the - * code of this program (as well as its derivative works) to "Half-Life 2," the - * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software - * by the Valve Corporation. You must obey the GNU General Public License in - * all respects for all other code used. Additionally, AlliedModders LLC grants - * this exception to all derivative works. AlliedModders LLC defines further - * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), - * or . - * - * Version: $Id$ - */ - -#include "interfaceimpl.h" - -SH_DECL_HOOK0_void(IExtensionInterface, OnExtensionUnload, SH_NOATTRIB, false); - -void Hook_OnExtensionUnload() -{ - IExtensionInterface * pExtAPI = META_IFACEPTR(IExtensionInterface); - for (int i = 0; i < g_Hooks.Count(); i++) - { - //remove all listeners for this extension - if (g_Hooks[i].vListeners->Count()) - for (int j = 0; j < g_Hooks[i].vListeners->Count(); j++) - { - ListenerCallbackInfo info = (*g_Hooks[i].vListeners)[j]; - if (info.m_pExtAPI == pExtAPI) - g_Hooks[i].vListeners->Remove(j); - } - //remove hook for this extension - if (g_Hooks[i].sCallbackInfo.iCallbackType == CallBackType::Callback_CPPCallbackInterface && static_cast(g_Hooks[i].sCallbackInfo.pOwner)->GetAPI() == pExtAPI) - g_SendProxyManager.UnhookProxy(i); - } - for (int i = 0; i < g_HooksGamerules.Count(); i++) - { - //remove all listeners for this extension - if (g_HooksGamerules[i].vListeners->Count()) - for (int j = 0; j < g_HooksGamerules[i].vListeners->Count(); j++) - { - ListenerCallbackInfo info = (*g_HooksGamerules[i].vListeners)[j]; - if (info.m_pExtAPI == pExtAPI) - g_HooksGamerules[i].vListeners->Remove(j); - } - //remove hook for this extension - if (g_HooksGamerules[i].sCallbackInfo.iCallbackType == CallBackType::Callback_CPPCallbackInterface && static_cast(g_HooksGamerules[i].sCallbackInfo.pOwner)->GetAPI() == pExtAPI) - g_SendProxyManager.UnhookProxyGamerules(i); - } - SH_REMOVE_HOOK(IExtensionInterface, OnExtensionUnload, pExtAPI, SH_STATIC(Hook_OnExtensionUnload), false); - RETURN_META(MRES_IGNORED); -} - -void HookExtensionUnload(IExtension * pExt) -{ - if (!pExt) - return; - - bool bHookedAlready = false; - for (int i = 0; i < g_Hooks.Count(); i++) - { - if (g_Hooks[i].sCallbackInfo.pOwner == pExt) - { - bHookedAlready = true; - break; - } - else if (g_Hooks[i].vListeners->Count()) - for (int j = 0; j < g_Hooks[i].vListeners->Count(); j++) - { - ListenerCallbackInfo info = (*g_Hooks[i].vListeners)[j]; - if (info.m_pExtAPI == pExt->GetAPI()) - { - bHookedAlready = true; - break; - } - } - } - if (!bHookedAlready) - for (int i = 0; i < g_HooksGamerules.Count(); i++) - { - if (g_HooksGamerules[i].sCallbackInfo.pOwner == pExt) - { - bHookedAlready = true; - break; - } - else if (g_HooksGamerules[i].vListeners->Count()) - for (int j = 0; j < g_HooksGamerules[i].vListeners->Count(); j++) - { - ListenerCallbackInfo info = (*g_HooksGamerules[i].vListeners)[j]; - if (info.m_pExtAPI == pExt->GetAPI()) - { - bHookedAlready = true; - break; - } - } - } - if (!bHookedAlready) //Hook only if needed! - SH_ADD_HOOK(IExtensionInterface, OnExtensionUnload, pExt->GetAPI(), SH_STATIC(Hook_OnExtensionUnload), false); -} - -void UnhookExtensionUnload(IExtension * pExt) -{ - if (!pExt) - return; - - bool bHaveHooks = false; - for (int i = 0; i < g_Hooks.Count(); i++) - { - if (g_Hooks[i].sCallbackInfo.pOwner == pExt) - { - bHaveHooks = true; - break; - } - else if (g_Hooks[i].vListeners->Count()) - for (int j = 0; j < g_Hooks[i].vListeners->Count(); j++) - { - ListenerCallbackInfo info = (*g_Hooks[i].vListeners)[j]; - if (info.m_pExtAPI == pExt->GetAPI()) - { - bHaveHooks = true; - break; - } - } - } - if (!bHaveHooks) - for (int i = 0; i < g_HooksGamerules.Count(); i++) - { - if (g_HooksGamerules[i].sCallbackInfo.pOwner == pExt) - { - bHaveHooks = true; - break; - } - else if (g_HooksGamerules[i].vListeners->Count()) - for (int j = 0; j < g_HooksGamerules[i].vListeners->Count(); j++) - { - ListenerCallbackInfo info = (*g_HooksGamerules[i].vListeners)[j]; - if (info.m_pExtAPI == pExt->GetAPI()) - { - bHaveHooks = true; - break; - } - } - } - - if (!bHaveHooks) //so, if there are active hooks, we shouldn't remove hook! - SH_REMOVE_HOOK(IExtensionInterface, OnExtensionUnload, pExt->GetAPI(), SH_STATIC(Hook_OnExtensionUnload), false); -} - -void CallListenersForHookID(int iHookID) -{ - SendPropHook Info = g_Hooks[iHookID]; - for (int i = 0; i < Info.vListeners->Count(); i++) - { - ListenerCallbackInfo sInfo = (*Info.vListeners)[i]; - sInfo.m_pCallBack->OnEntityPropHookRemoved(gameents->EdictToBaseEntity(Info.pEnt), Info.pVar, Info.PropType, Info.sCallbackInfo.iCallbackType, Info.sCallbackInfo.pCallback); - } -} - -void CallListenersForHookIDGamerules(int iHookID) -{ - SendPropHookGamerules Info = g_HooksGamerules[iHookID]; - for (int i = 0; i < Info.vListeners->Count(); i++) - { - ListenerCallbackInfo sInfo = (*Info.vListeners)[i]; - sInfo.m_pCallBack->OnGamerulesPropHookRemoved(Info.pVar, Info.PropType, Info.sCallbackInfo.iCallbackType, Info.sCallbackInfo.pCallback); - } -} - -//interface - -const char * SendProxyManagerInterfaceImpl::GetInterfaceName() { return SMINTERFACE_SENDPROXY_NAME; } -unsigned int SendProxyManagerInterfaceImpl::GetInterfaceVersion() { return SMINTERFACE_SENDPROXY_VERSION; } - -bool SendProxyManagerInterfaceImpl::HookProxy(IExtension * pExt, SendProp * pProp, CBaseEntity * pEntity, PropType iType, CallBackType iCallbackType, void * pCallback) -{ - if (!pEntity) - return false; - - edict_t * pEdict = gameents->BaseEntityToEdict(pEntity); - if (!pEdict || pEdict->IsFree()) - return false; - - if (!IsPropValid(pProp, iType)) - return false; - - SendPropHook hook; - hook.objectID = gamehelpers->IndexOfEdict(pEdict); - hook.sCallbackInfo.pCallback = pCallback; - hook.sCallbackInfo.iCallbackType = iCallbackType; - hook.sCallbackInfo.pOwner = (void *)pExt; - hook.PropType = iType; - hook.pEnt = pEdict; - hook.pVar = pProp; - bool bHookedAlready = false; - for (int i = 0; i < g_Hooks.Count(); i++) - { - if (g_Hooks[i].pVar == pProp) - { - hook.pRealProxy = g_Hooks[i].pRealProxy; - bHookedAlready = true; - break; - } - } - if (!bHookedAlready) - hook.pRealProxy = pProp->GetProxyFn(); - HookExtensionUnload(pExt); - if (g_SendProxyManager.AddHookToList(hook)) - { - if (!bHookedAlready) - pProp->SetProxyFn(GlobalProxy); - } - else - { - UnhookExtensionUnload(pExt); - return false; - } - return true; -} - -bool SendProxyManagerInterfaceImpl::HookProxy(IExtension * pExt, const char * pProp, CBaseEntity * pEntity, PropType iType, CallBackType iCallbackType, void * pCallback) -{ - if (!pProp || !*pProp) - return false; - if (!pEntity) - return false; - ServerClass * sc = ((IServerUnknown *)pEntity)->GetNetworkable()->GetServerClass(); - if (!sc) - return false; //we don't use exceptions, bad extensions may do not handle this and server will crashed, just return false - sm_sendprop_info_t info; - gamehelpers->FindSendPropInfo(sc->GetName(), pProp, &info); - SendProp * pSendProp = info.prop; - if (pSendProp) - return HookProxy(pExt, pSendProp, pEntity, iType, iCallbackType, pCallback); - return false; -} - -bool SendProxyManagerInterfaceImpl::HookProxyGamerules(IExtension * pExt, SendProp * pProp, PropType iType, CallBackType iCallbackType, void * pCallback) -{ - if (!IsPropValid(pProp, iType)) - return false; - - SendPropHookGamerules hook; - hook.sCallbackInfo.pCallback = pCallback; - hook.sCallbackInfo.iCallbackType = iCallbackType; - hook.sCallbackInfo.pOwner = (void *)pExt; - bool bHookedAlready = false; - for (int i = 0; i < g_HooksGamerules.Count(); i++) - { - if (g_HooksGamerules[i].pVar == pProp) - { - hook.pRealProxy = g_HooksGamerules[i].pRealProxy; - bHookedAlready = true; - break; - } - } - if (!bHookedAlready) - hook.pRealProxy = pProp->GetProxyFn(); - hook.PropType = iType; - hook.pVar = pProp; - sm_sendprop_info_t info; - gamehelpers->FindSendPropInfo(g_szGameRulesProxy, pProp->GetName(), &info); - - //if this prop has been hooked already, don't set the proxy again - HookExtensionUnload(pExt); - if (bHookedAlready) - { - if (g_SendProxyManager.AddHookToListGamerules(hook)) - return true; - UnhookExtensionUnload(pExt); - return false; - } - if (g_SendProxyManager.AddHookToListGamerules(hook)) - { - pProp->SetProxyFn(GlobalProxyGamerules); - return true; - } - UnhookExtensionUnload(pExt); - return false; -} - -bool SendProxyManagerInterfaceImpl::HookProxyGamerules(IExtension * pExt, const char * pProp, PropType iType, CallBackType iCallbackType, void * pCallback) -{ - if (!pProp || !*pProp) - return false; - sm_sendprop_info_t info; - gamehelpers->FindSendPropInfo(g_szGameRulesProxy, pProp, &info); - SendProp * pSendProp = info.prop; - if (pSendProp) - return HookProxyGamerules(pExt, pSendProp, iType, iCallbackType, pCallback); - return false; -} - -bool SendProxyManagerInterfaceImpl::UnhookProxy(IExtension * pExt, SendProp * pProp, CBaseEntity * pEntity, CallBackType iCallbackType, void * pCallback) -{ - const char * pPropName = pProp->GetName(); - return UnhookProxy(pExt, pPropName, pEntity, iCallbackType, pCallback); -} - -bool SendProxyManagerInterfaceImpl::UnhookProxy(IExtension * pExt, const char * pProp, CBaseEntity * pEntity, CallBackType iCallbackType, void * pCallback) -{ - if (!pProp || !*pProp) - return false; - edict_t * pEdict = gameents->BaseEntityToEdict(pEntity); - for (int i = 0; i < g_Hooks.Count(); i++) - if (g_Hooks[i].sCallbackInfo.pOwner == pExt /*Allow to extension remove only its hooks*/ && pEdict == g_Hooks[i].pEnt && g_Hooks[i].sCallbackInfo.iCallbackType == iCallbackType && !strcmp(g_Hooks[i].pVar->GetName(), pProp) && pCallback == g_Hooks[i].sCallbackInfo.pCallback) - { - g_SendProxyManager.UnhookProxy(i); - UnhookExtensionUnload(pExt); - return true; - } - return false; -} - -bool SendProxyManagerInterfaceImpl::UnhookProxyGamerules(IExtension * pExt, SendProp * pProp, CallBackType iCallbackType, void * pCallback) -{ - const char * pPropName = pProp->GetName(); - return UnhookProxyGamerules(pExt, pPropName, iCallbackType, pCallback); -} - -bool SendProxyManagerInterfaceImpl::UnhookProxyGamerules(IExtension * pExt, const char * pProp, CallBackType iCallbackType, void * pCallback) -{ - if (!pProp || !*pProp) - return false; - for (int i = 0; i < g_HooksGamerules.Count(); i++) - if (g_HooksGamerules[i].sCallbackInfo.pOwner == pExt /*Allow to extension remove only its hooks*/ && g_HooksGamerules[i].sCallbackInfo.iCallbackType == iCallbackType && !strcmp(g_HooksGamerules[i].pVar->GetName(), pProp) && pCallback == g_HooksGamerules[i].sCallbackInfo.pCallback) - { - g_SendProxyManager.UnhookProxyGamerules(i); - UnhookExtensionUnload(pExt); - return true; - } - return false; -} - -bool SendProxyManagerInterfaceImpl::AddUnhookListener(IExtension * pExt, SendProp * pProp, CBaseEntity * pEntity, CallBackType iCallbackType, void * pCallback, ISendProxyUnhookListener * pListener) -{ - const char * pPropName = pProp->GetName(); - return AddUnhookListener(pExt, pPropName, pEntity, iCallbackType, pCallback, pListener); -} - -bool SendProxyManagerInterfaceImpl::AddUnhookListener(IExtension * pExt, const char * pProp, CBaseEntity * pEntity, CallBackType iCallbackType, void * pCallback, ISendProxyUnhookListener * pListener) -{ - if (!pProp || !*pProp) - return false; - edict_t * pEdict = gameents->BaseEntityToEdict(pEntity); - for (int i = 0; i < g_Hooks.Count(); i++) - if (pEdict == g_Hooks[i].pEnt && g_Hooks[i].sCallbackInfo.iCallbackType == iCallbackType && !strcmp(g_Hooks[i].pVar->GetName(), pProp) && pCallback == g_Hooks[i].sCallbackInfo.pCallback) - { - ListenerCallbackInfo info; - info.m_pExt = pExt; - info.m_pExtAPI = pExt->GetAPI(); - info.m_pCallBack = pListener; - HookExtensionUnload(pExt); - g_Hooks[i].vListeners->AddToTail(info); - return true; - } - return false; -} - -bool SendProxyManagerInterfaceImpl::AddUnhookListenerGamerules(IExtension * pExt, SendProp * pProp, CallBackType iCallbackType, void * pCallback, ISendProxyUnhookListener * pListener) -{ - const char * pPropName = pProp->GetName(); - return AddUnhookListenerGamerules(pExt, pPropName, iCallbackType, pCallback, pListener); -} - -bool SendProxyManagerInterfaceImpl::AddUnhookListenerGamerules(IExtension * pExt, const char * pProp, CallBackType iCallbackType, void * pCallback, ISendProxyUnhookListener * pListener) -{ - if (!pProp || !*pProp) - return false; - for (int i = 0; i < g_HooksGamerules.Count(); i++) - if (g_HooksGamerules[i].sCallbackInfo.iCallbackType == iCallbackType && !strcmp(g_HooksGamerules[i].pVar->GetName(), pProp) && pCallback == g_HooksGamerules[i].sCallbackInfo.pCallback) - { - ListenerCallbackInfo info; - info.m_pExt = pExt; - info.m_pExtAPI = pExt->GetAPI(); - info.m_pCallBack = pListener; - HookExtensionUnload(pExt); - g_HooksGamerules[i].vListeners->AddToTail(info); - return true; - } - return false; -} - -bool SendProxyManagerInterfaceImpl::RemoveUnhookListener(IExtension * pExt, SendProp * pProp, CBaseEntity * pEntity, CallBackType iCallbackType, void * pCallback, ISendProxyUnhookListener * pListener) -{ - const char * pPropName = pProp->GetName(); - return RemoveUnhookListener(pExt, pPropName, pEntity, iCallbackType, pCallback, pListener); -} - -bool SendProxyManagerInterfaceImpl::RemoveUnhookListener(IExtension * pExt, const char * pProp, CBaseEntity * pEntity, CallBackType iCallbackType, void * pCallback, ISendProxyUnhookListener * pListener) -{ - if (!pProp || !*pProp) - return false; - - edict_t * pEdict = gameents->BaseEntityToEdict(pEntity); - for (int i = 0; i < g_Hooks.Count(); i++) - if (g_Hooks[i].pEnt == pEdict && g_Hooks[i].sCallbackInfo.iCallbackType == iCallbackType && !strcmp(g_Hooks[i].pVar->GetName(), pProp) && pCallback == g_Hooks[i].sCallbackInfo.pCallback) - { - for (int j = 0; j < g_Hooks[i].vListeners->Count(); j++) - { - ListenerCallbackInfo info = (*g_Hooks[i].vListeners)[j]; - if (info.m_pExt == pExt && info.m_pCallBack == pListener) - { - g_Hooks[i].vListeners->Remove(j); - UnhookExtensionUnload(pExt); - return true; - } - } - } - return false; -} - -bool SendProxyManagerInterfaceImpl::RemoveUnhookListenerGamerules(IExtension * pExt, SendProp * pProp, CallBackType iCallbackType, void * pCallback, ISendProxyUnhookListener * pListener) -{ - const char * pPropName = pProp->GetName(); - return RemoveUnhookListenerGamerules(pExt, pPropName, iCallbackType, pCallback, pListener); -} - -bool SendProxyManagerInterfaceImpl::RemoveUnhookListenerGamerules(IExtension * pExt, const char * pProp, CallBackType iCallbackType, void * pCallback, ISendProxyUnhookListener * pListener) -{ - if (!pProp || !*pProp) - return false; - - for (int i = 0; i < g_HooksGamerules.Count(); i++) - if (g_HooksGamerules[i].sCallbackInfo.iCallbackType == iCallbackType && !strcmp(g_HooksGamerules[i].pVar->GetName(), pProp) && pCallback == g_HooksGamerules[i].sCallbackInfo.pCallback) - { - for (int j = 0; j < g_HooksGamerules[i].vListeners->Count(); j++) - { - ListenerCallbackInfo info = (*g_HooksGamerules[i].vListeners)[j]; - if (info.m_pExt == pExt && info.m_pCallBack == pListener) - { - g_HooksGamerules[i].vListeners->Remove(j); - UnhookExtensionUnload(pExt); - return true; - } - } - } - return false; -} - -//same for the arrays - -bool SendProxyManagerInterfaceImpl::HookProxyArray(IExtension * pExt, SendProp * pProp, CBaseEntity * pEntity, PropType iType, int iElement, CallBackType iCallbackType, void * pCallback) -{ - if (!pEntity) - return false; - - edict_t * pEdict = gameents->BaseEntityToEdict(pEntity); - if (!pEdict || pEdict->IsFree()) - return false; - - SendTable * pSendTable = pProp->GetDataTable(); - if (!pSendTable) - return false; - - SendProp * pPropElem = pSendTable->GetProp(iElement); - if (!pPropElem) - return false; - - if (!IsPropValid(pPropElem, iType)) - return false; - - SendPropHook hook; - hook.objectID = gamehelpers->IndexOfEdict(pEdict); - hook.sCallbackInfo.pCallback = pCallback; - hook.sCallbackInfo.iCallbackType = iCallbackType; - hook.sCallbackInfo.pOwner = (void *)pExt; - hook.PropType = iType; - hook.pEnt = pEdict; - hook.pVar = pPropElem; - hook.Element = iElement; - bool bHookedAlready = false; - for (int i = 0; i < g_Hooks.Count(); i++) - { - if (g_Hooks[i].pVar == pPropElem) - { - hook.pRealProxy = g_Hooks[i].pRealProxy; - bHookedAlready = true; - break; - } - } - if (!bHookedAlready) - hook.pRealProxy = pPropElem->GetProxyFn(); - HookExtensionUnload(pExt); - if (g_SendProxyManager.AddHookToList(hook)) - { - if (!bHookedAlready) - pPropElem->SetProxyFn(GlobalProxy); - } - else - { - UnhookExtensionUnload(pExt); - return false; - } - return true; -} - -bool SendProxyManagerInterfaceImpl::HookProxyArray(IExtension * pExt, const char * pProp, CBaseEntity * pEntity, PropType iType, int iElement, CallBackType iCallbackType, void * pCallback) -{ - if (!pProp || !*pProp) - return false; - if (!pEntity) - return false; - ServerClass * sc = ((IServerUnknown *)pEntity)->GetNetworkable()->GetServerClass(); - if (!sc) - return false; //we don't use exceptions, bad extensions may do not handle this and server will crashed, just return false - sm_sendprop_info_t info; - gamehelpers->FindSendPropInfo(sc->GetName(), pProp, &info); - SendProp * pSendProp = info.prop; - if (pSendProp) - return HookProxyArray(pExt, pSendProp, pEntity, iType, iElement, iCallbackType, pCallback); - return false; -} - -bool SendProxyManagerInterfaceImpl::HookProxyArrayGamerules(IExtension * pExt, SendProp * pProp, PropType iType, int iElement, CallBackType iCallbackType, void * pCallback) -{ - SendTable * pSendTable = pProp->GetDataTable(); - if (!pSendTable) - return false; - - SendProp * pPropElem = pSendTable->GetProp(iElement); - if (!pPropElem) - return false; - - if (!IsPropValid(pPropElem, iType)) - return false; - - SendPropHookGamerules hook; - hook.sCallbackInfo.pCallback = pCallback; - hook.sCallbackInfo.iCallbackType = iCallbackType; - hook.sCallbackInfo.pOwner = (void *)pExt; - bool bHookedAlready = false; - for (int i = 0; i < g_HooksGamerules.Count(); i++) - { - if (g_HooksGamerules[i].pVar == pProp) - { - hook.pRealProxy = g_HooksGamerules[i].pRealProxy; - bHookedAlready = true; - break; - } - } - if (!bHookedAlready) - hook.pRealProxy = pProp->GetProxyFn(); - hook.PropType = iType; - hook.pVar = pProp; - sm_sendprop_info_t info; - gamehelpers->FindSendPropInfo(g_szGameRulesProxy, pProp->GetName(), &info); - hook.Element = iElement; - - //if this prop has been hooked already, don't set the proxy again - HookExtensionUnload(pExt); - if (bHookedAlready) - { - if (g_SendProxyManager.AddHookToListGamerules(hook)) - return true; - UnhookExtensionUnload(pExt); - return false; - } - if (g_SendProxyManager.AddHookToListGamerules(hook)) - { - pProp->SetProxyFn(GlobalProxyGamerules); - return true; - } - UnhookExtensionUnload(pExt); - return false; -} - -bool SendProxyManagerInterfaceImpl::HookProxyArrayGamerules(IExtension * pExt, const char * pProp, PropType iType, int iElement, CallBackType iCallbackType, void * pCallback) -{ - if (!pProp || !*pProp) - return false; - sm_sendprop_info_t info; - gamehelpers->FindSendPropInfo(g_szGameRulesProxy, pProp, &info); - SendProp * pSendProp = info.prop; - if (pSendProp) - return HookProxyArrayGamerules(pExt, pSendProp, iType, iElement, iCallbackType, pCallback); - return false; -} - -bool SendProxyManagerInterfaceImpl::UnhookProxyArray(IExtension * pExt, SendProp * pProp, CBaseEntity * pEntity, int iElement, CallBackType iCallbackType, void * pCallback) -{ - const char * pPropName = pProp->GetName(); - return UnhookProxyArray(pExt, pPropName, pEntity, iElement, iCallbackType, pCallback); -} - -bool SendProxyManagerInterfaceImpl::UnhookProxyArray(IExtension * pExt, const char * pProp, CBaseEntity * pEntity, int iElement, CallBackType iCallbackType, void * pCallback) -{ - if (!pProp || !*pProp) - return false; - edict_t * pEdict = gameents->BaseEntityToEdict(pEntity); - for (int i = 0; i < g_Hooks.Count(); i++) - if (g_Hooks[i].sCallbackInfo.pOwner == pExt /*Allow to extension remove only its hooks*/ && g_Hooks[i].Element == iElement && pEdict == g_Hooks[i].pEnt && g_Hooks[i].sCallbackInfo.iCallbackType == iCallbackType && !strcmp(g_Hooks[i].pVar->GetName(), pProp) && pCallback == g_Hooks[i].sCallbackInfo.pCallback) - { - g_SendProxyManager.UnhookProxy(i); - UnhookExtensionUnload(pExt); - return true; - } - return false; -} - -bool SendProxyManagerInterfaceImpl::UnhookProxyArrayGamerules(IExtension * pExt, SendProp * pProp, int iElement, CallBackType iCallbackType, void * pCallback) -{ - const char * pPropName = pProp->GetName(); - return UnhookProxyArrayGamerules(pExt, pPropName, iElement, iCallbackType, pCallback); -} - -bool SendProxyManagerInterfaceImpl::UnhookProxyArrayGamerules(IExtension * pExt, const char * pProp, int iElement, CallBackType iCallbackType, void * pCallback) -{ - if (!pProp || !*pProp) - return false; - for (int i = 0; i < g_HooksGamerules.Count(); i++) - if (g_HooksGamerules[i].sCallbackInfo.pOwner == pExt /*Allow to extension remove only its hooks*/ && g_HooksGamerules[i].Element == iElement && g_HooksGamerules[i].sCallbackInfo.iCallbackType == iCallbackType && !strcmp(g_HooksGamerules[i].pVar->GetName(), pProp) && pCallback == g_HooksGamerules[i].sCallbackInfo.pCallback) - { - g_SendProxyManager.UnhookProxyGamerules(i); - UnhookExtensionUnload(pExt); - return true; - } - return false; -} - -bool SendProxyManagerInterfaceImpl::AddUnhookListenerArray(IExtension * pExt, SendProp * pProp, CBaseEntity * pEntity, int iElement, CallBackType iCallbackType, void * pCallback, ISendProxyUnhookListener * pListener) -{ - const char * pPropName = pProp->GetName(); - return AddUnhookListenerArray(pExt, pPropName, pEntity, iElement, iCallbackType, pCallback, pListener); -} - -bool SendProxyManagerInterfaceImpl::AddUnhookListenerArray(IExtension * pExt, const char * pProp, CBaseEntity * pEntity, int iElement, CallBackType iCallbackType, void * pCallback, ISendProxyUnhookListener * pListener) -{ - if (!pProp || !*pProp) - return false; - edict_t * pEdict = gameents->BaseEntityToEdict(pEntity); - for (int i = 0; i < g_Hooks.Count(); i++) - if (pEdict == g_Hooks[i].pEnt && g_Hooks[i].sCallbackInfo.iCallbackType == iCallbackType && g_Hooks[i].Element == iElement && !strcmp(g_Hooks[i].pVar->GetName(), pProp) && pCallback == g_Hooks[i].sCallbackInfo.pCallback) - { - ListenerCallbackInfo info; - info.m_pExt = pExt; - info.m_pExtAPI = pExt->GetAPI(); - info.m_pCallBack = pListener; - HookExtensionUnload(pExt); - g_Hooks[i].vListeners->AddToTail(info); - return true; - } - return false; -} - -bool SendProxyManagerInterfaceImpl::AddUnhookListenerArrayGamerules(IExtension * pExt, SendProp * pProp, int iElement, CallBackType iCallbackType, void * pCallback, ISendProxyUnhookListener * pListener) -{ - const char * pPropName = pProp->GetName(); - return AddUnhookListenerArrayGamerules(pExt, pPropName, iElement, iCallbackType, pCallback, pListener); -} - -bool SendProxyManagerInterfaceImpl::AddUnhookListenerArrayGamerules(IExtension * pExt, const char * pProp, int iElement, CallBackType iCallbackType, void * pCallback, ISendProxyUnhookListener * pListener) -{ - if (!pProp || !*pProp) - return false; - for (int i = 0; i < g_HooksGamerules.Count(); i++) - if (g_HooksGamerules[i].sCallbackInfo.iCallbackType == iCallbackType && g_HooksGamerules[i].Element == iElement && !strcmp(g_HooksGamerules[i].pVar->GetName(), pProp) && pCallback == g_HooksGamerules[i].sCallbackInfo.pCallback) - { - ListenerCallbackInfo info; - info.m_pExt = pExt; - info.m_pExtAPI = pExt->GetAPI(); - info.m_pCallBack = pListener; - HookExtensionUnload(pExt); - g_HooksGamerules[i].vListeners->AddToTail(info); - return true; - } - return false; -} - -bool SendProxyManagerInterfaceImpl::RemoveUnhookListenerArray(IExtension * pExt, SendProp * pProp, CBaseEntity * pEntity, int iElement, CallBackType iCallbackType, void * pCallback, ISendProxyUnhookListener * pListener) -{ - const char * pPropName = pProp->GetName(); - return RemoveUnhookListenerArray(pExt, pPropName, pEntity, iElement, iCallbackType, pCallback, pListener); -} - -bool SendProxyManagerInterfaceImpl::RemoveUnhookListenerArray(IExtension * pExt, const char * pProp, CBaseEntity * pEntity, int iElement, CallBackType iCallbackType, void * pCallback, ISendProxyUnhookListener * pListener) -{ - if (!pProp || !*pProp) - return false; - - edict_t * pEdict = gameents->BaseEntityToEdict(pEntity); - for (int i = 0; i < g_Hooks.Count(); i++) - if (g_Hooks[i].pEnt == pEdict && g_Hooks[i].sCallbackInfo.iCallbackType == iCallbackType && g_Hooks[i].Element == iElement && !strcmp(g_Hooks[i].pVar->GetName(), pProp) && pCallback == g_Hooks[i].sCallbackInfo.pCallback) - { - for (int j = 0; j < g_Hooks[i].vListeners->Count(); j++) - { - ListenerCallbackInfo info = (*g_Hooks[i].vListeners)[j]; - if (info.m_pExt == pExt && info.m_pCallBack == pListener) - { - g_Hooks[i].vListeners->Remove(j); - UnhookExtensionUnload(pExt); - return true; - } - } - } - return false; -} - -bool SendProxyManagerInterfaceImpl::RemoveUnhookListenerArrayGamerules(IExtension * pExt, SendProp * pProp, int iElement, CallBackType iCallbackType, void * pCallback, ISendProxyUnhookListener * pListener) -{ - const char * pPropName = pProp->GetName(); - return RemoveUnhookListenerArrayGamerules(pExt, pPropName, iElement, iCallbackType, pCallback, pListener); -} - -bool SendProxyManagerInterfaceImpl::RemoveUnhookListenerArrayGamerules(IExtension * pExt, const char * pProp, int iElement, CallBackType iCallbackType, void * pCallback, ISendProxyUnhookListener * pListener) -{ - if (!pProp || !*pProp) - return false; - - for (int i = 0; i < g_HooksGamerules.Count(); i++) - if (g_HooksGamerules[i].sCallbackInfo.iCallbackType == iCallbackType && g_HooksGamerules[i].Element == iElement && !strcmp(g_HooksGamerules[i].pVar->GetName(), pProp) && pCallback == g_HooksGamerules[i].sCallbackInfo.pCallback) - { - for (int j = 0; j < g_HooksGamerules[i].vListeners->Count(); j++) - { - ListenerCallbackInfo info = (*g_HooksGamerules[i].vListeners)[j]; - if (info.m_pExt == pExt && info.m_pCallBack == pListener) - { - g_HooksGamerules[i].vListeners->Remove(j); - UnhookExtensionUnload(pExt); - return true; - } - } - } - return false; -} - -bool SendProxyManagerInterfaceImpl::IsProxyHooked(IExtension * pExt, SendProp * pProp, CBaseEntity * pEntity) const -{ - return IsProxyHooked(pExt, pProp->GetName(), pEntity); -} - -bool SendProxyManagerInterfaceImpl::IsProxyHooked(IExtension * pExt, const char * pProp, CBaseEntity * pEntity) const -{ - if (!pProp || !*pProp) - return false; - edict_t * pEdict = gameents->BaseEntityToEdict(pEntity); - for (int i = 0; i < g_Hooks.Count(); i++) - if (g_Hooks[i].sCallbackInfo.pOwner == (void *)pExt /*Check if is hooked for this extension*/ && g_Hooks[i].pEnt == pEdict && !strcmp(pProp, g_Hooks[i].pVar->GetName())) - return true; - return false; -} - -bool SendProxyManagerInterfaceImpl::IsProxyHookedGamerules(IExtension * pExt, SendProp * pProp) const -{ - return IsProxyHookedGamerules(pExt, pProp->GetName()); -} - -bool SendProxyManagerInterfaceImpl::IsProxyHookedGamerules(IExtension * pExt, const char * pProp) const -{ - if (!pProp || !*pProp) - return false; - for (int i = 0; i < g_HooksGamerules.Count(); i++) - if (g_HooksGamerules[i].sCallbackInfo.pOwner == (void *)pExt /*Check if is hooked for this extension*/ && !strcmp(pProp, g_HooksGamerules[i].pVar->GetName())) - return true; - return false; -} - -bool SendProxyManagerInterfaceImpl::IsProxyHookedArray(IExtension * pExt, SendProp * pProp, CBaseEntity * pEntity, int iElement) const -{ - return IsProxyHookedArray(pExt, pProp->GetName(), pEntity, iElement); -} - -bool SendProxyManagerInterfaceImpl::IsProxyHookedArray(IExtension * pExt, const char * pProp, CBaseEntity * pEntity, int iElement) const -{ - if (!pProp || !*pProp) - return false; - edict_t * pEdict = gameents->BaseEntityToEdict(pEntity); - for (int i = 0; i < g_Hooks.Count(); i++) - if (g_Hooks[i].sCallbackInfo.pOwner == (void *)pExt /*Check if is hooked for this extension*/ && g_Hooks[i].pEnt == pEdict && g_Hooks[i].Element == iElement && !strcmp(pProp, g_Hooks[i].pVar->GetName())) - return true; - return false; -} - -bool SendProxyManagerInterfaceImpl::IsProxyHookedArrayGamerules(IExtension * pExt, SendProp * pProp, int iElement) const -{ - return IsProxyHookedArrayGamerules(pExt, pProp->GetName(), iElement); -} - -bool SendProxyManagerInterfaceImpl::IsProxyHookedArrayGamerules(IExtension * pExt, const char * pProp, int iElement) const -{ - if (!pProp || !*pProp) - return false; - for (int i = 0; i < g_HooksGamerules.Count(); i++) - if (g_HooksGamerules[i].sCallbackInfo.pOwner == (void *)pExt /*Check if is hooked for this extension*/ && g_HooksGamerules[i].Element == iElement && !strcmp(pProp, g_HooksGamerules[i].pVar->GetName())) - return true; - return false; -} \ No newline at end of file diff --git a/extension/interfaceimpl.h b/extension/interfaceimpl.h deleted file mode 100644 index 0e812e6..0000000 --- a/extension/interfaceimpl.h +++ /dev/null @@ -1,123 +0,0 @@ -/** - * vim: set ts=4 : - * ============================================================================= - * SendVar Proxy Manager - * Copyright (C) 2011-2019 Afronanny & AlliedModders community. All rights reserved. - * ============================================================================= - * - * 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 . - * - * As a special exception, AlliedModders LLC gives you permission to link the - * code of this program (as well as its derivative works) to "Half-Life 2," the - * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software - * by the Valve Corporation. You must obey the GNU General Public License in - * all respects for all other code used. Additionally, AlliedModders LLC grants - * this exception to all derivative works. AlliedModders LLC defines further - * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), - * or . - * - * Version: $Id$ - */ - -#ifndef SENDPROXY_IFACE_IMPL_INC -#define SENDPROXY_IFACE_IMPL_INC - -#include "extension.h" -#include "ISendProxy.h" - -void CallListenersForHookID(int iID); -void CallListenersForHookIDGamerules(int iID); - -class SendProxyManagerInterfaceImpl : public ISendProxyManager -{ -public: //SMInterface - virtual const char * GetInterfaceName() override; - virtual unsigned int GetInterfaceVersion() override; -public: //interface impl: - virtual bool HookProxy(IExtension *, SendProp *, CBaseEntity *, PropType, CallBackType, void *) override; - virtual bool HookProxy(IExtension *, const char *, CBaseEntity *, PropType, CallBackType, void *) override; - virtual bool HookProxyGamerules(IExtension *, SendProp *, PropType, CallBackType, void *) override; - virtual bool HookProxyGamerules(IExtension *, const char *, PropType, CallBackType, void *) override; - virtual bool UnhookProxy(IExtension *, SendProp *, CBaseEntity *, CallBackType, void *) override; - virtual bool UnhookProxy(IExtension *, const char *, CBaseEntity *, CallBackType, void *) override; - virtual bool UnhookProxyGamerules(IExtension *, SendProp *, CallBackType, void *) override; - virtual bool UnhookProxyGamerules(IExtension *, const char *, CallBackType, void *) override; - virtual bool AddUnhookListener(IExtension *, SendProp *, CBaseEntity *, CallBackType, void *, ISendProxyUnhookListener *) override; - virtual bool AddUnhookListener(IExtension *, const char *, CBaseEntity *, CallBackType, void *, ISendProxyUnhookListener *) override; - virtual bool AddUnhookListenerGamerules(IExtension *, SendProp *, CallBackType, void *, ISendProxyUnhookListener *) override; - virtual bool AddUnhookListenerGamerules(IExtension *, const char *, CallBackType, void *, ISendProxyUnhookListener *) override; - virtual bool RemoveUnhookListener(IExtension *, SendProp *, CBaseEntity *, CallBackType, void *, ISendProxyUnhookListener *) override; - virtual bool RemoveUnhookListener(IExtension *, const char *, CBaseEntity *, CallBackType, void *, ISendProxyUnhookListener *) override; - virtual bool RemoveUnhookListenerGamerules(IExtension *, SendProp *, CallBackType, void *, ISendProxyUnhookListener *) override; - virtual bool RemoveUnhookListenerGamerules(IExtension *, const char *, CallBackType, void *, ISendProxyUnhookListener *) override; - //same for the arrays =| - virtual bool HookProxyArray(IExtension *, SendProp *, CBaseEntity *, PropType, int, CallBackType, void *) override; - virtual bool HookProxyArray(IExtension *, const char *, CBaseEntity *, PropType, int, CallBackType, void *) override; - virtual bool UnhookProxyArray(IExtension *, SendProp *, CBaseEntity *, int, CallBackType, void *) override; - virtual bool UnhookProxyArray(IExtension *, const char *, CBaseEntity *, int, CallBackType, void *) override; - virtual bool HookProxyArrayGamerules(IExtension *, SendProp *, PropType, int, CallBackType, void *) override; - virtual bool HookProxyArrayGamerules(IExtension *, const char *, PropType, int, CallBackType, void *) override; - virtual bool UnhookProxyArrayGamerules(IExtension *, SendProp *, int, CallBackType, void *) override; - virtual bool UnhookProxyArrayGamerules(IExtension *, const char *, int, CallBackType, void *) override; - virtual bool AddUnhookListenerArray(IExtension *, SendProp *, CBaseEntity *, int, CallBackType, void *, ISendProxyUnhookListener *) override; - virtual bool AddUnhookListenerArray(IExtension *, const char *, CBaseEntity *, int, CallBackType, void *, ISendProxyUnhookListener *) override; - virtual bool AddUnhookListenerArrayGamerules(IExtension *, SendProp *, int, CallBackType, void *, ISendProxyUnhookListener *) override; - virtual bool AddUnhookListenerArrayGamerules(IExtension *, const char *, int, CallBackType, void *, ISendProxyUnhookListener *) override; - virtual bool RemoveUnhookListenerArray(IExtension *, SendProp *, CBaseEntity *, int, CallBackType, void *, ISendProxyUnhookListener *) override; - virtual bool RemoveUnhookListenerArray(IExtension *, const char *, CBaseEntity *, int, CallBackType, void *, ISendProxyUnhookListener *) override; - virtual bool RemoveUnhookListenerArrayGamerules(IExtension *, SendProp *, int, CallBackType, void *, ISendProxyUnhookListener *) override; - virtual bool RemoveUnhookListenerArrayGamerules(IExtension *, const char *, int, CallBackType, void *, ISendProxyUnhookListener *) override; - //checkers - virtual bool IsProxyHooked(IExtension *, SendProp *, CBaseEntity *) const override; - virtual bool IsProxyHooked(IExtension *, const char *, CBaseEntity *) const override; - virtual bool IsProxyHookedGamerules(IExtension *, SendProp *) const override; - virtual bool IsProxyHookedGamerules(IExtension *, const char *) const override; - virtual bool IsProxyHookedArray(IExtension *, SendProp *, CBaseEntity *, int) const override; - virtual bool IsProxyHookedArray(IExtension *, const char *, CBaseEntity *, int) const override; - virtual bool IsProxyHookedArrayGamerules(IExtension *, SendProp *, int) const override; - virtual bool IsProxyHookedArrayGamerules(IExtension *, const char *, int) const override; - - /* - TODO: - //For the change hooks - virtual bool HookChange(IExtension *, SendProp *, CBaseEntity *, PropType, CallBackType, void *) override; - virtual bool HookChange(IExtension *, const char *, CBaseEntity *, PropType, CallBackType, void *) override; - virtual bool HookChangeGamerules(IExtension *, SendProp *, PropType, CallBackType, void *) override; - virtual bool HookChangeGamerules(IExtension *, const char *, PropType, CallBackType, void *) override; - virtual bool UnhookChange(IExtension *, SendProp *, CBaseEntity *, CallBackType, void *) override; - virtual bool UnhookChange(IExtension *, const char *, CBaseEntity *, CallBackType, void *) override; - virtual bool UnhookChangeGamerules(IExtension *, SendProp *, CallBackType, void *) override; - virtual bool UnhookChangeGamerules(IExtension *, const char *, CallBackType, void *) override; - //same for the arrays =| - virtual bool HookChangeArray(IExtension *, SendProp *, CBaseEntity *, PropType, int, CallBackType, void *) override; - virtual bool HookChangeArray(IExtension *, const char *, CBaseEntity *, PropType, int, CallBackType, void *) override; - virtual bool UnhookChangeArray(IExtension *, SendProp *, CBaseEntity *, int, CallBackType, void *) override; - virtual bool UnhookChangeArray(IExtension *, const char *, CBaseEntity *, int, CallBackType, void *) override; - virtual bool HookChangeArrayGamerules(IExtension *, SendProp *, PropType, int, CallBackType, void *) override; - virtual bool HookChangeArrayGamerules(IExtension *, const char *, PropType, int, CallBackType, void *) override; - virtual bool UnhookChangeArrayGamerules(IExtension *, SendProp *, int, CallBackType, void *) override; - virtual bool UnhookChangeArrayGamerules(IExtension *, const char *, int, CallBackType, void *) override; - //checkers - virtual bool IsChangeHooked(IExtension *, SendProp *, CBaseEntity *) override; - virtual bool IsChangeHooked(IExtension *, const char *, CBaseEntity *) override; - virtual bool IsChangeHookedGamerules(IExtension *, SendProp *) override; - virtual bool IsChangeHookedGamerules(IExtension *, const char *) override; - virtual bool IsChangeHookedArray(IExtension *, SendProp *, CBaseEntity *, int) override; - virtual bool IsChangeHookedArray(IExtension *, const char *, CBaseEntity *, int) override; - virtual bool IsChangeHookedArrayGamerules(IExtension *, SendProp *, int) override; - virtual bool IsChangeHookedArrayGamerules(IExtension *, const char *, int) override; - //More TODO: Add unhook listeners for change hooks - */ -}; - -#endif \ No newline at end of file diff --git a/extension/natives.cpp b/extension/natives.cpp index 9893a95..18e2c9c 100644 --- a/extension/natives.cpp +++ b/extension/natives.cpp @@ -30,998 +30,255 @@ */ #include "natives.h" +#include "util.h" +#include "sendprop_hookmanager.h" -static cell_t Native_UnhookPropChange(IPluginContext * pContext, const cell_t * params) +void UTIL_FindSendProp(SendProp* &ret, IPluginContext *pContext, int index, const char* propname, bool checkType, PropType type, int element) { - if (params[1] < 0 || params[1] >= g_iEdictCount) - return pContext->ThrowNativeError("Invalid Edict Index %d", params[1]); - - int entity = params[1]; - char * name; - edict_t * pEnt = gamehelpers->EdictOfIndex(entity); - pContext->LocalToString(params[2], &name); - ServerClass * sc = pEnt->GetNetworkable()->GetServerClass(); + edict_t *edict = UTIL_EdictOfIndex(index); + if (!edict) + return pContext->ReportError("Invalid edict index (%d)", index); + ServerClass *sc = edict->GetNetworkable()->GetServerClass(); if (!sc) - return pContext->ThrowNativeError("Cannot find ServerClass for entity %d", entity); + return pContext->ReportError("Cannot find ServerClass for entity %d", index); sm_sendprop_info_t info; - gamehelpers->FindSendPropInfo(sc->GetName(), name, &info); - IPluginFunction * callback = pContext->GetFunctionById(params[3]); + gamehelpers->FindSendPropInfo(sc->GetName(), propname, &info); - for (int i = 0; i < g_ChangeHooks.Count(); i++) - { - if (g_ChangeHooks[i].objectID == entity && g_ChangeHooks[i].pVar == info.prop) - { - CallBackInfo sInfo; - sInfo.pCallback = callback; - sInfo.pOwner = (void *)pContext; - sInfo.iCallbackType = CallBackType::Callback_PluginFunction; - g_SendProxyManager.UnhookChange(i, &sInfo); - break; - } - } - return 1; -} - -static cell_t Native_UnhookPropChangeGameRules(IPluginContext * pContext, const cell_t * params) -{ - char * name; - pContext->LocalToString(params[1], &name); - IPluginFunction * callback = pContext->GetFunctionById(params[2]); - sm_sendprop_info_t info; - gamehelpers->FindSendPropInfo(g_szGameRulesProxy, name, &info); + SendProp *pProp = info.prop; + if (!pProp) + return pContext->ReportError("Could not find prop %s", propname); - for (int i = 0; i < g_ChangeHooksGamerules.Count(); i++) + if (pProp->GetType() == DPT_Array) { - if (g_ChangeHooksGamerules[i].pVar == info.prop) - { - CallBackInfo sInfo; - sInfo.pCallback = callback; - sInfo.pOwner = (void *)pContext; - sInfo.iCallbackType = CallBackType::Callback_PluginFunction; - g_SendProxyManager.UnhookChangeGamerules(i, &sInfo); - break; - } + pProp = pProp->GetArrayProp(); + if (!pProp) + return pContext->ReportError("Unexpected: Prop %s is an array but has no array prop", propname); + + if (element < 0 || element > info.prop->GetNumElements()) + return pContext->ReportError("Unable to find element %d of prop %s", element, propname); } - return 1; -} - -static cell_t Native_HookPropChange(IPluginContext * pContext, const cell_t * params) -{ - bool bSafeCheck = params[0] >= 4; - - if (params[1] < 0 || params[1] >= g_iEdictCount) - return pContext->ThrowNativeError("Invalid Edict Index %d", params[1]); - - int entity = params[1]; - char * name; - edict_t * pEnt = gamehelpers->EdictOfIndex(entity); - pContext->LocalToString(params[2], &name); - ServerClass * sc = pEnt->GetNetworkable()->GetServerClass(); - - if (!sc) - return pContext->ThrowNativeError("Cannot find ServerClass for entity %d", entity); - - sm_sendprop_info_t info; - gamehelpers->FindSendPropInfo(sc->GetName(), name, &info); - SendProp * pProp = info.prop; - - if (!pProp) - return pContext->ThrowNativeError("Could not find prop %s", name); - - IPluginFunction * callback = nullptr; - PropType propType = PropType::Prop_Max; - if (bSafeCheck) + else if (pProp->GetType() == DPT_DataTable) { - propType = static_cast(params[3]); - callback = pContext->GetFunctionById(params[4]); - } - else - callback = pContext->GetFunctionById(params[3]); - - if (bSafeCheck && !IsPropValid(pProp, propType)) - switch (propType) - { - case PropType::Prop_Int: - return pContext->ThrowNativeError("Prop %s is not an int!", pProp->GetName()); - case PropType::Prop_Float: - return pContext->ThrowNativeError("Prop %s is not a float!", pProp->GetName()); - case PropType::Prop_String: - return pContext->ThrowNativeError("Prop %s is not a string!", pProp->GetName()); - case PropType::Prop_Vector: - return pContext->ThrowNativeError("Prop %s is not a vector!", pProp->GetName()); - default: - return pContext->ThrowNativeError("Unsupported prop type %d", propType); - } - - PropChangeHook hook; - hook.objectID = entity; - hook.Offset = info.actual_offset; - hook.pVar = pProp; - CallBackInfo sCallInfo; - sCallInfo.iCallbackType = CallBackType::Callback_PluginFunction; - sCallInfo.pCallback = (void *)callback; - sCallInfo.pOwner = (void *)pContext; - if (!g_SendProxyManager.AddChangeHookToList(hook, &sCallInfo)) - return pContext->ThrowNativeError("Entity %d isn't valid", entity); - return 1; -} - -static cell_t Native_HookPropChangeGameRules(IPluginContext * pContext, const cell_t * params) -{ - bool bSafeCheck = params[0] >= 3; - char * name; - pContext->LocalToString(params[1], &name); - sm_sendprop_info_t info; - gamehelpers->FindSendPropInfo(g_szGameRulesProxy, name, &info); - SendProp * pProp = info.prop; + if (!pProp->GetDataTable()) + return pContext->ReportError("Unexpected: Prop %s is a datatable but has no data table", propname); - if (!pProp) - return pContext->ThrowNativeError("Could not find prop %s", name); - - IPluginFunction * callback = nullptr; - PropType propType = PropType::Prop_Max; - if (bSafeCheck) - { - propType = static_cast(params[2]); - callback = pContext->GetFunctionById(params[3]); + pProp = pProp->GetDataTable()->GetProp(element); + if (!pProp) + return pContext->ReportError("Unable to find element %d of prop %s", element, propname); } - else - callback = pContext->GetFunctionById(params[2]); - - if (bSafeCheck && !IsPropValid(pProp, propType)) - switch (propType) - { - case PropType::Prop_Int: - return pContext->ThrowNativeError("Prop %s is not an int!", pProp->GetName()); - case PropType::Prop_Float: - return pContext->ThrowNativeError("Prop %s is not a float!", pProp->GetName()); - case PropType::Prop_String: - return pContext->ThrowNativeError("Prop %s is not a string!", pProp->GetName()); - case PropType::Prop_Vector: - return pContext->ThrowNativeError("Prop %s is not a vector!", pProp->GetName()); - default: - return pContext->ThrowNativeError("Unsupported prop type %d", propType); - } - if (!g_pGameRules) + if (checkType && !IsPropValid(pProp, type)) { - g_pGameRules = g_pSDKTools->GetGameRules(); - if (!g_pGameRules) - { - return pContext->ThrowNativeError("CRITICAL ERROR: Could not get gamerules pointer!"); - } - } - - PropChangeHookGamerules hook; - hook.Offset = info.actual_offset; - hook.pVar = pProp; - CallBackInfo sCallInfo; - sCallInfo.iCallbackType = CallBackType::Callback_PluginFunction; - sCallInfo.pCallback = (void *)callback; - sCallInfo.pOwner = (void *)pContext; - if (!g_SendProxyManager.AddChangeHookToListGamerules(hook, &sCallInfo)) - return pContext->ThrowNativeError("Prop type %d isn't valid", pProp->GetType()); //should never happen - return 1; -} - -static cell_t Native_Hook(IPluginContext * pContext, const cell_t * params) -{ - if (params[1] < 0 || params[1] >= g_iEdictCount) - return pContext->ThrowNativeError("Invalid Edict Index %d", params[1]); - - int entity = params[1]; - char * name; - pContext->LocalToString(params[2], &name); - edict_t * pEnt = gamehelpers->EdictOfIndex(entity); - ServerClass * sc = pEnt->GetNetworkable()->GetServerClass(); - - if (!sc) - return pContext->ThrowNativeError("Cannot find ServerClass for entity %d", entity); - - sm_sendprop_info_t info; - gamehelpers->FindSendPropInfo(sc->GetName(), name, &info); - SendProp * pProp = info.prop; - - if (!pProp) - return pContext->ThrowNativeError("Could not find prop %s", name); - - PropType propType = static_cast(params[3]); - if (!IsPropValid(pProp, propType)) - switch (propType) + switch (type) { case PropType::Prop_Int: - return pContext->ThrowNativeError("Prop %s is not an int!", pProp->GetName()); + return pContext->ReportError("Prop %s is not an int!", propname); case PropType::Prop_Float: - return pContext->ThrowNativeError("Prop %s is not a float!", pProp->GetName()); + return pContext->ReportError("Prop %s is not a float!", propname); case PropType::Prop_String: - return pContext->ThrowNativeError("Prop %s is not a string!", pProp->GetName()); + return pContext->ReportError("Prop %s is not a string!", propname); case PropType::Prop_Vector: - return pContext->ThrowNativeError("Prop %s is not a vector!", pProp->GetName()); + return pContext->ReportError("Prop %s is not a vector!", propname); + case PropType::Prop_EHandle: + return pContext->ReportError("Prop %s is not an EHandle!", propname); default: - return pContext->ThrowNativeError("Unsupported prop type %d", propType); - } - - SendPropHook hook; - hook.objectID = entity; - hook.sCallbackInfo.pCallback = (void *)pContext->GetFunctionById(params[4]); - hook.sCallbackInfo.iCallbackType = CallBackType::Callback_PluginFunction; - hook.sCallbackInfo.pOwner = (void *)pContext; - hook.pEnt = pEnt; - bool bHookedAlready = false; - for (int i = 0; i < g_Hooks.Count(); i++) - { - if (g_Hooks[i].pVar == pProp) - { - hook.pRealProxy = g_Hooks[i].pRealProxy; - bHookedAlready = true; - break; + return pContext->ReportError("Unsupported prop type %d", type); } } - if (!bHookedAlready) - hook.pRealProxy = pProp->GetProxyFn(); - hook.PropType = propType; - hook.pVar = pProp; - - //if this prop has been hooked already, don't set the proxy again - if (bHookedAlready) - { - if (g_SendProxyManager.AddHookToList(hook)) - return 1; - return 0; - } - if (g_SendProxyManager.AddHookToList(hook)) - { - pProp->SetProxyFn(GlobalProxy); - return 1; - } - return 0; -} -static cell_t Native_HookGameRules(IPluginContext * pContext, const cell_t * params) -{ - char * name; - pContext->LocalToString(params[1], &name); - sm_sendprop_info_t info; - - gamehelpers->FindSendPropInfo(g_szGameRulesProxy, name, &info); - SendProp * pProp = info.prop; - - if (!pProp) - return pContext->ThrowNativeError("Could not find prop %s", name); - - PropType propType = static_cast(params[2]); - if (!IsPropValid(pProp, propType)) - switch (propType) - { - case PropType::Prop_Int: - return pContext->ThrowNativeError("Prop %s is not an int!", pProp->GetName()); - case PropType::Prop_Float: - return pContext->ThrowNativeError("Prop %s is not a float!", pProp->GetName()); - case PropType::Prop_String: - return pContext->ThrowNativeError("Prop %s is not a string!", pProp->GetName()); - case PropType::Prop_Vector: - return pContext->ThrowNativeError("Prop %s is not a vector!", pProp->GetName()); - default: - return pContext->ThrowNativeError("Unsupported prop type %d", propType); - } - - SendPropHookGamerules hook; - hook.sCallbackInfo.pCallback = (void *)pContext->GetFunctionById(params[3]); - hook.sCallbackInfo.iCallbackType = CallBackType::Callback_PluginFunction; - hook.sCallbackInfo.pOwner = (void *)pContext; - bool bHookedAlready = false; - for (int i = 0; i < g_HooksGamerules.Count(); i++) - { - if (g_HooksGamerules[i].pVar == pProp) - { - hook.pRealProxy = g_HooksGamerules[i].pRealProxy; - bHookedAlready = true; - break; - } - } - if (!bHookedAlready) - hook.pRealProxy = pProp->GetProxyFn(); - hook.PropType = propType; - hook.pVar = pProp; - - //if this prop has been hooked already, don't set the proxy again - if (bHookedAlready) - { - if (g_SendProxyManager.AddHookToListGamerules(hook)) - return 1; - return 0; - } - if (g_SendProxyManager.AddHookToListGamerules(hook)) - { - pProp->SetProxyFn(GlobalProxyGamerules); - return 1; - } - return 0; + ret = pProp; } -static cell_t Native_HookArrayProp(IPluginContext * pContext, const cell_t * params) +static cell_t Native_Hook(IPluginContext *pContext, const cell_t *params) { - if (params[1] < 0 || params[1] >= g_iEdictCount) - return pContext->ThrowNativeError("Invalid Edict Index %d", params[1]); - - int entity = params[1]; - char * propName; - pContext->LocalToString(params[2], &propName); - - edict_t * pEnt = gamehelpers->EdictOfIndex(entity); - ServerClass * sc = pEnt->GetNetworkable()->GetServerClass(); - - if (!sc) - return pContext->ThrowNativeError("Cannot find ServerClass for entity %d", entity); - - sm_sendprop_info_t info; - gamehelpers->FindSendPropInfo(sc->GetName(), propName, &info); - - int element = params[3]; - PropType propType = static_cast(params[4]); - SendProp *pProp = NULL; - switch (info.prop->GetType()) + constexpr cell_t PARAM_COUNT = 5; + if (params[0] < PARAM_COUNT) { - case DPT_Array: - { - pProp = info.prop->GetArrayProp(); - if (!pProp) - return pContext->ThrowNativeError("Prop %s does not contain any elements", propName); - - if (element > info.prop->GetNumElements()) - return pContext->ThrowNativeError("Could not find element %d in %s", element, info.prop->GetName()); - - if (!IsPropValid(pProp, propType)) - switch (propType) - { - case PropType::Prop_Int: - return pContext->ThrowNativeError("Prop %s is not an int!", pProp->GetName()); - case PropType::Prop_Float: - return pContext->ThrowNativeError("Prop %s is not a float!", pProp->GetName()); - case PropType::Prop_String: - return pContext->ThrowNativeError("Prop %s is not a string!", pProp->GetName()); - case PropType::Prop_Vector: - return pContext->ThrowNativeError("Prop %s is not a vector!", pProp->GetName()); - default: - return pContext->ThrowNativeError("Unsupported prop type %d", propType); - } - - break; - } - - case DPT_DataTable: - { - SendTable * st = info.prop->GetDataTable(); - - if (!st) - return pContext->ThrowNativeError("Prop %s does not contain any elements", propName); + pContext->ReportError("Expected %d params, found %d", PARAM_COUNT, params[0]); + return false; + } - pProp = st->GetProp(element); - if (!pProp) - return pContext->ThrowNativeError("Could not find element %d in %s", element, info.prop->GetName()); + char *propname = nullptr; + SendProp *pProp = nullptr; - if (!IsPropValid(pProp, propType)) - switch (propType) - { - case PropType::Prop_Int: - return pContext->ThrowNativeError("Prop %s is not an int!", pProp->GetName()); - case PropType::Prop_Float: - return pContext->ThrowNativeError("Prop %s is not a float!", pProp->GetName()); - case PropType::Prop_String: - return pContext->ThrowNativeError("Prop %s is not a string!", pProp->GetName()); - case PropType::Prop_Vector: - return pContext->ThrowNativeError("Prop %s is not a vector!", pProp->GetName()); - default: - return pContext->ThrowNativeError("Unsupported prop type %d", propType); - } + int index = params[1]; + pContext->LocalToString(params[2], &propname); + PropType type = static_cast(params[3]); + IPluginFunction *pFunc = pContext->GetFunctionById(params[4]); + int element = params[5]; - break; - } - - default: - return pContext->ThrowNativeError("Prop %s does not contain any elements", propName); - } - - SendPropHook hook; - hook.objectID = entity; - hook.sCallbackInfo.pCallback = (void *)pContext->GetFunctionById(params[5]);; - hook.sCallbackInfo.iCallbackType = CallBackType::Callback_PluginFunction; - hook.sCallbackInfo.pOwner = (void *)pContext; - hook.pEnt = pEnt; - hook.Element = element; - bool bHookedAlready = false; - for (int i = 0; i < g_Hooks.Count(); i++) - { - if (g_Hooks[i].pVar == pProp) - { - hook.pRealProxy = g_Hooks[i].pRealProxy; - bHookedAlready = true; - break; - } - } - if (!bHookedAlready) - hook.pRealProxy = pProp->GetProxyFn(); - hook.PropType = propType; - hook.pVar = pProp; + UTIL_FindSendProp(pProp, pContext, index, propname, true, type, element); + if (pProp == nullptr) + return false; - if (bHookedAlready) - { - if (g_SendProxyManager.AddHookToList(hook)) - return 1; - return 0; - } - if (g_SendProxyManager.AddHookToList(hook)) - { - pProp->SetProxyFn(GlobalProxy); - return 1; - } - return 0; -} - -static cell_t Native_UnhookArrayProp(IPluginContext * pContext, const cell_t * params) -{ - if (params[1] < 0 || params[1] >= g_iEdictCount) - return pContext->ThrowNativeError("Invalid Edict Index %d", params[1]); + if (g_pSendPropHookManager->IsEntityHooked(index, pProp, element, pFunc)) + return true; - int entity = params[1]; - char * propName; - pContext->LocalToString(params[2], &propName); - int element = params[3]; - PropType propType = static_cast(params[4]); - IPluginFunction * callback = pContext->GetFunctionById(params[5]); - for (int i = 0; i < g_Hooks.Count(); i++) - { - //we check callback here, so, we do not need to check owner - if (g_Hooks[i].Element == element && g_Hooks[i].sCallbackInfo.iCallbackType == CallBackType::Callback_PluginFunction && g_Hooks[i].PropType == propType && g_Hooks[i].sCallbackInfo.pCallback == (void *)callback && !strcmp(g_Hooks[i].pVar->GetName(), propName) && g_Hooks[i].objectID == entity) - { - g_SendProxyManager.UnhookProxy(i); - return 1; - } - } - return 0; + return g_pSendPropHookManager->HookEntity(index, pProp, element, type, pFunc); } static cell_t Native_Unhook(IPluginContext * pContext, const cell_t * params) { - char * propName; - pContext->LocalToString(params[2], &propName); - IPluginFunction * pFunction = pContext->GetFunctionById(params[3]); - for (int i = 0; i < g_Hooks.Count(); i++) + constexpr cell_t PARAM_COUNT = 4; + if (params[0] < PARAM_COUNT) { - //we check callback here, so, we do not need to check owner - if (params[1] == g_Hooks[i].objectID && g_Hooks[i].sCallbackInfo.iCallbackType == CallBackType::Callback_PluginFunction && strcmp(g_Hooks[i].pVar->GetName(), propName) == 0 && (void *)pFunction == g_Hooks[i].sCallbackInfo.pCallback) - { - g_SendProxyManager.UnhookProxy(i); - return 1; - } + pContext->ReportError("Expected %d params, found %d", PARAM_COUNT, params[0]); + return false; } - return 0; -} -static cell_t Native_UnhookGameRules(IPluginContext * pContext, const cell_t * params) -{ - char * propName; - pContext->LocalToString(params[1], &propName); - IPluginFunction * pFunction = pContext->GetFunctionById(params[2]); - for (int i = 0; i < g_HooksGamerules.Count(); i++) - { - //we check callback here, so, we do not need to check owner - if (g_HooksGamerules[i].sCallbackInfo.iCallbackType == CallBackType::Callback_PluginFunction && strcmp(g_HooksGamerules[i].pVar->GetName(), propName) == 0 && (void *)pFunction == g_HooksGamerules[i].sCallbackInfo.pCallback) - { - g_SendProxyManager.UnhookProxyGamerules(i); - return 1; - } - } - return 0; -} + char *propname = nullptr; + SendProp *pProp = nullptr; -static cell_t Native_IsHooked(IPluginContext * pContext, const cell_t * params) -{ - int objectID = params[1]; - char * propName; - pContext->LocalToString(params[2], &propName); + int index = params[1]; + pContext->LocalToString(params[2], &propname); + IPluginFunction *pFunc = pContext->GetFunctionById(params[3]); + int element = params[4]; - for (int i = 0; i < g_Hooks.Count(); i++) - { - if (g_Hooks[i].objectID == objectID && g_Hooks[i].sCallbackInfo.pOwner == (void *)pContext && strcmp(propName, g_Hooks[i].pVar->GetName()) == 0) - return 1; - } - return 0; -} + UTIL_FindSendProp(pProp, pContext, index, propname, false, PropType::Prop_Max, element); + if (pProp == nullptr) + return false; -static cell_t Native_IsHookedGameRules(IPluginContext * pContext, const cell_t * params) -{ - char * propName; - pContext->LocalToString(params[1], &propName); + if (!g_pSendPropHookManager->IsEntityHooked(index, pProp, element, pFunc)) + return false; - for (int i = 0; i < g_HooksGamerules.Count(); i++) - { - if (g_HooksGamerules[i].sCallbackInfo.pOwner == (void *)pContext && strcmp(propName, g_HooksGamerules[i].pVar->GetName()) == 0) - return 1; - } - return 0; + g_pSendPropHookManager->UnhookEntity(index, pProp, element, pFunc); + return true; } -static cell_t Native_HookArrayPropGamerules(IPluginContext * pContext, const cell_t * params) +static cell_t Native_IsHooked(IPluginContext * pContext, const cell_t * params) { - char * propName; - pContext->LocalToString(params[1], &propName); - sm_sendprop_info_t info; - - gamehelpers->FindSendPropInfo(g_szGameRulesProxy, propName, &info); - - if (!info.prop) - return pContext->ThrowNativeError("Could not find prop %s", propName); - - int element = params[2]; - PropType propType = static_cast(params[3]); - SendProp *pProp = NULL; - switch (info.prop->GetType()) + constexpr cell_t PARAM_COUNT = 4; + if (params[0] < PARAM_COUNT) { - case DPT_Array: - { - pProp = info.prop->GetArrayProp(); - if (!pProp) - return pContext->ThrowNativeError("Prop %s does not contain any elements", propName); - - if (element > info.prop->GetNumElements()) - return pContext->ThrowNativeError("Could not find element %d in %s", element, info.prop->GetName()); - - if (!IsPropValid(pProp, propType)) - switch (propType) - { - case PropType::Prop_Int: - return pContext->ThrowNativeError("Prop %s is not an int!", pProp->GetName()); - case PropType::Prop_Float: - return pContext->ThrowNativeError("Prop %s is not a float!", pProp->GetName()); - case PropType::Prop_String: - return pContext->ThrowNativeError("Prop %s is not a string!", pProp->GetName()); - case PropType::Prop_Vector: - return pContext->ThrowNativeError("Prop %s is not a vector!", pProp->GetName()); - default: - return pContext->ThrowNativeError("Unsupported prop type %d", propType); - } - - break; - } - - case DPT_DataTable: - { - SendTable * st = info.prop->GetDataTable(); + pContext->ReportError("Expected %d params, found %d", PARAM_COUNT, params[0]); + return false; + } - if (!st) - return pContext->ThrowNativeError("Prop %s does not contain any elements", propName); + char *propname = nullptr; + SendProp *pProp = nullptr; - pProp = st->GetProp(element); - if (!pProp) - return pContext->ThrowNativeError("Could not find element %d in %s", element, info.prop->GetName()); + int index = params[1]; + pContext->LocalToString(params[2], &propname); + IPluginFunction *pFunc = pContext->GetFunctionById(params[3]); + int element = params[4]; - if (!IsPropValid(pProp, propType)) - switch (propType) - { - case PropType::Prop_Int: - return pContext->ThrowNativeError("Prop %s is not an int!", pProp->GetName()); - case PropType::Prop_Float: - return pContext->ThrowNativeError("Prop %s is not a float!", pProp->GetName()); - case PropType::Prop_String: - return pContext->ThrowNativeError("Prop %s is not a string!", pProp->GetName()); - case PropType::Prop_Vector: - return pContext->ThrowNativeError("Prop %s is not a vector!", pProp->GetName()); - default: - return pContext->ThrowNativeError("Unsupported prop type %d", propType); - } + UTIL_FindSendProp(pProp, pContext, index, propname, false, PropType::Prop_Max, element); + if (pProp == nullptr) + return false; - break; - } - - default: - return pContext->ThrowNativeError("Prop %s does not contain any elements", propName); - } - - SendPropHookGamerules hook; - hook.sCallbackInfo.pCallback = (void *)pContext->GetFunctionById(params[4]); - hook.sCallbackInfo.iCallbackType = CallBackType::Callback_PluginFunction; - hook.sCallbackInfo.pOwner = (void *)pContext; - hook.Element = element; - bool bHookedAlready = false; - for (int i = 0; i < g_HooksGamerules.Count(); i++) - { - if (g_HooksGamerules[i].pVar == pProp) - { - hook.pRealProxy = g_HooksGamerules[i].pRealProxy; - bHookedAlready = true; - break; - } - } - if (!bHookedAlready) - hook.pRealProxy = pProp->GetProxyFn(); - hook.PropType = propType; - hook.pVar = pProp; - - if (bHookedAlready) - { - if (g_SendProxyManager.AddHookToListGamerules(hook)) - return 1; - return 0; - } - if (g_SendProxyManager.AddHookToListGamerules(hook)) - { - pProp->SetProxyFn(GlobalProxyGamerules); - return 1; - } - return 0; -} - -static cell_t Native_UnhookArrayPropGamerules(IPluginContext * pContext, const cell_t * params) -{ - char * propName; - pContext->LocalToString(params[1], &propName); - int iElement = params[2]; - PropType iPropType = static_cast(params[3]); - IPluginFunction * pFunction = pContext->GetFunctionById(params[4]); - for (int i = 0; i < g_HooksGamerules.Count(); i++) - { - if (g_HooksGamerules[i].Element == iElement && g_HooksGamerules[i].sCallbackInfo.iCallbackType == CallBackType::Callback_PluginFunction && g_HooksGamerules[i].PropType == iPropType && g_HooksGamerules[i].sCallbackInfo.pCallback == (void *)pFunction && !strcmp(g_HooksGamerules[i].pVar->GetName(), propName)) - { - g_SendProxyManager.UnhookProxyGamerules(i); - return 1; - } - } - return 0; + return g_pSendPropHookManager->IsEntityHooked(index, pProp, element, pFunc); } -static cell_t Native_IsHookedArray(IPluginContext * pContext, const cell_t * params) +static cell_t Native_HookGameRules(IPluginContext * pContext, const cell_t * params) { - int objectID = params[1]; - char * propName; - pContext->LocalToString(params[2], &propName); - int iElement = params[3]; - - for (int i = 0; i < g_Hooks.Count(); i++) + constexpr cell_t PARAM_COUNT = 4; + if (params[0] < PARAM_COUNT) { - if (g_Hooks[i].sCallbackInfo.pOwner == (void *)pContext && g_Hooks[i].objectID == objectID && g_Hooks[i].Element == iElement && strcmp(propName, g_Hooks[i].pVar->GetName()) == 0) - return 1; + pContext->ReportError("Expected %d params, found %d", PARAM_COUNT, params[0]); + return false; } - return 0; -} - -static cell_t Native_IsHookedArrayGameRules(IPluginContext * pContext, const cell_t * params) -{ - char * propName; - pContext->LocalToString(params[1], &propName); - int iElement = params[2]; - for (int i = 0; i < g_HooksGamerules.Count(); i++) + CBaseEntity *pGameRulesProxy = GetGameRulesProxyEnt(); + if (!pGameRulesProxy) { - if (g_HooksGamerules[i].sCallbackInfo.pOwner == (void *)pContext && g_HooksGamerules[i].Element == iElement && strcmp(propName, g_HooksGamerules[i].pVar->GetName()) == 0) - return 1; + pContext->ReportError("MGameRulesProxy entity not found. (Maybe try hooking later after \"round_start\")."); + return false; } - return 0; -} - -static cell_t Native_HookPropChangeArray(IPluginContext * pContext, const cell_t * params) -{ - if (params[1] < 0 || params[1] >= g_iEdictCount) - return pContext->ThrowNativeError("Invalid Edict Index %d", params[1]); - - int entity = params[1]; - char * name; - edict_t * pEnt = gamehelpers->EdictOfIndex(entity); - pContext->LocalToString(params[2], &name); - ServerClass * sc = pEnt->GetNetworkable()->GetServerClass(); - - if (!sc) - return pContext->ThrowNativeError("Cannot find ServerClass for entity %d", entity); - - sm_sendprop_info_t info; - gamehelpers->FindSendPropInfo(sc->GetName(), name, &info); - if (!info.prop) - return pContext->ThrowNativeError("Could not find prop %s", name); + char *propname = nullptr; + SendProp *pProp = nullptr; + int index = gamehelpers->EntityToBCompatRef(pGameRulesProxy); - SendTable * st = info.prop->GetDataTable(); + pContext->LocalToString(params[1], &propname); + PropType type = static_cast(params[2]); + IPluginFunction *pFunc = pContext->GetFunctionById(params[3]); + int element = params[4]; - if (!st) - return pContext->ThrowNativeError("Prop %s does not contain any elements", name); - - int element = params[3]; - SendProp * pProp = st->GetProp(element); - - if (!pProp) - return pContext->ThrowNativeError("Could not find element %d in %s", element, info.prop->GetName()); - - PropType propType = static_cast(params[4]); - - if (!IsPropValid(pProp, propType)) - switch (propType) - { - case PropType::Prop_Int: - return pContext->ThrowNativeError("Prop %s is not an int!", pProp->GetName()); - case PropType::Prop_Float: - return pContext->ThrowNativeError("Prop %s is not a float!", pProp->GetName()); - case PropType::Prop_String: - return pContext->ThrowNativeError("Prop %s is not a string!", pProp->GetName()); - case PropType::Prop_Vector: - return pContext->ThrowNativeError("Prop %s is not a vector!", pProp->GetName()); - default: - return pContext->ThrowNativeError("Unsupported prop type %d", propType); - } + UTIL_FindSendProp(pProp, pContext, index, propname, true, type, element); + if (pProp == nullptr) + return false; - PropChangeHook hook; - hook.objectID = entity; - hook.Offset = info.actual_offset + pProp->GetOffset(); - hook.pVar = pProp; - CallBackInfo sCallInfo; - sCallInfo.iCallbackType = CallBackType::Callback_PluginFunction; - sCallInfo.pCallback = (void *)pContext->GetFunctionById(params[5]); - sCallInfo.pOwner = (void *)pContext; - if (!g_SendProxyManager.AddChangeHookToList(hook, &sCallInfo)) - return pContext->ThrowNativeError("Entity %d isn't valid", entity); - return 1; + if (g_pSendPropHookManager->IsEntityHooked(index, pProp, element, pFunc)) + return true; + + return g_pSendPropHookManager->HookEntity(index, pProp, element, type, pFunc); } -static cell_t Native_UnhookPropChangeArray(IPluginContext * pContext, const cell_t * params) +static cell_t Native_UnhookGameRules(IPluginContext * pContext, const cell_t * params) { - if (params[1] < 0 || params[1] >= g_iEdictCount) - return pContext->ThrowNativeError("Invalid Edict Index %d", params[1]); - - int entity = params[1]; - char * name; - edict_t * pEnt = gamehelpers->EdictOfIndex(entity); - pContext->LocalToString(params[2], &name); - ServerClass * sc = pEnt->GetNetworkable()->GetServerClass(); - - if (!sc) - return pContext->ThrowNativeError("Cannot find ServerClass for entity %d", entity); - - sm_sendprop_info_t info; - gamehelpers->FindSendPropInfo(sc->GetName(), name, &info); - SendTable * st = info.prop->GetDataTable(); - - if (!st) - return pContext->ThrowNativeError("Prop %s does not contain any elements", name); - - int element = params[3]; - SendProp * pProp = st->GetProp(element); - - if (!pProp) - return pContext->ThrowNativeError("Could not find element %d in %s", element, info.prop->GetName()); - - IPluginFunction * callback = pContext->GetFunctionById(params[4]); - - for (int i = 0; i < g_ChangeHooks.Count(); i++) + constexpr cell_t PARAM_COUNT = 3; + if (params[0] < PARAM_COUNT) { - if (g_ChangeHooks[i].objectID == entity && g_ChangeHooks[i].pVar == info.prop) - { - CallBackInfo sInfo; - sInfo.pCallback = callback; - sInfo.pOwner = (void *)pContext; - sInfo.iCallbackType = CallBackType::Callback_PluginFunction; - g_SendProxyManager.UnhookChange(i, &sInfo); - break; - } + pContext->ReportError("Expected %d params, found %d", PARAM_COUNT, params[0]); + return false; } - return 1; -} -static cell_t Native_HookPropChangeArrayGameRules(IPluginContext * pContext, const cell_t * params) -{ - char * name; - pContext->LocalToString(params[1], &name); - sm_sendprop_info_t info; + CBaseEntity *pGameRulesProxy = GetGameRulesProxyEnt(); + if (!pGameRulesProxy) + { + pContext->ReportError("MGameRulesProxy entity not found. (Maybe try hooking later after \"round_start\")."); + return false; + } - gamehelpers->FindSendPropInfo(g_szGameRulesProxy, name, &info); + char *propname = nullptr; + SendProp *pProp = nullptr; + int index = gamehelpers->EntityToBCompatRef(pGameRulesProxy); - if (!info.prop) - return pContext->ThrowNativeError("Could not find prop %s", name); - - SendTable * st = info.prop->GetDataTable(); + pContext->LocalToString(params[1], &propname); + IPluginFunction *pFunc = pContext->GetFunctionById(params[2]); + int element = params[3]; - if (!st) - return pContext->ThrowNativeError("Prop %s does not contain any elements", name); - + UTIL_FindSendProp(pProp, pContext, index, propname, false, PropType::Prop_Max, element); - int element = params[2]; - SendProp * pProp = st->GetProp(element); + if (pProp == nullptr) + return false; - if (!pProp) - return pContext->ThrowNativeError("Could not find element %d in %s", element, info.prop->GetName()); - - PropType propType = static_cast(params[3]); - if (!IsPropValid(pProp, propType)) - switch (propType) - { - case PropType::Prop_Int: - return pContext->ThrowNativeError("Prop %s is not an int!", pProp->GetName()); - case PropType::Prop_Float: - return pContext->ThrowNativeError("Prop %s is not a float!", pProp->GetName()); - case PropType::Prop_String: - return pContext->ThrowNativeError("Prop %s is not a string!", pProp->GetName()); - case PropType::Prop_Vector: - return pContext->ThrowNativeError("Prop %s is not a vector!", pProp->GetName()); - default: - return pContext->ThrowNativeError("Unsupported prop type %d", propType); - } + if (!g_pSendPropHookManager->IsEntityHooked(index, pProp, element, pFunc)) + return false; - PropChangeHookGamerules hook; - hook.Offset = info.actual_offset + pProp->GetOffset(); - hook.pVar = pProp; - CallBackInfo sCallInfo; - sCallInfo.iCallbackType = CallBackType::Callback_PluginFunction; - sCallInfo.pCallback = (void *)pContext->GetFunctionById(params[4]); - sCallInfo.pOwner = (void *)pContext; - if (!g_SendProxyManager.AddChangeHookToListGamerules(hook, &sCallInfo)) - return pContext->ThrowNativeError("Prop type %d isn't valid", pProp->GetType()); //should never happen - return 1; + g_pSendPropHookManager->UnhookEntity(index, pProp, element, pFunc); + return true; } -static cell_t Native_UnhookPropChangeArrayGameRules(IPluginContext * pContext, const cell_t * params) +static cell_t Native_IsHookedGameRules(IPluginContext * pContext, const cell_t * params) { - char * name; - pContext->LocalToString(params[1], &name); - sm_sendprop_info_t info; - gamehelpers->FindSendPropInfo(g_szGameRulesProxy, name, &info); - - if (!info.prop) - return pContext->ThrowNativeError("Could not find prop %s", name); - - SendTable * st = info.prop->GetDataTable(); - - if (!st) - return pContext->ThrowNativeError("Prop %s does not contain any elements", name); - - int element = params[2]; - SendProp * pProp = st->GetProp(element); - - if (!pProp) - return pContext->ThrowNativeError("Could not find element %d in %s", element, info.prop->GetName()); - - IPluginFunction * callback = pContext->GetFunctionById(params[3]); - - for (int i = 0; i < g_ChangeHooksGamerules.Count(); i++) + constexpr cell_t PARAM_COUNT = 3; + if (params[0] < PARAM_COUNT) { - if (g_ChangeHooksGamerules[i].pVar == info.prop) - { - CallBackInfo sInfo; - sInfo.pCallback = callback; - sInfo.pOwner = (void *)pContext; - sInfo.iCallbackType = CallBackType::Callback_PluginFunction; - g_SendProxyManager.UnhookChangeGamerules(i, &sInfo); - break; - } + pContext->ReportError("Expected %d params, found %d", PARAM_COUNT, params[0]); + return false; } - return 1; -} - -static cell_t Native_IsPropChangeHooked(IPluginContext * pContext, const cell_t * params) -{ - int objectID = params[1]; - char * propName; - pContext->LocalToString(params[2], &propName); - for (int i = 0; i < g_ChangeHooks.Count(); i++) + CBaseEntity *pGameRulesProxy = GetGameRulesProxyEnt(); + if (!pGameRulesProxy) { - if (g_ChangeHooks[i].objectID == objectID && strcmp(propName, g_ChangeHooks[i].pVar->GetName()) == 0) - { - auto pCallbacks = g_ChangeHooks[i].vCallbacksInfo; - if (pCallbacks->Count()) - { - for (int j = 0; j < pCallbacks->Count(); j++) - if ((*pCallbacks)[j].iCallbackType == CallBackType::Callback_PluginFunction && (*pCallbacks)[j].pOwner == (void *)pContext) - { - return 1; - } - } - break; - } + pContext->ReportError("MGameRulesProxy entity not found. (Maybe try hooking later after \"round_start\")."); + return false; } - return 0; -} - -static cell_t Native_IsPropChangeHookedGameRules(IPluginContext * pContext, const cell_t * params) -{ - char * propName; - pContext->LocalToString(params[1], &propName); - for (int i = 0; i < g_ChangeHooksGamerules.Count(); i++) - { - if (strcmp(propName, g_ChangeHooksGamerules[i].pVar->GetName()) == 0) - { - auto pCallbacks = g_ChangeHooksGamerules[i].vCallbacksInfo; - if (pCallbacks->Count()) - { - for (int j = 0; j < pCallbacks->Count(); j++) - if ((*pCallbacks)[j].iCallbackType == CallBackType::Callback_PluginFunction && (*pCallbacks)[j].pOwner == (void *)pContext) - { - return 1; - } - } - break; - } - } - return 0; -} + char *propname = nullptr; + SendProp *pProp = nullptr; + int index = gamehelpers->EntityToBCompatRef(pGameRulesProxy); -static cell_t Native_IsPropChangeArrayHooked(IPluginContext * pContext, const cell_t * params) -{ - int objectID = params[1]; - char * propName; - pContext->LocalToString(params[2], &propName); + pContext->LocalToString(params[1], &propname); + IPluginFunction *pFunc = pContext->GetFunctionById(params[2]); int element = params[3]; - for (int i = 0; i < g_ChangeHooks.Count(); i++) - { - if (g_ChangeHooks[i].Element == element && g_ChangeHooks[i].objectID == objectID && strcmp(propName, g_ChangeHooks[i].pVar->GetName()) == 0) - { - auto pCallbacks = g_ChangeHooks[i].vCallbacksInfo; - if (pCallbacks->Count()) - { - for (int j = 0; j < pCallbacks->Count(); j++) - if ((*pCallbacks)[j].iCallbackType == CallBackType::Callback_PluginFunction && (*pCallbacks)[j].pOwner == (void *)pContext) - { - return 1; - } - } - break; - } - } - return 0; -} + UTIL_FindSendProp(pProp, pContext, index, propname, false, PropType::Prop_Max, element); -static cell_t Native_IsPropChangeArrayHookedGameRules(IPluginContext * pContext, const cell_t * params) -{ - char * propName; - pContext->LocalToString(params[1], &propName); - int element = params[2]; + if (pProp == nullptr) + return false; - for (int i = 0; i < g_ChangeHooksGamerules.Count(); i++) - { - if (g_ChangeHooksGamerules[i].Element == element && strcmp(propName, g_ChangeHooksGamerules[i].pVar->GetName()) == 0) - { - auto pCallbacks = g_ChangeHooksGamerules[i].vCallbacksInfo; - if (pCallbacks->Count()) - { - for (int j = 0; j < pCallbacks->Count(); j++) - if ((*pCallbacks)[j].iCallbackType == CallBackType::Callback_PluginFunction && (*pCallbacks)[j].pOwner == (void *)pContext) - { - return 1; - } - } - break; - } - } - return 0; + return g_pSendPropHookManager->IsEntityHooked(index, pProp, element, pFunc); } const sp_nativeinfo_t g_MyNatives[] = { - {"SendProxy_Hook", Native_Hook}, + {"SendProxy_HookEntity", Native_Hook}, {"SendProxy_HookGameRules", Native_HookGameRules}, - {"SendProxy_HookArrayProp", Native_HookArrayProp}, - {"SendProxy_UnhookArrayProp", Native_UnhookArrayProp}, - {"SendProxy_Unhook", Native_Unhook}, + {"SendProxy_UnhookEntity", Native_Unhook}, {"SendProxy_UnhookGameRules", Native_UnhookGameRules}, - {"SendProxy_IsHooked", Native_IsHooked}, + {"SendProxy_IsHookedEntity", Native_IsHooked}, {"SendProxy_IsHookedGameRules", Native_IsHookedGameRules}, - {"SendProxy_HookPropChange", Native_HookPropChange}, - {"SendProxy_HookPropChangeGameRules", Native_HookPropChangeGameRules}, - {"SendProxy_UnhookPropChange", Native_UnhookPropChange}, - {"SendProxy_UnhookPropChangeGameRules", Native_UnhookPropChangeGameRules}, - {"SendProxy_HookArrayPropGamerules", Native_HookArrayPropGamerules}, - {"SendProxy_UnhookArrayPropGamerules", Native_UnhookArrayPropGamerules}, - {"SendProxy_IsHookedArrayProp", Native_IsHookedArray}, - {"SendProxy_IsHookedArrayPropGamerules", Native_IsHookedArrayGameRules}, - {"SendProxy_HookPropChangeArray", Native_HookPropChangeArray}, - {"SendProxy_UnhookPropChangeArray", Native_UnhookPropChangeArray}, - {"SendProxy_HookPropChangeArrayGameRules", Native_HookPropChangeArrayGameRules}, - {"SendProxy_UnhookPropChangeArrayGameRules", Native_UnhookPropChangeArrayGameRules}, - {"SendProxy_IsPropChangeHooked", Native_IsPropChangeHooked}, - {"SendProxy_IsPropChangeHookedGameRules", Native_IsPropChangeHookedGameRules}, - {"SendProxy_IsPropChangeArrayHooked", Native_IsPropChangeArrayHooked}, - {"SendProxy_IsPropChangeArrayHookedGameRules", Native_IsPropChangeArrayHookedGameRules}, - {"SendProxy_HookPropChangeSafe", Native_HookPropChange}, - {"SendProxy_HookPropChangeGameRulesSafe", Native_HookPropChangeGameRules}, - //Probably add listeners for plugins? - {NULL, NULL} -}; \ No newline at end of file + {nullptr, nullptr}}; \ No newline at end of file diff --git a/extension/sdk/typeinfo.h b/extension/sdk/typeinfo.h new file mode 100644 index 0000000..70cdb57 --- /dev/null +++ b/extension/sdk/typeinfo.h @@ -0,0 +1,2 @@ +// Simple hack for missing include "typeinfo.h" +#include \ No newline at end of file diff --git a/extension/sendprop_hookmanager.cpp b/extension/sendprop_hookmanager.cpp new file mode 100644 index 0000000..ca39c87 --- /dev/null +++ b/extension/sendprop_hookmanager.cpp @@ -0,0 +1,267 @@ +#include "sendprop_hookmanager.h" +#include "clientpacks_detours.h" +#include + +void GlobalProxy(const SendProp *pProp, const void *pStructBase, const void *pData, DVariant *pOut, int iElement, int objectID); + +SendProxyHook::SendProxyHook(SendProp *pProp, SendVarProxyFn pfnProxy) + : m_pProp(pProp) +{ + m_fnRealProxy = m_pProp->GetProxyFn(); + m_pProp->SetProxyFn( pfnProxy ); +} + +SendProxyHook::~SendProxyHook() +{ + m_pProp->SetProxyFn( m_fnRealProxy ); + g_pSendPropHookManager->RemoveHook(m_pProp); +} + +SendPropHookManager::SendPropHookManager() +{ + m_propHooks.init(); + m_entityInfos.init(); +} + +void SendPropHookManager::Clear() +{ + m_propHooks.clear(); + m_entityInfos.clear(); + ClientPacksDetour::Clear(); +} + +void SendPropHookManager::RemoveHook(const SendProp *pProp) +{ + m_propHooks.removeIfExists(pProp); +} + +void SendPropHookManager::RemoveEntity(int entity, std::function pred) +{ + auto r = m_entityInfos.find(entity); + if (!r.found()) + return; + + r->value.list.remove_if(pred); + + if (r->value.list.empty()) + { + OnEntityLeaveHook(entity); + m_entityInfos.remove(r); + } +} + +void SendPropHookManager::RemoveEntity(SendPropEntityInfoMap::iterator it, std::function pred) +{ + it->value.list.remove_if(pred); + + if (it->value.list.empty()) + { + OnEntityLeaveHook(it->key); + it.erase(); + } +} + +void SendPropHookManager::OnEntityEnterHook(int entity) +{ + ClientPacksDetour::OnEntityHooked(entity); +} + +void SendPropHookManager::OnEntityLeaveHook(int entity) +{ + ClientPacksDetour::OnEntityUnhooked(entity); +} + +bool SendPropHookManager::HookEntity(int entity, SendProp *pProp, int element, PropType type, IPluginFunction *pFunc) +{ + SendPropHook hook; + hook.element = element; + hook.type = type; + hook.fnProcess = SendProxyPluginCallback; + hook.pCallback = pFunc; + hook.pOwner = pFunc->GetParentRuntime(); + + auto i = m_propHooks.findForAdd(pProp); + Assert(!i.found() || !i->value.expired()); + + if (i.found()) + { + hook.proxy = i->value.lock(); + } + + if (!hook.proxy) + { + hook.proxy = std::make_shared(pProp, GlobalProxy); + m_propHooks.add(i, pProp, hook.proxy); + } + + auto ii = m_entityInfos.findForAdd(entity); + if (!ii.found()) + { + m_entityInfos.add(ii, entity); + OnEntityEnterHook(entity); + } + ii->value.list.emplace_front(std::move(hook)); + + return true; +} + +void SendPropHookManager::UnhookEntity(int entity, const SendProp *pProp, int element, const void *pCallback) +{ + RemoveEntity(entity, [&](const SendPropHook &hook) + { return hook.proxy->GetProp() == pProp && hook.element == element && hook.pCallback == pCallback; }); +} + +void SendPropHookManager::UnhookEntityAll(int entity) +{ + RemoveEntity(entity, [&](const SendPropHook &hook) + { return true; }); +} + +void SendPropHookManager::OnPluginUnloaded(IPlugin *plugin) +{ + for (auto it = m_entityInfos.iter(); !it.empty(); it.next()) + { + RemoveEntity(it, [pOwner = plugin->GetRuntime()](const SendPropHook &hook) + { return hook.pOwner == pOwner; }); + } +} + +void SendPropHookManager::OnExtentionUnloaded(IExtension *ext) +{ + for (auto it = m_entityInfos.iter(); !it.empty(); it.next()) + { + RemoveEntity(it, [pOwner = ext](const SendPropHook &hook) + { return hook.pOwner == pOwner; }); + } +} + +SendPropEntityInfo* SendPropHookManager::GetEntityHooks(int entity) +{ + auto r = m_entityInfos.find(entity); + if (!r.found()) + return nullptr; + + return &r->value; +} + +std::shared_ptr SendPropHookManager::GetPropHook(const SendProp *pProp) +{ + auto r = m_propHooks.find(pProp); + if (!r.found() || r->value.expired()) + return nullptr; + + return r->value.lock(); +} + +bool SendPropHookManager::IsPropHooked(const SendProp *pProp) const +{ + auto r = m_propHooks.find(pProp); + return r.found() && !r->value.expired(); +} + +bool SendPropHookManager::IsEntityHooked(int entity) const +{ + auto r = m_entityInfos.find(entity); + return r.found(); +} + +bool SendPropHookManager::IsEntityHooked(int entity, const SendProp *pProp, int element, const IPluginFunction *pFunc) const +{ + auto r = m_entityInfos.find(entity); + if (!r.found()) + return false; + + return std::any_of(r->value.list.cbegin(), r->value.list.cend(), [&](const SendPropHook &hook) { + return hook.proxy->GetProp() == pProp + && hook.pCallback == (void *)pFunc + && ((hook.proxy->GetProp()->GetType() != DPT_Array && hook.proxy->GetProp()->GetType() != DPT_DataTable) + || hook.element == element); + }); +} + +bool SendPropHookManager::IsAnyEntityHooked() const +{ + return m_entityInfos.elements() > 0; +} + +class TailInvoker +{ +public: + explicit TailInvoker(std::function fn) : m_call(fn) {} + TailInvoker() = delete; + TailInvoker(const TailInvoker &other) = delete; + ~TailInvoker() { m_call(); } + +private: + std::function m_call; +}; + +// !! MUST BE CALLED ON MAIN THREAD +void GlobalProxy(const SendProp *pProp, const void *pStructBase, const void * pData, DVariant *pOut, int iElement, int objectID) +{ + auto pHook = g_pSendPropHookManager->GetPropHook(pProp); + Assert(pHook != nullptr); + if (!pHook) + { + g_pSM->LogError(myself, "FATAL: Leftover entity proxy %s", pProp->GetName()); + return; + } + + ProxyVariant *pOverride = nullptr; + TailInvoker finally( + [&]() -> void + { + if (pOverride) { + const void *pNewData = nullptr; + std::visit(overloaded { + [&pNewData](const auto &arg) { pNewData = &arg; }, + [&pNewData](const CBaseHandle &arg) { pNewData = &arg; }, + [&pNewData](const std::string &arg) { pNewData = arg.c_str(); }, + }, *pOverride); + + pHook->CallOriginal(pStructBase, pNewData, pOut, iElement, objectID); + } else { + pHook->CallOriginal(pStructBase, pData, pOut, iElement, objectID); + } + } + ); + + SendPropEntityInfo *pEntHook = g_pSendPropHookManager->GetEntityHooks(objectID); + if (!pEntHook) + return; + + int client = ClientPacksDetour::GetCurrentClientIndex(); + AssertFatal(client > 0); + if (client == -1) + return; + + for (const SendPropHook& hook : pEntHook->list) + { + if (hook.proxy->GetProp() != pProp) + continue; + + if (pProp->IsInsideArray() && hook.element != iElement) + continue; + + if (hook.type == PropType::Prop_Int) { + pEntHook->data = *reinterpret_cast(pData); + } else if (hook.type == PropType::Prop_Float) { + pEntHook->data = *reinterpret_cast(pData); + } else if (hook.type == PropType::Prop_Vector) { + pEntHook->data = *reinterpret_cast(pData); + } else if (hook.type == PropType::Prop_String) { + pEntHook->data.emplace(reinterpret_cast(pData), DT_MAX_STRING_BUFFERSIZE); + } else if (hook.type == PropType::Prop_EHandle) { + pEntHook->data = *reinterpret_cast(pData); + } else { + g_pSM->LogError(myself, "%s: SendProxy report: Unknown prop type (%s).", __func__, pProp->GetName()); + continue; + } + + if (hook.fnProcess(hook.pCallback, pProp, pEntHook->data, iElement, objectID, client)) + { + pOverride = &pEntHook->data; + return; + } + } +} \ No newline at end of file diff --git a/extension/sendprop_hookmanager.h b/extension/sendprop_hookmanager.h new file mode 100644 index 0000000..455d490 --- /dev/null +++ b/extension/sendprop_hookmanager.h @@ -0,0 +1,91 @@ +#ifndef _SENDPROP_HOOKMANAGER_H +#define _SENDPROP_HOOKMANAGER_H + +#include "extension.h" +#include "sm_hashmap.h" +#include "sendproxy_callback.h" +#include "sendproxy_variant.h" +#include +#include +#include + +class SendProp; + +class SendProxyHook +{ +public: + explicit SendProxyHook(SendProp *pProp, SendVarProxyFn pfnProxy); + ~SendProxyHook(); + SendProxyHook(const SendProxyHook &other) = delete; + + const SendProp* GetProp() const { return m_pProp; } + void CallOriginal(const void *pStructBase, const void *pData, DVariant *pOut, int iElement, int objectID) + { + m_fnRealProxy(m_pProp, pStructBase, pData, pOut, iElement, objectID); + } + +private: + SendProp *m_pProp; + SendVarProxyFn m_fnRealProxy; +}; + +struct SendPropHook +{ + std::shared_ptr proxy{nullptr}; + std::function fnProcess{nullptr}; + void *pCallback{nullptr}; + void *pOwner{nullptr}; + int element{-1}; + PropType type{PropType::Prop_Max}; +}; + +struct SendPropEntityInfo +{ + std::forward_list list; + ProxyVariant data; +}; + +using SendPropHookMap = ke::HashMap, ke::PointerPolicy>; +using SendPropEntityInfoMap = ke::HashMap; + +class SendPropHookManager +{ +public: + SendPropHookManager(); + SendPropHookManager(const SendPropHookManager &other) = delete; + SendPropHookManager(SendPropHookManager &&other) = delete; + + bool HookEntity(int entity, SendProp *pProp, int element, PropType type, IPluginFunction *callback); + void UnhookEntity(int entity, const SendProp *pProp, int element, const void *callback); + void UnhookEntityAll(int entity); + SendPropEntityInfo* GetEntityHooks(int entity); + + void OnPluginUnloaded(IPlugin *plugin); + void OnExtentionUnloaded(IExtension *ext); + + std::shared_ptr GetPropHook(const SendProp *pProp); + bool IsPropHooked(const SendProp *pProp) const; + bool IsEntityHooked(int entity) const; + bool IsEntityHooked(int entity, const SendProp *pProp, int element, const IPluginFunction *pFunc) const; + bool IsAnyEntityHooked() const; + + void Clear(); + +protected: + friend class SendProxyHook; + void RemoveHook(const SendProp *pProp); + + void RemoveEntity(int entity, std::function pred); + void RemoveEntity(SendPropEntityInfoMap::iterator it, std::function pred); + + void OnEntityEnterHook(int entity); + void OnEntityLeaveHook(int entity); + +private: + SendPropHookMap m_propHooks; + SendPropEntityInfoMap m_entityInfos; +}; + +extern SendPropHookManager *g_pSendPropHookManager; + +#endif \ No newline at end of file diff --git a/extension/sendproxy_callback.cpp b/extension/sendproxy_callback.cpp new file mode 100644 index 0000000..f2061db --- /dev/null +++ b/extension/sendproxy_callback.cpp @@ -0,0 +1,57 @@ +#include "sendproxy_callback.h" +#include "dt_send.h" +#include + +bool SendProxyPluginCallback(void *callback, const SendProp *pProp, ProxyVariant &variant, int element, int entity, int client) +{ + auto func = static_cast(callback); + + cell_t result = Pl_Continue; + func->PushCell(entity); + func->PushString(pProp->GetName()); + + ProxyVariant temp = variant; + cell_t iEntity = -1; + + std::visit(overloaded { + [func](int &arg) { func->PushCellByRef(reinterpret_cast(&arg)); }, + [func](float &arg) { func->PushFloatByRef(&arg); }, + [func](std::string& arg) { func->PushStringEx(arg.data(), arg.capacity(), SM_PARAM_STRING_UTF8 | SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK); }, + [func](Vector &arg) { func->PushArray(reinterpret_cast(&arg), 3, SM_PARAM_COPYBACK); }, + [func, &iEntity](CBaseHandle &arg) { + if (edict_t *edict = gamehelpers->GetHandleEntity(arg)) + iEntity = gamehelpers->IndexOfEdict(edict); + func->PushCellByRef(&iEntity); + }, + }, temp); + + func->PushCell(element); + func->PushCell(client); + func->Execute(&result); + + if (result == Pl_Changed) + { + bool intercept = true; + + std::visit(overloaded { + [&variant](auto &arg) { + variant = arg; + }, + + [func, iEntity, &intercept](CBaseHandle &arg) { + if (edict_t *edict = gamehelpers->EdictOfIndex(iEntity)) { + gamehelpers->SetHandleEntity(arg, edict); + } else { + func->GetParentRuntime()->GetDefaultContext()->BlamePluginError( + func, "Unexpected invalid edict index (%d)", iEntity); + + intercept = false; + } + }, + }, temp); + + return intercept; + } + + return false; +} diff --git a/extension/sendproxy_callback.h b/extension/sendproxy_callback.h new file mode 100644 index 0000000..b6fd311 --- /dev/null +++ b/extension/sendproxy_callback.h @@ -0,0 +1,14 @@ +#ifndef _SENDPROXY_CALLBACK_H +#define _SENDPROXY_CALLBACK_H + +#include "extension.h" +#include "sendproxy_variant.h" + +class SendProp; + +using SendProxyCallback = bool (void *callback, const SendProp *pProp, ProxyVariant &variant, int element, int entity, int client); + +bool SendProxyPluginCallback(void *callback, const SendProp *pProp, ProxyVariant &variant, int element, int entity, int client); +// bool SendProxyExtCallback(void *callback, const SendProp *pProp, ProxyVariant &variant, int element, int entity, int client); + +#endif \ No newline at end of file diff --git a/extension/sendproxy_variant.h b/extension/sendproxy_variant.h new file mode 100644 index 0000000..17262b2 --- /dev/null +++ b/extension/sendproxy_variant.h @@ -0,0 +1,17 @@ +#ifndef _SENDPROXY_VARIANT_H +#define _SENDPROXY_VARIANT_H + +#include +#include +#include "mathlib/vector.h" +#include "basehandle.h" + +using ProxyVariant = std::variant; + +// helper type for the visitor +template +struct overloaded : Ts... { using Ts::operator()...; }; +template +overloaded(Ts...) -> overloaded; + +#endif \ No newline at end of file diff --git a/extension/util.cpp b/extension/util.cpp new file mode 100644 index 0000000..63520bc --- /dev/null +++ b/extension/util.cpp @@ -0,0 +1,32 @@ +#include "util.h" + +edict_t *UTIL_EdictOfIndex(int index) +{ + if (index < 0 || index >= MAX_EDICTS) + return nullptr; + + return gamehelpers->EdictOfIndex(index); +} + +bool IsPropValid(const SendProp *prop, PropType type) +{ + switch (type) + { + case PropType::Prop_Int: + return prop->GetType() == DPT_Int; + + case PropType::Prop_EHandle: + return prop->GetType() == DPT_Int && prop->m_nBits == NUM_NETWORKED_EHANDLE_BITS; + + case PropType::Prop_Float: + return prop->GetType() == DPT_Float; + + case PropType::Prop_Vector: + return prop->GetType() == DPT_Vector || prop->GetType() == DPT_VectorXY; + + case PropType::Prop_String: + return prop->GetType() == DPT_String; + } + + return false; +} \ No newline at end of file diff --git a/extension/util.h b/extension/util.h new file mode 100644 index 0000000..f7c4a71 --- /dev/null +++ b/extension/util.h @@ -0,0 +1,71 @@ +#ifndef _SENDPROXY_UTIL_H +#define _SENDPROXY_UTIL_H + +#include "extension.h" + +#define GET_CONVAR(name) \ + name = g_pCVar->FindVar(#name); \ + if (name == nullptr) { \ + if (error != nullptr && maxlen != 0) { \ + ismm->Format(error, maxlen, "Could not find ConVar: " #name); \ + } \ + return false; \ + } + +edict_t *UTIL_EdictOfIndex(int index); +bool IsPropValid(const SendProp *prop, PropType type); + +class ConVarSaveSet +{ +public: + explicit ConVarSaveSet(ConVar *cvar, const char *value) + : m_cvar(cvar), m_savevalue(m_cvar->GetString()) + { + m_cvar->SetValue(value); + } + + ConVarSaveSet() = delete; + ConVarSaveSet(const ConVarSaveSet &other) = delete; + + ~ConVarSaveSet() + { + m_cvar->SetValue(m_savevalue.data()); + } + +private: + ConVar *m_cvar; + std::string m_savevalue; +}; + +inline edict_t *UTIL_EdictOfIndex(int index) +{ + if (index < 0 || index >= MAX_EDICTS) + return nullptr; + + return gamehelpers->EdictOfIndex(index); +} + +inline bool IsPropValid(const SendProp *prop, PropType type) +{ + switch (type) + { + case PropType::Prop_Int: + return prop->GetType() == DPT_Int; + + case PropType::Prop_EHandle: + return prop->GetType() == DPT_Int && prop->m_nBits == NUM_NETWORKED_EHANDLE_BITS; + + case PropType::Prop_Float: + return prop->GetType() == DPT_Float; + + case PropType::Prop_Vector: + return prop->GetType() == DPT_Vector || prop->GetType() == DPT_VectorXY; + + case PropType::Prop_String: + return prop->GetType() == DPT_String; + } + + return false; +} + +#endif \ No newline at end of file diff --git a/extension/wrappers.h b/extension/wrappers.h index 0847983..b0b7dbc 100644 --- a/extension/wrappers.h +++ b/extension/wrappers.h @@ -2,16 +2,139 @@ #define _WRAPPERS_H_INC_ #include -#include "tier0/threadtools.h" #include "tier1/utlvector.h" #include "tier1/utllinkedlist.h" #include "tier1/mempool.h" +#include "iclient.h" + +#if defined( _LINUX ) || defined( _OSX ) +// linux implementation +namespace Wrappers { + +inline int32 ThreadInterlockedIncrement( int32 volatile *p ) +{ + Assert( (size_t)p % 4 == 0 ); + return __sync_fetch_and_add( p, 1 ) + 1; +} + +inline int32 ThreadInterlockedDecrement( int32 volatile *p ) +{ + Assert( (size_t)p % 4 == 0 ); + return __sync_fetch_and_add( p, -1 ) - 1; +} + +inline int32 ThreadInterlockedExchange( int32 volatile *p, int32 value ) +{ + Assert( (size_t)p % 4 == 0 ); + int32 nRet; + + // Note: The LOCK instruction prefix is assumed on the XCHG instruction and GCC gets very confused on the Mac when we use it. + __asm __volatile( + "xchgl %2,(%1)" + : "=r" (nRet) + : "r" (p), "0" (value) + : "memory"); + return nRet; +} + +inline int32 ThreadInterlockedExchangeAdd( int32 volatile *p, int32 value ) +{ + Assert( (size_t)p % 4 == 0 ); + return __sync_fetch_and_add( p, value ); +} +inline int32 ThreadInterlockedCompareExchange( int32 volatile *p, int32 value, int32 comperand ) +{ + Assert( (size_t)p % 4 == 0 ); + return __sync_val_compare_and_swap( p, comperand, value ); +} + +inline bool ThreadInterlockedAssignIf( int32 volatile *p, int32 value, int32 comperand ) +{ + Assert( (size_t)p % 4 == 0 ); + return __sync_bool_compare_and_swap( p, comperand, value ); +} + +} + +template +class CInterlockedIntTHack +{ +public: + CInterlockedIntTHack() : m_value( 0 ) { COMPILE_TIME_ASSERT( sizeof(T) == sizeof(int32) ); } + CInterlockedIntTHack( T value ) : m_value( value ) {} + + operator T() const { return m_value; } + + bool operator!() const { return ( m_value == 0 ); } + bool operator==( T rhs ) const { return ( m_value == rhs ); } + bool operator!=( T rhs ) const { return ( m_value != rhs ); } + + T operator++() { return (T)Wrappers::ThreadInterlockedIncrement( (int32 *)&m_value ); } + T operator++(int) { return operator++() - 1; } + + T operator--() { return (T)Wrappers::ThreadInterlockedDecrement( (int32 *)&m_value ); } + T operator--(int) { return operator--() + 1; } + + bool AssignIf( T conditionValue, T newValue ) { return Wrappers::ThreadInterlockedAssignIf( (int32 *)&m_value, (int32)newValue, (int32)conditionValue ); } + + T operator=( T newValue ) { Wrappers::ThreadInterlockedExchange((int32 *)&m_value, newValue); return m_value; } + + void operator+=( T add ) { Wrappers::ThreadInterlockedExchangeAdd( (int32 *)&m_value, (int32)add ); } + void operator-=( T subtract ) { operator+=( -subtract ); } + void operator*=( T multiplier ) { + T original, result; + do + { + original = m_value; + result = original * multiplier; + } while ( !AssignIf( original, result ) ); + } + void operator/=( T divisor ) { + T original, result; + do + { + original = m_value; + result = original / divisor; + } while ( !AssignIf( original, result ) ); + } + + T operator+( T rhs ) const { return m_value + rhs; } + T operator-( T rhs ) const { return m_value - rhs; } + +private: + volatile T m_value; +}; +#endif class ServerClass; class ClientClass; -class CFrameSnapshotEntry; -class CHLTVEntityData; class CEventInfo; +class CChangeFrameList; + +#define INVALID_PACKED_ENTITY_HANDLE (0) +typedef int PackedEntityHandle_t; + +struct UnpackedDataCache_t; + +//----------------------------------------------------------------------------- +// Purpose: Individual entity data, did the entity exist and what was it's serial number +//----------------------------------------------------------------------------- +class CFrameSnapshotEntry +{ +public: + ServerClass* m_pClass; + int m_nSerialNumber; + // Keeps track of the fullpack info for this frame for all entities in any pvs: + PackedEntityHandle_t m_pPackedData; +}; + +// HLTV needs some more data per entity +class CHLTVEntityData +{ +public: + vec_t origin[3]; // entity position + unsigned int m_nNodeCluster; // if (1<<31) is set it's a node, otherwise a cluster +}; class CFrameSnapshot { @@ -28,7 +151,11 @@ class CFrameSnapshot } // Index info CFrameSnapshotManager::m_FrameSnapshots. +#if defined( _LINUX ) || defined( _OSX ) + CInterlockedIntTHack m_ListIndex; +#else CInterlockedInt m_ListIndex; +#endif // Associated frame. int m_nTickCount; // = sv.tickcount @@ -50,37 +177,78 @@ class CFrameSnapshot CUtlVector m_iExplicitDeleteSlots; // Snapshots auto-delete themselves when their refcount goes to zero. +#if defined( _LINUX ) || defined( _OSX ) + CInterlockedIntTHack m_nReferences; +#else CInterlockedInt m_nReferences; +#endif }; -class PackedEntity; +class PackedEntity +{ +public: + int GetSnapshotCreationTick() const; + + ServerClass *m_pServerClass; // Valid on the server + ClientClass *m_pClientClass; // Valid on the client + + int m_nEntityIndex; // Entity index. +#if defined( _LINUX ) || defined( _OSX ) + CInterlockedIntTHack m_ReferenceCount; +#else + CInterlockedInt m_ReferenceCount; // reference count; +#endif -#define INVALID_PACKED_ENTITY_HANDLE (0) -typedef int PackedEntityHandle_t; + CUtlVector m_Recipients; + + void *m_pData; // Packed data. + int m_nBits; // Number of bits used to encode. + CChangeFrameList *m_pChangeFrameList; // Only the most current + + // This is the tick this PackedEntity was created on + unsigned int m_nSnapshotCreationTick : 31; + unsigned int m_nShouldCheckCreationTick : 1; +}; + +inline int PackedEntity::GetSnapshotCreationTick() const +{ + return (int)m_nSnapshotCreationTick; +} -struct UnpackedDataCache_t; class CFrameSnapshotManager { public: virtual ~CFrameSnapshotManager( void ); - static void* s_pfnTakeTickSnapshot; - static ICallWrapper* s_callTakeTickSnapshot; - - CFrameSnapshot* TakeTickSnapshot(int tickcount) + static void* s_pfnCreateEmptySnapshot; + static ICallWrapper* s_callCreateEmptySnapshot; + CFrameSnapshot* CreateEmptySnapshot(int tickcount, int maxEntities) { struct { CFrameSnapshotManager *pThis; int tickcount; - } stack{ this, tickcount }; + int maxEntities; + } stack{ this, tickcount, maxEntities }; CFrameSnapshot *ret; - s_callTakeTickSnapshot->Execute(&stack, &ret); + s_callCreateEmptySnapshot->Execute(&stack, &ret); return ret; } + void AddEntityReference( PackedEntityHandle_t handle ) + { + m_PackedEntities[ handle ]->m_ReferenceCount++; + } + + static void* s_pfnRemoveEntityReference; + static ICallWrapper* s_callRemoveEntityReference; + void RemoveEntityReference( PackedEntityHandle_t handle ) + { + m_PackedEntities[ handle ]->m_ReferenceCount++; + } + public: - int pad[21]; + uint32_t pad[21]; CUtlFixedLinkedList m_PackedEntities; @@ -99,8 +267,23 @@ class CFrameSnapshotManager CUtlVector m_iExplicitDeleteSlots; }; -class CGameClient; +class CCheckTransmitInfo; +class CGameClient +{ +public: + int GetPlayerSlot() + { + IClient *pClient = reinterpret_cast((uint8_t*)this + 4); + return pClient->GetPlayerSlot(); + } +}; extern CFrameSnapshotManager* framesnapshotmanager; +#if SOURCE_ENGINE == SE_LEFT4DEAD || SOURCE_ENGINE == SE_LEFT4DEAD2 +constexpr int MAXPLAYERS = 32; +#else +constexpr int MAXPLAYERS = SM_MAXPLAYERS; +#endif + #endif \ No newline at end of file From 551aea3e9a7aad621666ba2dfcc030ef1e6f7441 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Mon, 18 Aug 2025 01:16:24 +0800 Subject: [PATCH 26/73] Fix missing "sm_hashmap.h" --- extension/sendprop_hookmanager.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/sendprop_hookmanager.h b/extension/sendprop_hookmanager.h index 455d490..259d76b 100644 --- a/extension/sendprop_hookmanager.h +++ b/extension/sendprop_hookmanager.h @@ -2,7 +2,7 @@ #define _SENDPROP_HOOKMANAGER_H #include "extension.h" -#include "sm_hashmap.h" +#include "am-hashmap.h" #include "sendproxy_callback.h" #include "sendproxy_variant.h" #include From d6f66f94b3787d6954a36e504df82580fd8f0c9b Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Mon, 18 Aug 2025 01:23:31 +0800 Subject: [PATCH 27/73] Fix missing hash policy --- extension/sendprop_hookmanager.h | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/extension/sendprop_hookmanager.h b/extension/sendprop_hookmanager.h index 259d76b..3375deb 100644 --- a/extension/sendprop_hookmanager.h +++ b/extension/sendprop_hookmanager.h @@ -45,8 +45,21 @@ struct SendPropEntityInfo ProxyVariant data; }; +namespace SendProxy +{ + struct IntHashMapPolicy + { + static inline bool matches(const int32_t lookup, const int32_t compare) { + return lookup == compare; + } + static inline uint32_t hash(const int32_t key) { + return ke::HashInt32(key); + } + }; +} + using SendPropHookMap = ke::HashMap, ke::PointerPolicy>; -using SendPropEntityInfoMap = ke::HashMap; +using SendPropEntityInfoMap = ke::HashMap; class SendPropHookManager { From 6834384fdf4e2eab01364e63f78f9da2a2efa79f Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Mon, 18 Aug 2025 01:32:43 +0800 Subject: [PATCH 28/73] Cleanup --- extension/util.cpp | 32 -------------------------------- extension/util.h | 3 --- 2 files changed, 35 deletions(-) delete mode 100644 extension/util.cpp diff --git a/extension/util.cpp b/extension/util.cpp deleted file mode 100644 index 63520bc..0000000 --- a/extension/util.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "util.h" - -edict_t *UTIL_EdictOfIndex(int index) -{ - if (index < 0 || index >= MAX_EDICTS) - return nullptr; - - return gamehelpers->EdictOfIndex(index); -} - -bool IsPropValid(const SendProp *prop, PropType type) -{ - switch (type) - { - case PropType::Prop_Int: - return prop->GetType() == DPT_Int; - - case PropType::Prop_EHandle: - return prop->GetType() == DPT_Int && prop->m_nBits == NUM_NETWORKED_EHANDLE_BITS; - - case PropType::Prop_Float: - return prop->GetType() == DPT_Float; - - case PropType::Prop_Vector: - return prop->GetType() == DPT_Vector || prop->GetType() == DPT_VectorXY; - - case PropType::Prop_String: - return prop->GetType() == DPT_String; - } - - return false; -} \ No newline at end of file diff --git a/extension/util.h b/extension/util.h index f7c4a71..6ab95f2 100644 --- a/extension/util.h +++ b/extension/util.h @@ -12,9 +12,6 @@ return false; \ } -edict_t *UTIL_EdictOfIndex(int index); -bool IsPropValid(const SendProp *prop, PropType type); - class ConVarSaveSet { public: From b0656f76e90dd536cf6722116f4a51e01b9324e5 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Mon, 18 Aug 2025 01:34:09 +0800 Subject: [PATCH 29/73] Fix SM1.11 compatibility --- extension/extension.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/extension/extension.cpp b/extension/extension.cpp index 937573e..6243d6a 100644 --- a/extension/extension.cpp +++ b/extension/extension.cpp @@ -278,7 +278,13 @@ static CBaseEntity *FindEntityByNetClass(int start, const char *classname) continue; } - ServerClass *pServerClass = gamehelpers->FindEntityServerClass(pEntity); + IServerNetworkable* pNetwork = ((IServerUnknown *)pEntity)->GetNetworkable(); + if (pNetwork == nullptr) + { + continue; + } + + ServerClass *pServerClass = pNetwork->GetServerClass(); if (pServerClass == nullptr) { continue; From 461290313cb565ac070a133540cc12b07b2c9296 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Mon, 18 Aug 2025 02:25:26 +0800 Subject: [PATCH 30/73] Fix callback type of gamerules hooks --- addons/sourcemod/scripting/include/sendproxy.inc | 6 +++--- extension/sendproxy_callback.cpp | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/addons/sourcemod/scripting/include/sendproxy.inc b/addons/sourcemod/scripting/include/sendproxy.inc index 71c51dd..a5b5dec 100644 --- a/addons/sourcemod/scripting/include/sendproxy.inc +++ b/addons/sourcemod/scripting/include/sendproxy.inc @@ -35,9 +35,9 @@ typeset SendProxyCallbackGamerules native bool SendProxy_HookEntity(int entity, const char[] prop, SendPropType type, SendProxyCallback callback, int element = 0); native bool SendProxy_UnhookEntity(int entity, const char[] prop, SendProxyCallback callback, int element = 0); native bool SendProxy_IsHookedEntity(int entity, const char[] prop, SendProxyCallback callback, int element = 0); -native bool SendProxy_HookGameRules(const char[] prop, SendPropType type, SendProxyCallback callback, int element = 0); -native bool SendProxy_UnhookGameRules(const char[] prop, SendProxyCallback callback, int element = 0); -native bool SendProxy_IsHookedGameRules(const char[] prop, SendProxyCallback callback, int element = 0); +native bool SendProxy_HookGameRules(const char[] prop, SendPropType type, SendProxyCallbackGamerules callback, int element = 0); +native bool SendProxy_UnhookGameRules(const char[] prop, SendProxyCallbackGamerules callback, int element = 0); +native bool SendProxy_IsHookedGameRules(const char[] prop, SendProxyCallbackGamerules callback, int element = 0); public __ext_sendproxymanager_SetNTVOptional() { diff --git a/extension/sendproxy_callback.cpp b/extension/sendproxy_callback.cpp index f2061db..3b9c626 100644 --- a/extension/sendproxy_callback.cpp +++ b/extension/sendproxy_callback.cpp @@ -7,7 +7,10 @@ bool SendProxyPluginCallback(void *callback, const SendProp *pProp, ProxyVariant auto func = static_cast(callback); cell_t result = Pl_Continue; - func->PushCell(entity); + + if (gamehelpers->ReferenceToEntity(entity) != GetGameRulesProxyEnt()) + func->PushCell(entity); + func->PushString(pProp->GetName()); ProxyVariant temp = variant; From cf5a28afd6ca4f1a3432c83d93dc668b4ec004fd Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Mon, 18 Aug 2025 03:01:29 +0800 Subject: [PATCH 31/73] Fix assertion failure on current client index --- extension/clientpacks_detours.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/extension/clientpacks_detours.cpp b/extension/clientpacks_detours.cpp index 18befee..b86036d 100644 --- a/extension/clientpacks_detours.cpp +++ b/extension/clientpacks_detours.cpp @@ -102,9 +102,6 @@ DETOUR_DECL_STATIC3(PackEntities_Normal, void, int, iClientCount, CGameClient ** DETOUR_DECL_STATIC3(SV_ComputeClientPacks, void, int, iClientCount, CGameClient **, pClients, CFrameSnapshot *, pSnapShot) { if (playerhelpers->GetMaxClients() <= 1 -#ifndef DEBUG - || iClientCount <= 1 -#endif || !g_pSendPropHookManager->IsAnyEntityHooked()) { return DETOUR_STATIC_CALL(SV_ComputeClientPacks)(iClientCount, pClients, pSnapShot); From b6b04fbbe0020aae026a504a9dcb9ac3d2f8459e Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Mon, 18 Aug 2025 03:07:32 +0800 Subject: [PATCH 32/73] Enable debug build --- .github/workflows/build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d013bb3..457634a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -124,7 +124,8 @@ jobs: --sm-path="${{ github.workspace }}/alliedmodders/sourcemod" \ --mms-path="${{ github.workspace }}/alliedmodders/mmsource-1.10" \ --sdks=${{ join(fromJSON(env.SDKS)) }} \ - --enable-optimize + --enable-optimize \ + --enable-debug ambuild - name: Copy to addons directory From 85b4ab3609bcb013e5641c1446a584a0a4a73cf6 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Mon, 18 Aug 2025 17:36:14 +0800 Subject: [PATCH 33/73] Fix wrong element passed to callback when hooking datatable member --- extension/sendprop_hookmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/sendprop_hookmanager.cpp b/extension/sendprop_hookmanager.cpp index ca39c87..b8fccb3 100644 --- a/extension/sendprop_hookmanager.cpp +++ b/extension/sendprop_hookmanager.cpp @@ -258,7 +258,7 @@ void GlobalProxy(const SendProp *pProp, const void *pStructBase, const void * pD continue; } - if (hook.fnProcess(hook.pCallback, pProp, pEntHook->data, iElement, objectID, client)) + if (hook.fnProcess(hook.pCallback, pProp, pEntHook->data, hook.element, objectID, client)) { pOverride = &pEntHook->data; return; From d80906e2e6f959f9da0afb72ce5a8849e41f665d Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Tue, 2 Sep 2025 16:19:24 +0800 Subject: [PATCH 34/73] Fix some mistakes causing crashes Address #3 --- extension/clientpacks_detours.cpp | 4 +++- extension/sendprop_hookmanager.cpp | 2 +- extension/sendprop_hookmanager.h | 7 ++++--- extension/wrappers.h | 23 ++++++++++++++++++----- 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/extension/clientpacks_detours.cpp b/extension/clientpacks_detours.cpp index b86036d..8fefa63 100644 --- a/extension/clientpacks_detours.cpp +++ b/extension/clientpacks_detours.cpp @@ -277,7 +277,9 @@ static void CopyPackedEntities(CFrameSnapshot *dest, const CFrameSnapshot *src) { auto data = src->m_pEntities[i].m_pPackedData; dest->m_pEntities[i].m_pPackedData = data; - framesnapshotmanager->AddEntityReference(data); + + if (data != INVALID_PACKED_ENTITY_HANDLE) + framesnapshotmanager->AddEntityReference(data); } if (dest->m_pHLTVEntityData && src->m_pHLTVEntityData) diff --git a/extension/sendprop_hookmanager.cpp b/extension/sendprop_hookmanager.cpp index b8fccb3..50e7cb5 100644 --- a/extension/sendprop_hookmanager.cpp +++ b/extension/sendprop_hookmanager.cpp @@ -196,7 +196,7 @@ class TailInvoker std::function m_call; }; -// !! MUST BE CALLED ON MAIN THREAD +// !! MUST BE CALLED IN MAIN THREAD void GlobalProxy(const SendProp *pProp, const void *pStructBase, const void * pData, DVariant *pOut, int iElement, int objectID) { auto pHook = g_pSendPropHookManager->GetPropHook(pProp); diff --git a/extension/sendprop_hookmanager.h b/extension/sendprop_hookmanager.h index 3375deb..c404d71 100644 --- a/extension/sendprop_hookmanager.h +++ b/extension/sendprop_hookmanager.h @@ -58,11 +58,12 @@ namespace SendProxy }; } -using SendPropHookMap = ke::HashMap, ke::PointerPolicy>; -using SendPropEntityInfoMap = ke::HashMap; - class SendPropHookManager { +public: + using SendPropHookMap = ke::HashMap, ke::PointerPolicy>; + using SendPropEntityInfoMap = ke::HashMap; + public: SendPropHookManager(); SendPropHookManager(const SendPropHookManager &other) = delete; diff --git a/extension/wrappers.h b/extension/wrappers.h index b0b7dbc..6dd2007 100644 --- a/extension/wrappers.h +++ b/extension/wrappers.h @@ -6,6 +6,7 @@ #include "tier1/utllinkedlist.h" #include "tier1/mempool.h" #include "iclient.h" +#include #if defined( _LINUX ) || defined( _OSX ) // linux implementation @@ -244,16 +245,28 @@ class CFrameSnapshotManager static ICallWrapper* s_callRemoveEntityReference; void RemoveEntityReference( PackedEntityHandle_t handle ) { - m_PackedEntities[ handle ]->m_ReferenceCount++; + struct { + CFrameSnapshotManager *pThis; + PackedEntityHandle_t handle; + } stack{ this, handle }; + + s_callRemoveEntityReference->Execute(&stack, nullptr); } public: uint32_t pad[21]; - CUtlFixedLinkedList m_PackedEntities; - - // FIXME: Update CUtlFixedLinkedList in hl2sdk-l4d2 - int pad2; + template + class CUtlFixedLinkedListHack : public CUtlFixedLinkedList + { + private: + int m_NumAlloced; // The number of allocated elements + }; + + std::conditional_t) == 40u, + CUtlFixedLinkedListHack, + CUtlFixedLinkedList + > m_PackedEntities; int m_nPackedEntityCacheCounter; // increase with every cache access CUtlVector m_PackedEntityCache; // cache for uncompressed packed entities From b2124fe3cf3377d6010082a288f4dba3c3fd0cc3 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Wed, 3 Sep 2025 21:07:48 +0800 Subject: [PATCH 35/73] Fix startup crash on windows --- extension/extension.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/extension/extension.cpp b/extension/extension.cpp index 6243d6a..19ea7b8 100644 --- a/extension/extension.cpp +++ b/extension/extension.cpp @@ -255,9 +255,7 @@ bool SendProxyManager::SDK_OnMetamodLoad(ISmmAPI *ismm, char *error, size_t maxl gpGlobals = ismm->GetCGlobals(); - sv_parallel_packentities = cvar->FindVar("sv_parallel_packentities"); - if (sv_parallel_packentities == nullptr) - return false; + GET_CONVAR(sv_parallel_packentities); return true; } From 11bfeab950c4e12805bb61840a63165205e62573 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Wed, 3 Sep 2025 21:08:26 +0800 Subject: [PATCH 36/73] Remove currently unnecessary interfaces --- extension/smsdk_config.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extension/smsdk_config.h b/extension/smsdk_config.h index a9f97af..2b47b01 100644 --- a/extension/smsdk_config.h +++ b/extension/smsdk_config.h @@ -59,7 +59,7 @@ #define SMEXT_CONF_METAMOD /** Enable interfaces you want to use here by uncommenting lines */ -#define SMEXT_ENABLE_FORWARDSYS +//#define SMEXT_ENABLE_FORWARDSYS //#define SMEXT_ENABLE_HANDLESYS #define SMEXT_ENABLE_PLAYERHELPERS //#define SMEXT_ENABLE_DBMANAGER @@ -77,6 +77,6 @@ //#define SMEXT_ENABLE_USERMSGS //#define SMEXT_ENABLE_TRANSLATOR //#define SMEXT_ENABLE_NINVOKE -#define SMEXT_ENABLE_ROOTCONSOLEMENU +//#define SMEXT_ENABLE_ROOTCONSOLEMENU #endif // _INCLUDE_SOURCEMOD_EXTENSION_CONFIG_H_ From b7207cd2935f5bf889e5606a138cad071848dde8 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Wed, 3 Sep 2025 21:09:28 +0800 Subject: [PATCH 37/73] Deprecate use on local server --- extension/extension.cpp | 7 ++++++- extension/wrappers.h | 8 ++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/extension/extension.cpp b/extension/extension.cpp index 19ea7b8..1f3cce9 100644 --- a/extension/extension.cpp +++ b/extension/extension.cpp @@ -251,8 +251,13 @@ void SendProxyManager::OnCoreMapEnd() bool SendProxyManager::SDK_OnMetamodLoad(ISmmAPI *ismm, char *error, size_t maxlen, bool late) { - GET_V_IFACE_ANY(GetEngineFactory, g_pCVar, ICvar, CVAR_INTERFACE_VERSION); + if (!engine->IsDedicatedServer()) + { + ke::SafeStrcpy(error, maxlen, "Local server support is deprecated."); + return false; + } + GET_V_IFACE_ANY(GetEngineFactory, g_pCVar, ICvar, CVAR_INTERFACE_VERSION); gpGlobals = ismm->GetCGlobals(); GET_CONVAR(sv_parallel_packentities); diff --git a/extension/wrappers.h b/extension/wrappers.h index 6dd2007..6c00934 100644 --- a/extension/wrappers.h +++ b/extension/wrappers.h @@ -142,7 +142,7 @@ class CFrameSnapshot public: static void *s_pfnReleaseReference; static ICallWrapper *s_callReleaseReference; - void ReleaseReference() + inline void ReleaseReference() { struct { CFrameSnapshot *pThis; @@ -223,7 +223,7 @@ class CFrameSnapshotManager static void* s_pfnCreateEmptySnapshot; static ICallWrapper* s_callCreateEmptySnapshot; - CFrameSnapshot* CreateEmptySnapshot(int tickcount, int maxEntities) + inline CFrameSnapshot* CreateEmptySnapshot(int tickcount, int maxEntities) { struct { CFrameSnapshotManager *pThis; @@ -236,14 +236,14 @@ class CFrameSnapshotManager return ret; } - void AddEntityReference( PackedEntityHandle_t handle ) + inline void AddEntityReference( PackedEntityHandle_t handle ) { m_PackedEntities[ handle ]->m_ReferenceCount++; } static void* s_pfnRemoveEntityReference; static ICallWrapper* s_callRemoveEntityReference; - void RemoveEntityReference( PackedEntityHandle_t handle ) + inline void RemoveEntityReference( PackedEntityHandle_t handle ) { struct { CFrameSnapshotManager *pThis; From 3a3da91214da586b86eadeeee04008da289c79d0 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Wed, 3 Sep 2025 21:10:14 +0800 Subject: [PATCH 38/73] Improve documentation in include file --- .../sourcemod/scripting/include/sendproxy.inc | 66 +++++++++++++++++-- 1 file changed, 61 insertions(+), 5 deletions(-) diff --git a/addons/sourcemod/scripting/include/sendproxy.inc b/addons/sourcemod/scripting/include/sendproxy.inc index a5b5dec..24947ba 100644 --- a/addons/sourcemod/scripting/include/sendproxy.inc +++ b/addons/sourcemod/scripting/include/sendproxy.inc @@ -15,6 +15,18 @@ enum SendPropType Prop_Max }; +/** + * Callback for send proxy hooks. + * + * @param entity Index of the hooked entity. + * @param prop Name of the hooked send prop. + * @param value Prop value. + * @param element 0 if the hooked prop is not an array, + * otherwise an index into the array (starting from 0). + * @param client Index of the current processing client. + * + * @return Action Plugin_Changed to override value, otherwise ignored. + */ typeset SendProxyCallback { function Action (int entity, const char[] prop, int &value, int element, int client); //Prop_Int, Prop_EHandle @@ -23,6 +35,17 @@ typeset SendProxyCallback function Action (int entity, const char[] prop, float value[3], int element, int client); //Prop_Vector }; +/** + * Callback for gamerules send proxy hooks. + * + * @param prop Name of the hooked send prop. + * @param value Prop value. + * @param element 0 if the hooked prop is NOT an array (InsideArray), + * otherwise an index into the array (starting from 0). + * @param client Index of the current processing client. + * + * @return Action Plugin_Changed to override value, ignored otherwise. + */ typeset SendProxyCallbackGamerules { function Action (const char[] prop, int &value, int element, int client); //Prop_Int, Prop_EHandle @@ -31,22 +54,55 @@ typeset SendProxyCallbackGamerules function Action (const char[] prop, float value[3], int element, int client); //Prop_Vector }; -//Returns true upon success, false upon failure +/** + * Hook an entity's prop to override its value in callback without actually changing the prop. + * @note Callback function cannot be checked so make sure it matches the prop type. + * + * @param entity Entity index to hook. + * @param prop Send prop name. + * @param type Prop type. Reports an error if type is mismatched. + * @param callback Callback function. + * @param element Element of the prop. Has no effect if the prop is NOT an array or a table. + * + * @return bool True if success, false if already hooked. + */ native bool SendProxy_HookEntity(int entity, const char[] prop, SendPropType type, SendProxyCallback callback, int element = 0); -native bool SendProxy_UnhookEntity(int entity, const char[] prop, SendProxyCallback callback, int element = 0); -native bool SendProxy_IsHookedEntity(int entity, const char[] prop, SendProxyCallback callback, int element = 0); native bool SendProxy_HookGameRules(const char[] prop, SendPropType type, SendProxyCallbackGamerules callback, int element = 0); + +/** + * Unhook an entity's prop. + * + * @param entity Entity index to unhook. + * @param prop Send prop name. + * @param callback Callback function. + * @param element Element of the prop. Has no effect if the prop is NOT an array or a table. + * + * @return bool True if found, false otherwise. + */ +native bool SendProxy_UnhookEntity(int entity, const char[] prop, SendProxyCallback callback, int element = 0); native bool SendProxy_UnhookGameRules(const char[] prop, SendProxyCallbackGamerules callback, int element = 0); + +/** + * Test if an entity's prop is hooked. + * + * @param entity Entity index to hook. + * @param prop Send prop name. + * @param callback Callback function. + * @param element Element of the prop. Has no effect if the prop is NOT an array or a table. + * + * @return bool True if hooked, false otherwise. + */ +native bool SendProxy_IsHookedEntity(int entity, const char[] prop, SendProxyCallback callback, int element = 0); native bool SendProxy_IsHookedGameRules(const char[] prop, SendProxyCallbackGamerules callback, int element = 0); public __ext_sendproxymanager_SetNTVOptional() { #if !defined REQUIRE_EXTENSIONS MarkNativeAsOptional("SendProxy_HookEntity"); - MarkNativeAsOptional("SendProxy_UnhookEntity"); - MarkNativeAsOptional("SendProxy_IsHookedEntity"); MarkNativeAsOptional("SendProxy_HookGameRules"); + MarkNativeAsOptional("SendProxy_UnhookEntity"); MarkNativeAsOptional("SendProxy_UnhookGameRules"); + MarkNativeAsOptional("SendProxy_IsHookedEntity"); MarkNativeAsOptional("SendProxy_IsHookedGameRules"); #endif } From 4850c8874c4e346a91332c52ab30536a9ba5339f Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Wed, 3 Sep 2025 21:51:28 +0800 Subject: [PATCH 39/73] Restore `SMEXT_ENABLE_FORWARDSYS` --- extension/smsdk_config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/smsdk_config.h b/extension/smsdk_config.h index 2b47b01..f528e7f 100644 --- a/extension/smsdk_config.h +++ b/extension/smsdk_config.h @@ -59,7 +59,7 @@ #define SMEXT_CONF_METAMOD /** Enable interfaces you want to use here by uncommenting lines */ -//#define SMEXT_ENABLE_FORWARDSYS +#define SMEXT_ENABLE_FORWARDSYS //#define SMEXT_ENABLE_HANDLESYS #define SMEXT_ENABLE_PLAYERHELPERS //#define SMEXT_ENABLE_DBMANAGER From 2a190e76a7af61c609e90bcb634e9d7f9d18c447 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Wed, 8 Oct 2025 01:37:36 +0800 Subject: [PATCH 40/73] Remove client assertion to prevent crashes Prevent crashes due to client assertion failure if props get hooked before "OnMapStart". --- extension/sendprop_hookmanager.cpp | 5 ++--- extension/sendprop_hookmanager.h | 9 +++------ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/extension/sendprop_hookmanager.cpp b/extension/sendprop_hookmanager.cpp index 50e7cb5..6b22075 100644 --- a/extension/sendprop_hookmanager.cpp +++ b/extension/sendprop_hookmanager.cpp @@ -5,8 +5,8 @@ void GlobalProxy(const SendProp *pProp, const void *pStructBase, const void *pData, DVariant *pOut, int iElement, int objectID); SendProxyHook::SendProxyHook(SendProp *pProp, SendVarProxyFn pfnProxy) - : m_pProp(pProp) { + m_pProp = pProp; m_fnRealProxy = m_pProp->GetProxyFn(); m_pProp->SetProxyFn( pfnProxy ); } @@ -81,7 +81,7 @@ bool SendPropHookManager::HookEntity(int entity, SendProp *pProp, int element, P hook.pOwner = pFunc->GetParentRuntime(); auto i = m_propHooks.findForAdd(pProp); - Assert(!i.found() || !i->value.expired()); + AssertFatal(!i.found() || !i->value.expired()); if (i.found()) { @@ -231,7 +231,6 @@ void GlobalProxy(const SendProp *pProp, const void *pStructBase, const void * pD return; int client = ClientPacksDetour::GetCurrentClientIndex(); - AssertFatal(client > 0); if (client == -1) return; diff --git a/extension/sendprop_hookmanager.h b/extension/sendprop_hookmanager.h index c404d71..ca4bb0e 100644 --- a/extension/sendprop_hookmanager.h +++ b/extension/sendprop_hookmanager.h @@ -45,8 +45,9 @@ struct SendPropEntityInfo ProxyVariant data; }; -namespace SendProxy +class SendPropHookManager { +protected: struct IntHashMapPolicy { static inline bool matches(const int32_t lookup, const int32_t compare) { @@ -56,13 +57,9 @@ namespace SendProxy return ke::HashInt32(key); } }; -} -class SendPropHookManager -{ -public: using SendPropHookMap = ke::HashMap, ke::PointerPolicy>; - using SendPropEntityInfoMap = ke::HashMap; + using SendPropEntityInfoMap = ke::HashMap; public: SendPropHookManager(); From 0ef1ccb9ec1d3f1afe988b36dcbea904b8d840d9 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Wed, 8 Oct 2025 01:37:44 +0800 Subject: [PATCH 41/73] Fix client 0 in string proxy callback --- extension/sendproxy_callback.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/sendproxy_callback.cpp b/extension/sendproxy_callback.cpp index 3b9c626..a89944e 100644 --- a/extension/sendproxy_callback.cpp +++ b/extension/sendproxy_callback.cpp @@ -19,7 +19,7 @@ bool SendProxyPluginCallback(void *callback, const SendProp *pProp, ProxyVariant std::visit(overloaded { [func](int &arg) { func->PushCellByRef(reinterpret_cast(&arg)); }, [func](float &arg) { func->PushFloatByRef(&arg); }, - [func](std::string& arg) { func->PushStringEx(arg.data(), arg.capacity(), SM_PARAM_STRING_UTF8 | SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK); }, + [func](std::string& arg) { func->PushStringEx(arg.data(), arg.capacity(), SM_PARAM_STRING_UTF8 | SM_PARAM_STRING_COPY, SM_PARAM_COPYBACK); func->PushCell(arg.capacity()); }, [func](Vector &arg) { func->PushArray(reinterpret_cast(&arg), 3, SM_PARAM_COPYBACK); }, [func, &iEntity](CBaseHandle &arg) { if (edict_t *edict = gamehelpers->GetHandleEntity(arg)) From 47e00f7a2840965b3218dedf2a125a1fa6bc33a4 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Wed, 8 Oct 2025 01:37:47 +0800 Subject: [PATCH 42/73] Bump version & Update info --- extension/smsdk_config.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extension/smsdk_config.h b/extension/smsdk_config.h index f528e7f..3a8906d 100644 --- a/extension/smsdk_config.h +++ b/extension/smsdk_config.h @@ -40,9 +40,9 @@ /* Basic information exposed publicly */ #define SMEXT_CONF_NAME "SendProxy Manager" #define SMEXT_CONF_DESCRIPTION "Change stuff without actually changing stuff!" -#define SMEXT_CONF_VERSION "1.3.3" -#define SMEXT_CONF_AUTHOR "afronanny & AlliedModders community" -#define SMEXT_CONF_URL "https://forums.alliedmods.net/showthread.php?t=169795" +#define SMEXT_CONF_VERSION "2.0.0" +#define SMEXT_CONF_AUTHOR "afronanny & AlliedModders community, Forgetest" +#define SMEXT_CONF_URL "https://github.com/jensewe/Left4SendProxy" #define SMEXT_CONF_LOGTAG "SENDPROXY" #define SMEXT_CONF_LICENSE "GPL" #define SMEXT_CONF_DATESTRING __DATE__ From 6f6bddc8e629fee3976f31f9c44053e983194406 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Wed, 8 Oct 2025 01:40:41 +0800 Subject: [PATCH 43/73] Revert fatal assertion in `HookEntity` --- extension/sendprop_hookmanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/sendprop_hookmanager.cpp b/extension/sendprop_hookmanager.cpp index 6b22075..c49e554 100644 --- a/extension/sendprop_hookmanager.cpp +++ b/extension/sendprop_hookmanager.cpp @@ -81,7 +81,7 @@ bool SendPropHookManager::HookEntity(int entity, SendProp *pProp, int element, P hook.pOwner = pFunc->GetParentRuntime(); auto i = m_propHooks.findForAdd(pProp); - AssertFatal(!i.found() || !i->value.expired()); + Assert(!i.found() || !i->value.expired()); if (i.found()) { From 4d3def9194286309a40eddf3cf3cf0fae252f72b Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Sun, 12 Oct 2025 02:58:46 +0800 Subject: [PATCH 44/73] Fix frame snapshot leaking --- extension/clientpacks_detours.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/extension/clientpacks_detours.cpp b/extension/clientpacks_detours.cpp index 8fefa63..9dee810 100644 --- a/extension/clientpacks_detours.cpp +++ b/extension/clientpacks_detours.cpp @@ -181,6 +181,13 @@ DETOUR_DECL_STATIC3(SV_ComputeClientPacks, void, int, iClientCount, CGameClient } g_iCurrentClientIndexInLoop = -1; + + // finally decrement reference of manually created snapshots + for (int i = 1; i < iClientCount; ++i) + { + Assert(clientSnapshots[i]->m_nReferences == 2); + clientSnapshots[i]->ReleaseReference(); + } } int ClientPacksDetour::GetCurrentClientIndex() From 12b42ea0441203f08621f4b1a0a2e619bf506af6 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Sun, 12 Oct 2025 03:31:19 +0800 Subject: [PATCH 45/73] Fix obsolete packs cause crashes on new client joining Fix: - Dereference entity packs of leaving clients, so later joining clients that reuse slots won't crash the server for mismatching prop count during entity packing. Change: - Save edict change state for each client instead of forcing repack whenever there's a newer pack, which causes enormous entity pack count in total (and thus leads to "CUtlRBTree overflow" crashes). - Prefer `at` over `operator[]` on STL map to probably reduce some overhead. --- extension/clientpacks_detours.cpp | 115 +++++++++++++++++++----------- extension/clientpacks_detours.h | 5 +- extension/extension.cpp | 1 + 3 files changed, 76 insertions(+), 45 deletions(-) diff --git a/extension/clientpacks_detours.cpp b/extension/clientpacks_detours.cpp index 9dee810..885494a 100644 --- a/extension/clientpacks_detours.cpp +++ b/extension/clientpacks_detours.cpp @@ -6,6 +6,11 @@ #include "util.h" #include #include +#include + +#if defined(DEBUG) || defined(_DEBUG) +#define DEBUG_SENDPROXY_MEMORY +#endif DECL_DETOUR(CFrameSnapshotManager_UsePreviouslySentPacket); DECL_DETOUR(CFrameSnapshotManager_GetPreviouslySentPacket); @@ -15,8 +20,18 @@ DECL_DETOUR(SV_ComputeClientPacks); volatile int g_iCurrentClientIndexInLoop = -1; //used for optimization -using PackedEntityArray = std::array; -std::unordered_map g_EntityPackMap; +struct PackedEntityInfo +{ + PackedEntityInfo() + { + handles.fill(INVALID_PACKED_ENTITY_HANDLE); + updatebits.set(); + } + + std::array handles; + std::bitset updatebits; +}; +std::unordered_map g_EntityPackMap; ConVar ext_sendproxy_frame_callback("ext_sendproxy_frame_callback", "0", FCVAR_NONE, "Invoke hooked proxy every frame."); @@ -33,36 +48,21 @@ ConVar ext_sendproxy_frame_callback("ext_sendproxy_frame_callback", "0", FCVAR_N DETOUR_DECL_MEMBER3(CFrameSnapshotManager_UsePreviouslySentPacket, bool, CFrameSnapshot*, pSnapshot, int, entity, int, entSerialNumber) { - if (g_iCurrentClientIndexInLoop == -1) - return DETOUR_MEMBER_CALL(CFrameSnapshotManager_UsePreviouslySentPacket)(pSnapshot, entity, entSerialNumber); - - if (g_EntityPackMap[entity][g_iCurrentClientIndexInLoop] == INVALID_PACKED_ENTITY_HANDLE) - return false; - - if (framesnapshotmanager->m_pLastPackedData[entity] != INVALID_PACKED_ENTITY_HANDLE) + if (g_iCurrentClientIndexInLoop != -1) { - PackedEntity *newpe = framesnapshotmanager->m_PackedEntities[ framesnapshotmanager->m_pLastPackedData[entity] ]; - PackedEntity *oldpe = framesnapshotmanager->m_PackedEntities[ g_EntityPackMap[entity][g_iCurrentClientIndexInLoop] ]; - - if (newpe && oldpe && newpe->m_nSnapshotCreationTick > oldpe->m_nSnapshotCreationTick) - { - gamehelpers->EdictOfIndex(entity)->m_fStateFlags |= FL_EDICT_CHANGED; - return false; - } + framesnapshotmanager->m_pLastPackedData[entity] = g_EntityPackMap.at(entity).handles[g_iCurrentClientIndexInLoop]; } - framesnapshotmanager->m_pLastPackedData[entity] = g_EntityPackMap[entity][g_iCurrentClientIndexInLoop]; return DETOUR_MEMBER_CALL(CFrameSnapshotManager_UsePreviouslySentPacket)(pSnapshot, entity, entSerialNumber); } DETOUR_DECL_MEMBER2(CFrameSnapshotManager_GetPreviouslySentPacket, PackedEntity*, int, entity, int, entSerialNumber) { - if (g_iCurrentClientIndexInLoop == -1) + if (g_iCurrentClientIndexInLoop != -1) { - return DETOUR_MEMBER_CALL(CFrameSnapshotManager_GetPreviouslySentPacket)(entity, entSerialNumber); + framesnapshotmanager->m_pLastPackedData[entity] = g_EntityPackMap.at(entity).handles[g_iCurrentClientIndexInLoop]; } - framesnapshotmanager->m_pLastPackedData[entity] = g_EntityPackMap[entity][g_iCurrentClientIndexInLoop]; return DETOUR_MEMBER_CALL(CFrameSnapshotManager_GetPreviouslySentPacket)(entity, entSerialNumber); } @@ -73,16 +73,11 @@ DETOUR_DECL_MEMBER2(CFrameSnapshotManager_CreatePackedEntity, PackedEntity*, CFr return DETOUR_MEMBER_CALL(CFrameSnapshotManager_CreatePackedEntity)(pSnapshot, entity); } - PackedEntityHandle_t origHandle = framesnapshotmanager->m_pLastPackedData[entity]; - - if (g_EntityPackMap[entity][g_iCurrentClientIndexInLoop] != INVALID_PACKED_ENTITY_HANDLE) - { - framesnapshotmanager->m_pLastPackedData[entity] = g_EntityPackMap[entity][g_iCurrentClientIndexInLoop]; - } + framesnapshotmanager->m_pLastPackedData[entity] = g_EntityPackMap.at(entity).handles[g_iCurrentClientIndexInLoop]; PackedEntity *result = DETOUR_MEMBER_CALL(CFrameSnapshotManager_CreatePackedEntity)(pSnapshot, entity); - g_EntityPackMap[entity][g_iCurrentClientIndexInLoop] = framesnapshotmanager->m_pLastPackedData[entity]; + g_EntityPackMap.at(entity).handles[g_iCurrentClientIndexInLoop] = framesnapshotmanager->m_pLastPackedData[entity]; return result; } @@ -170,9 +165,21 @@ DETOUR_DECL_STATIC3(SV_ComputeClientPacks, void, int, iClientCount, CGameClient snapshot->m_pValidEntities += numEntities - numHooked; snapshot->m_nValidEntities = numHooked; - if (ext_sendproxy_frame_callback.GetBool()) - std::for_each_n( snapshot->m_pValidEntities, snapshot->m_nValidEntities, [](int edictidx){ gamehelpers->EdictOfIndex(edictidx)->m_fStateFlags |= FL_EDICT_CHANGED; }); - + std::for_each_n(snapshot->m_pValidEntities, + snapshot->m_nValidEntities, + [](int edictidx) + { + auto edict = gamehelpers->EdictOfIndex(edictidx); + if (edict->HasStateChanged() || ext_sendproxy_frame_callback.GetBool()) + g_EntityPackMap.at(edictidx).updatebits.set(); + + if (g_EntityPackMap.at(edictidx).updatebits[g_iCurrentClientIndexInLoop]) + { + g_EntityPackMap.at(edictidx).updatebits[g_iCurrentClientIndexInLoop] = false; + edict->m_fStateFlags |= FL_EDICT_CHANGED; + } + }); + DETOUR_STATIC_CALL(PackEntities_Normal)(1, &client, snapshot); snapshot->m_nValidEntities = numEntities; @@ -198,31 +205,49 @@ int ClientPacksDetour::GetCurrentClientIndex() return g_iCurrentClientIndexInLoop + 1; } -void ClientPacksDetour::OnEntityHooked(int index) +void ClientPacksDetour::OnEntityHooked(int entity) { - if (g_EntityPackMap.find(index) == g_EntityPackMap.end()) + if (g_EntityPackMap.find(entity) == g_EntityPackMap.end()) { - PackedEntityArray arr; - arr.fill(INVALID_PACKED_ENTITY_HANDLE); - g_EntityPackMap.emplace(index, std::move(arr)); + g_EntityPackMap.emplace(entity, PackedEntityInfo()); + + if (framesnapshotmanager->m_pLastPackedData[entity] != INVALID_PACKED_ENTITY_HANDLE) + { + framesnapshotmanager->RemoveEntityReference(framesnapshotmanager->m_pLastPackedData[entity]); + framesnapshotmanager->m_pLastPackedData[entity] = INVALID_PACKED_ENTITY_HANDLE; + } } } -void ClientPacksDetour::OnEntityUnhooked(int index) +void ClientPacksDetour::OnEntityUnhooked(int entity) { - if (g_EntityPackMap.find(index) == g_EntityPackMap.end()) + if (g_EntityPackMap.find(entity) == g_EntityPackMap.end()) return; - for (PackedEntityHandle_t pack : g_EntityPackMap[index]) + for (PackedEntityHandle_t handle : g_EntityPackMap.at(entity).handles) { - if (pack != INVALID_PACKED_ENTITY_HANDLE - && pack != framesnapshotmanager->m_pLastPackedData[index]) + if (handle != INVALID_PACKED_ENTITY_HANDLE) { - framesnapshotmanager->RemoveEntityReference(pack); + framesnapshotmanager->RemoveEntityReference(handle); } } - g_EntityPackMap.erase(index); + g_EntityPackMap.erase(entity); + framesnapshotmanager->m_pLastPackedData[entity] = INVALID_PACKED_ENTITY_HANDLE; +} + +void ClientPacksDetour::OnClientDisconnect(int client) +{ + int index = client - 1; + + for (auto& [_, info] : g_EntityPackMap) + { + if (info.handles[index] != INVALID_PACKED_ENTITY_HANDLE) + { + framesnapshotmanager->RemoveEntityReference(info.handles[index]); + info.handles[index] = INVALID_PACKED_ENTITY_HANDLE; + } + } } bool ClientPacksDetour::Init(IGameConfig *gc) @@ -253,6 +278,10 @@ void ClientPacksDetour::Shutdown() void ClientPacksDetour::Clear() { +#ifdef DEBUG_SENDPROXY_MEMORY + g_pSM->LogMessage(myself, "=== PACKED ENTITIES COUNT (%d) ===", framesnapshotmanager->m_PackedEntities.Count()); +#endif + g_EntityPackMap.clear(); } diff --git a/extension/clientpacks_detours.h b/extension/clientpacks_detours.h index bf2209b..df0e216 100644 --- a/extension/clientpacks_detours.h +++ b/extension/clientpacks_detours.h @@ -10,8 +10,9 @@ class ClientPacksDetour static void Shutdown(); static void Clear(); static int GetCurrentClientIndex(); - static void OnEntityHooked(int index); - static void OnEntityUnhooked(int index); + static void OnEntityHooked(int entity); + static void OnEntityUnhooked(int entity); + static void OnClientDisconnect(int client); }; #endif \ No newline at end of file diff --git a/extension/extension.cpp b/extension/extension.cpp index 1f3cce9..c93f826 100644 --- a/extension/extension.cpp +++ b/extension/extension.cpp @@ -238,6 +238,7 @@ void SendProxyManager::OnEntityDestroyed(CBaseEntity* pEnt) void SendProxyManager::OnClientDisconnected(int client) { g_pSendPropHookManager->UnhookEntityAll(client); + ClientPacksDetour::OnClientDisconnect(client); } void SendProxyManager::OnCoreMapStart(edict_t * pEdictList, int edictCount, int clientMax) From 00dbed323a1b796ed1d156851e5523efbeb613b5 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Mon, 13 Oct 2025 03:27:24 +0800 Subject: [PATCH 46/73] Fix crash on unloading --- extension/extension.cpp | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/extension/extension.cpp b/extension/extension.cpp index c93f826..db45b05 100644 --- a/extension/extension.cpp +++ b/extension/extension.cpp @@ -46,8 +46,8 @@ SendPropHookManager *g_pSendPropHookManager = &s_SendPropHookManager; std::string g_szGameRulesProxy; IServerGameEnts * gameents = nullptr; IServerGameClients * gameclients = nullptr; -CGlobalVars *gpGlobals = NULL; -IBinTools* bintools = NULL; +CGlobalVars *gpGlobals = nullptr; +IBinTools* bintools = nullptr; ISDKHooks * sdkhooks = nullptr; ConVar *sv_parallel_packentities = nullptr; @@ -191,7 +191,8 @@ void SendProxyManager::SDK_OnAllLoaded() bool SendProxyManager::QueryInterfaceDrop(SMInterface* pInterface) { - if (pInterface == sdkhooks || pInterface == bintools) + std::string_view name = pInterface->GetInterfaceName(); + if (name == SMINTERFACE_SDKHOOKS_NAME || name == SMINTERFACE_BINTOOLS_NAME) return false; return true; @@ -199,6 +200,17 @@ bool SendProxyManager::QueryInterfaceDrop(SMInterface* pInterface) void SendProxyManager::NotifyInterfaceDrop(SMInterface* pInterface) { + std::string_view name = pInterface->GetInterfaceName(); + + if (name == SMINTERFACE_SDKHOOKS_NAME) + { + sdkhooks = nullptr; + } + else if (name == SMINTERFACE_BINTOOLS_NAME) + { + bintools = nullptr; + } + SDK_OnUnload(); } @@ -218,6 +230,9 @@ bool SendProxyManager::RegisterConCommandBase(ConCommandBase* pVar) void SendProxyManager::SDK_OnUnload() { + g_pSendPropHookManager->Clear(); + ClientPacksDetour::Shutdown(); + plsys->RemovePluginsListener(this); playerhelpers->RemoveClientListener(this); @@ -227,6 +242,24 @@ void SendProxyManager::SDK_OnUnload() } ConVar_Unregister(); + + if (CFrameSnapshot::s_callReleaseReference != nullptr) + { + CFrameSnapshot::s_callReleaseReference->Destroy(); + CFrameSnapshot::s_callReleaseReference = nullptr; + } + + if (CFrameSnapshotManager::s_callCreateEmptySnapshot != nullptr) + { + CFrameSnapshotManager::s_callCreateEmptySnapshot->Destroy(); + CFrameSnapshotManager::s_callCreateEmptySnapshot = nullptr; + } + + if (CFrameSnapshotManager::s_callRemoveEntityReference != nullptr) + { + CFrameSnapshotManager::s_callRemoveEntityReference->Destroy(); + CFrameSnapshotManager::s_callRemoveEntityReference = nullptr; + } } void SendProxyManager::OnEntityDestroyed(CBaseEntity* pEnt) From 28b1d3836191cc71d1cfd3656229dbbd1fe2ea0a Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Mon, 13 Oct 2025 03:29:15 +0800 Subject: [PATCH 47/73] Minor optimization on save/apply change state --- extension/clientpacks_detours.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/extension/clientpacks_detours.cpp b/extension/clientpacks_detours.cpp index 885494a..d06785b 100644 --- a/extension/clientpacks_detours.cpp +++ b/extension/clientpacks_detours.cpp @@ -114,6 +114,9 @@ DETOUR_DECL_STATIC3(SV_ComputeClientPacks, void, int, iClientCount, CGameClient const auto tail = pSnapShot->m_nValidEntities - numHooked - 1; std::swap(pSnapShot->m_pValidEntities[i], pSnapShot->m_pValidEntities[tail]); numHooked++; + + if (gamehelpers->EdictOfIndex(entindex)->HasStateChanged() || ext_sendproxy_frame_callback.GetBool()) + g_EntityPackMap.at(entindex).updatebits.set(); } } @@ -169,14 +172,10 @@ DETOUR_DECL_STATIC3(SV_ComputeClientPacks, void, int, iClientCount, CGameClient snapshot->m_nValidEntities, [](int edictidx) { - auto edict = gamehelpers->EdictOfIndex(edictidx); - if (edict->HasStateChanged() || ext_sendproxy_frame_callback.GetBool()) - g_EntityPackMap.at(edictidx).updatebits.set(); - if (g_EntityPackMap.at(edictidx).updatebits[g_iCurrentClientIndexInLoop]) { g_EntityPackMap.at(edictidx).updatebits[g_iCurrentClientIndexInLoop] = false; - edict->m_fStateFlags |= FL_EDICT_CHANGED; + gamehelpers->EdictOfIndex(edictidx)->m_fStateFlags |= FL_EDICT_CHANGED; } }); From 93f5167bbc5cb5c55cdd887b93e5d26351e6d556 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Mon, 13 Oct 2025 03:32:59 +0800 Subject: [PATCH 48/73] Allow returning invalid ehandle --- extension/sendproxy_callback.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/extension/sendproxy_callback.cpp b/extension/sendproxy_callback.cpp index a89944e..626721a 100644 --- a/extension/sendproxy_callback.cpp +++ b/extension/sendproxy_callback.cpp @@ -42,7 +42,9 @@ bool SendProxyPluginCallback(void *callback, const SendProp *pProp, ProxyVariant }, [func, iEntity, &intercept](CBaseHandle &arg) { - if (edict_t *edict = gamehelpers->EdictOfIndex(iEntity)) { + if (iEntity == -1) { + arg.Term(); + } else if (edict_t *edict = gamehelpers->EdictOfIndex(iEntity)) { gamehelpers->SetHandleEntity(arg, edict); } else { func->GetParentRuntime()->GetDefaultContext()->BlamePluginError( From 2fc2d5efb5a8bbad1a6d33dc14e4a390db60153f Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Mon, 13 Oct 2025 03:36:05 +0800 Subject: [PATCH 49/73] Minor code reordering --- extension/extension.cpp | 108 +++--------------------------- extension/natives.cpp | 23 +++++++ extension/util.h | 142 ++++++++++++++++++++++++++++++++++------ 3 files changed, 154 insertions(+), 119 deletions(-) diff --git a/extension/extension.cpp b/extension/extension.cpp index db45b05..27a9d3a 100644 --- a/extension/extension.cpp +++ b/extension/extension.cpp @@ -51,42 +51,6 @@ IBinTools* bintools = nullptr; ISDKHooks * sdkhooks = nullptr; ConVar *sv_parallel_packentities = nullptr; -class AutoSMGameConfig -{ -public: - static std::optional Load(const char* name) - { - char error[256]; - IGameConfig *gc = nullptr; - if (!gameconfs->LoadGameConfigFile(name, &gc, error, sizeof(error))) - { - smutils->LogError(myself, "Could not read config file sdktools.games.txt: %s", error); - return {}; - } - return gc; - } - -public: - AutoSMGameConfig() noexcept: gc_(nullptr) { } - AutoSMGameConfig(IGameConfig *gc) noexcept: gc_(gc) { } - ~AutoSMGameConfig() - { - if (gc_ != nullptr) - gameconfs->CloseGameConfigFile(gc_); - } - operator IGameConfig*() const noexcept - { - return gc_; - } - IGameConfig* operator->() const noexcept - { - return gc_; - } - -private: - IGameConfig *gc_; -}; - CFrameSnapshotManager* framesnapshotmanager = nullptr; void* CFrameSnapshotManager::s_pfnCreateEmptySnapshot = nullptr; ICallWrapper* CFrameSnapshotManager::s_callCreateEmptySnapshot = nullptr; @@ -95,43 +59,22 @@ ICallWrapper* CFrameSnapshotManager::s_callRemoveEntityReference = nullptr; void* CFrameSnapshot::s_pfnReleaseReference = nullptr; ICallWrapper *CFrameSnapshot::s_callReleaseReference = nullptr; -bool SendProxyManager::SDK_OnLoad(char *error, size_t maxlength, bool late) +bool SendProxyManager::SDK_OnLoad(char *error, size_t maxlen, bool late) { - auto gc_sdktools = AutoSMGameConfig::Load("sdktools.games"); - auto gc = AutoSMGameConfig::Load("sendproxy"); + auto gc_sdktools = *AutoGameConfig::Load("sdktools.games"); + auto gc = *AutoGameConfig::Load("sendproxy"); if (!gc_sdktools || !gc) return false; - g_szGameRulesProxy = gc_sdktools.value()->GetKeyValue("GameRulesProxy"); + g_szGameRulesProxy = gc_sdktools->GetKeyValue("GameRulesProxy"); - if (!gc.value()->GetMemSig("CFrameSnapshotManager::CreateEmptySnapshot", &CFrameSnapshotManager::s_pfnCreateEmptySnapshot)) - { - ke::SafeSprintf(error, maxlength, "Unable to find signature address ""\"CFrameSnapshotManager::CreateEmptySnapshot\""""); - return false; - } + GAMECONF_GETSIGNATURE(gc, "CFrameSnapshotManager::CreateEmptySnapshot", &CFrameSnapshotManager::s_pfnCreateEmptySnapshot); + GAMECONF_GETSIGNATURE(gc, "CFrameSnapshotManager::RemoveEntityReference", &CFrameSnapshotManager::s_pfnRemoveEntityReference); + GAMECONF_GETSIGNATURE(gc, "CFrameSnapshot::ReleaseReference", &CFrameSnapshot::s_pfnReleaseReference); + GAMECONF_GETADDRESS(gc, "framesnapshotmanager", &framesnapshotmanager); - if (!gc.value()->GetMemSig("CFrameSnapshotManager::RemoveEntityReference", &CFrameSnapshotManager::s_pfnRemoveEntityReference)) - { - ke::SafeSprintf(error, maxlength, "Unable to find signature address ""\"CFrameSnapshotManager::RemoveEntityReference\""""); + if (!ClientPacksDetour::Init(gc)) return false; - } - - if (!gc.value()->GetMemSig("CFrameSnapshot::ReleaseReference", &CFrameSnapshot::s_pfnReleaseReference)) - { - ke::SafeSprintf(error, maxlength, "Unable to find signature address ""\"CFrameSnapshot::ReleaseReference\""""); - return false; - } - - if (!gc.value()->GetAddress("framesnapshotmanager", (void**)&framesnapshotmanager)) - { - ke::SafeSprintf(error, maxlength, "Unable to find address (framesnapshotmanager)"); - return false; - } - - if (!ClientPacksDetour::Init(gc.value())) - { - return false; - } if (late) //if we loaded late, we need manually to call that OnCoreMapStart(nullptr, 0, 0); @@ -304,39 +247,6 @@ void SendProxyManager::OnPluginUnloaded(IPlugin * plugin) g_pSendPropHookManager->OnPluginUnloaded(plugin); } -static CBaseEntity *FindEntityByNetClass(int start, const char *classname) -{ - int maxEntities = gpGlobals->maxEntities; - for (int i = start; i < maxEntities; i++) - { - CBaseEntity *pEntity = gamehelpers->ReferenceToEntity(i); - if (pEntity == nullptr) - { - continue; - } - - IServerNetworkable* pNetwork = ((IServerUnknown *)pEntity)->GetNetworkable(); - if (pNetwork == nullptr) - { - continue; - } - - ServerClass *pServerClass = pNetwork->GetServerClass(); - if (pServerClass == nullptr) - { - continue; - } - - const char *name = pServerClass->GetName(); - if (!strcmp(name, classname)) - { - return pEntity; - } - } - - return nullptr; -} - CBaseEntity* GetGameRulesProxyEnt() { static cell_t proxyEntRef = -1; diff --git a/extension/natives.cpp b/extension/natives.cpp index 18e2c9c..e79020c 100644 --- a/extension/natives.cpp +++ b/extension/natives.cpp @@ -33,6 +33,29 @@ #include "util.h" #include "sendprop_hookmanager.h" +static bool IsPropValid(const SendProp *prop, PropType type) +{ + switch (type) + { + case PropType::Prop_Int: + return prop->GetType() == DPT_Int; + + case PropType::Prop_EHandle: + return prop->GetType() == DPT_Int && prop->m_nBits == NUM_NETWORKED_EHANDLE_BITS; + + case PropType::Prop_Float: + return prop->GetType() == DPT_Float; + + case PropType::Prop_Vector: + return prop->GetType() == DPT_Vector || prop->GetType() == DPT_VectorXY; + + case PropType::Prop_String: + return prop->GetType() == DPT_String; + } + + return false; +} + void UTIL_FindSendProp(SendProp* &ret, IPluginContext *pContext, int index, const char* propname, bool checkType, PropType type, int element) { edict_t *edict = UTIL_EdictOfIndex(index); diff --git a/extension/util.h b/extension/util.h index 6ab95f2..40ba070 100644 --- a/extension/util.h +++ b/extension/util.h @@ -1,7 +1,4 @@ -#ifndef _SENDPROXY_UTIL_H -#define _SENDPROXY_UTIL_H - -#include "extension.h" +#pragma once #define GET_CONVAR(name) \ name = g_pCVar->FindVar(#name); \ @@ -34,6 +31,99 @@ class ConVarSaveSet std::string m_savevalue; }; +class AutoGameConfig +{ +public: + static std::optional Load(const char *name) + { + char buffer[256]; + IGameConfig *gc; + if (!gameconfs->LoadGameConfigFile(name, &gc, buffer, sizeof(buffer))) + { + smutils->LogError(myself, "Could not read config file (%s) (%s)", name, buffer); + return {}; + } + return AutoGameConfig(gc); + } + +protected: + AutoGameConfig(IGameConfig *gc) : gc_(gc) {} + +public: + AutoGameConfig() : gc_(nullptr) {} + + AutoGameConfig(AutoGameConfig &&other) noexcept + { + gc_ = other.gc_; + other.gc_ = nullptr; + } + AutoGameConfig &operator=(AutoGameConfig &&other) + { + gc_ = other.gc_; + other.gc_ = nullptr; + } + + AutoGameConfig(const AutoGameConfig &other) = delete; + AutoGameConfig &operator=(const AutoGameConfig &other) = delete; + + ~AutoGameConfig() + { + Destroy(); + } + + void Destroy() noexcept + { + if (gc_ != nullptr) + { + gameconfs->CloseGameConfigFile(gc_); + gc_ = nullptr; + } + } + + operator IGameConfig *() const noexcept + { + return gc_; + } + + IGameConfig *operator->() const noexcept + { + return gc_; + } + +private: + IGameConfig *gc_; +}; + +#define GAMECONF_GETADDRESS(conf, key, var) \ + do \ + { \ + if (!conf->GetAddress(key, (void **)var)) \ + { \ + ke::SafeStrcpy(error, maxlen, "Unable to find address (" key ")\n"); \ + return false; \ + } \ + } while (false) + +#define GAMECONF_GETOFFSET(conf, key, var) \ + do \ + { \ + if (!conf->GetOffset(key, (void **)var)) \ + { \ + ke::SafeStrcpy(error, maxlen, "Unable to find offset (" key ")\n"); \ + return false; \ + } \ + } while (false) + +#define GAMECONF_GETSIGNATURE(conf, key, var) \ + do \ + { \ + if (!conf->GetMemSig(key, (void **)var)) \ + { \ + ke::SafeStrcpy(error, maxlen, "Unable to find signature (" key ")\n"); \ + return false; \ + } \ + } while (false) + inline edict_t *UTIL_EdictOfIndex(int index) { if (index < 0 || index >= MAX_EDICTS) @@ -42,27 +132,39 @@ inline edict_t *UTIL_EdictOfIndex(int index) return gamehelpers->EdictOfIndex(index); } -inline bool IsPropValid(const SendProp *prop, PropType type) +extern CGlobalVars *gpGlobals; +inline CBaseEntity *FindEntityByNetClass(int start, const char *classname) { - switch (type) - { - case PropType::Prop_Int: - return prop->GetType() == DPT_Int; + if (gpGlobals == nullptr) + return nullptr; - case PropType::Prop_EHandle: - return prop->GetType() == DPT_Int && prop->m_nBits == NUM_NETWORKED_EHANDLE_BITS; + int maxEntities = gpGlobals->maxEntities; + for (int i = start; i < maxEntities; i++) + { + CBaseEntity *pEntity = gamehelpers->ReferenceToEntity(i); + if (pEntity == nullptr) + { + continue; + } - case PropType::Prop_Float: - return prop->GetType() == DPT_Float; + IServerNetworkable* pNetwork = ((IServerUnknown *)pEntity)->GetNetworkable(); + if (pNetwork == nullptr) + { + continue; + } - case PropType::Prop_Vector: - return prop->GetType() == DPT_Vector || prop->GetType() == DPT_VectorXY; + ServerClass *pServerClass = pNetwork->GetServerClass(); + if (pServerClass == nullptr) + { + continue; + } - case PropType::Prop_String: - return prop->GetType() == DPT_String; + const char *name = pServerClass->GetName(); + if (!strcmp(name, classname)) + { + return pEntity; + } } - return false; + return nullptr; } - -#endif \ No newline at end of file From 885afa30e529a4bb4795ba34374908e1fec1fbd4 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Mon, 13 Oct 2025 03:37:30 +0800 Subject: [PATCH 50/73] Cleanup --- extension/ISendProxy.h | 19 ------------------- extension/extension.cpp | 7 ------- extension/extension.h | 1 - 3 files changed, 27 deletions(-) diff --git a/extension/ISendProxy.h b/extension/ISendProxy.h index f49cb12..28c96ae 100644 --- a/extension/ISendProxy.h +++ b/extension/ISendProxy.h @@ -32,18 +32,6 @@ #ifndef _INCLUDE_ISENDPROXY_ #define _INCLUDE_ISENDPROXY_ -#include "dt_send.h" -#include "server_class.h" - -#define SMINTERFACE_SENDPROXY_NAME "ISendProxyInterface133" -#define SMINTERFACE_SENDPROXY_VERSION 0x133 - -class CBaseEntity; -class CBasePlayer; -class ISendProxyUnhookListener; - -using namespace SourceMod; - enum class PropType : uint8_t { Prop_Int = 0, @@ -54,11 +42,4 @@ enum class PropType : uint8_t Prop_Max }; -enum class CallbackType : uint8_t -{ - Callback_OnChanged = 0, // Callback only when the edict is marked changed - Callback_Constant, // Callback on every frame -}; - - #endif \ No newline at end of file diff --git a/extension/extension.cpp b/extension/extension.cpp index 27a9d3a..155fafc 100644 --- a/extension/extension.cpp +++ b/extension/extension.cpp @@ -76,9 +76,6 @@ bool SendProxyManager::SDK_OnLoad(char *error, size_t maxlen, bool late) if (!ClientPacksDetour::Init(gc)) return false; - if (late) //if we loaded late, we need manually to call that - OnCoreMapStart(nullptr, 0, 0); - sharesys->AddDependency(myself, "sdkhooks.ext", true, true); sharesys->AddDependency(myself, "bintools.ext", true, true); @@ -217,10 +214,6 @@ void SendProxyManager::OnClientDisconnected(int client) ClientPacksDetour::OnClientDisconnect(client); } -void SendProxyManager::OnCoreMapStart(edict_t * pEdictList, int edictCount, int clientMax) -{ -} - void SendProxyManager::OnCoreMapEnd() { g_pSendPropHookManager->Clear(); diff --git a/extension/extension.h b/extension/extension.h index 695f7c2..1087f3b 100644 --- a/extension/extension.h +++ b/extension/extension.h @@ -101,7 +101,6 @@ class SendProxyManager : #endif public: //SDKExtension - void OnCoreMapStart(edict_t *, int, int) override; void OnCoreMapEnd() override; public: //IPluginsListener From f80e6a49b2bdd7d213847ac6adbaed2ee073c027 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Mon, 13 Oct 2025 03:38:19 +0800 Subject: [PATCH 51/73] Minor include reorganizing --- extension/clientpacks_detours.cpp | 2 -- extension/extension.cpp | 3 --- extension/extension.h | 4 ++++ extension/natives.cpp | 1 - extension/sendprop_hookmanager.h | 2 -- extension/sendproxy_callback.cpp | 1 - extension/sendproxy_callback.h | 2 -- 7 files changed, 4 insertions(+), 11 deletions(-) diff --git a/extension/clientpacks_detours.cpp b/extension/clientpacks_detours.cpp index d06785b..9ff6eb8 100644 --- a/extension/clientpacks_detours.cpp +++ b/extension/clientpacks_detours.cpp @@ -1,9 +1,7 @@ #include "clientpacks_detours.h" #include "sendprop_hookmanager.h" #include "CDetour/detours.h" -#include "wrappers.h" #include "iclient.h" -#include "util.h" #include #include #include diff --git a/extension/extension.cpp b/extension/extension.cpp index 155fafc..a0aa634 100644 --- a/extension/extension.cpp +++ b/extension/extension.cpp @@ -33,9 +33,6 @@ #include "natives.h" #include "clientpacks_detours.h" #include "sendprop_hookmanager.h" -#include "util.h" -#include -#include SendProxyManager g_SendProxyManager; SMEXT_LINK(&g_SendProxyManager); diff --git a/extension/extension.h b/extension/extension.h index 1087f3b..c0f0fd4 100644 --- a/extension/extension.h +++ b/extension/extension.h @@ -43,6 +43,10 @@ #include #include #include "wrappers.h" +#include +#include +#include +#include "util.h" class SendProxyManager : public SDKExtension, diff --git a/extension/natives.cpp b/extension/natives.cpp index e79020c..55b63ee 100644 --- a/extension/natives.cpp +++ b/extension/natives.cpp @@ -30,7 +30,6 @@ */ #include "natives.h" -#include "util.h" #include "sendprop_hookmanager.h" static bool IsPropValid(const SendProp *prop, PropType type) diff --git a/extension/sendprop_hookmanager.h b/extension/sendprop_hookmanager.h index ca4bb0e..61acfcf 100644 --- a/extension/sendprop_hookmanager.h +++ b/extension/sendprop_hookmanager.h @@ -9,8 +9,6 @@ #include #include -class SendProp; - class SendProxyHook { public: diff --git a/extension/sendproxy_callback.cpp b/extension/sendproxy_callback.cpp index 626721a..7a4e58f 100644 --- a/extension/sendproxy_callback.cpp +++ b/extension/sendproxy_callback.cpp @@ -1,6 +1,5 @@ #include "sendproxy_callback.h" #include "dt_send.h" -#include bool SendProxyPluginCallback(void *callback, const SendProp *pProp, ProxyVariant &variant, int element, int entity, int client) { diff --git a/extension/sendproxy_callback.h b/extension/sendproxy_callback.h index b6fd311..6422b5c 100644 --- a/extension/sendproxy_callback.h +++ b/extension/sendproxy_callback.h @@ -4,8 +4,6 @@ #include "extension.h" #include "sendproxy_variant.h" -class SendProp; - using SendProxyCallback = bool (void *callback, const SendProp *pProp, ProxyVariant &variant, int element, int entity, int client); bool SendProxyPluginCallback(void *callback, const SendProp *pProp, ProxyVariant &variant, int element, int entity, int client); From eca27e229ba019e764f2a507adcfd5fe0e1fb13f Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Sun, 7 Dec 2025 14:52:16 +0800 Subject: [PATCH 52/73] Enable Valve's memory override --- extension/AMBuilder | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extension/AMBuilder b/extension/AMBuilder index a9053eb..f7c524c 100644 --- a/extension/AMBuilder +++ b/extension/AMBuilder @@ -33,5 +33,7 @@ for sdk_name in Extension.sdks: sdk = Extension.sdks[sdk_name] binary = Extension.HL2Config(project, projectName + '.ext.' + sdk.ext, sdk) + # Memory management via g_pMemAlloc + binary.sources += [os.path.join(sdk.path, 'public', 'tier0', 'memoverride.cpp')] Extension.extensions = builder.Add(project) From 8587476a46de1156e4e1731eef778771dd432e53 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Sun, 7 Dec 2025 15:11:18 +0800 Subject: [PATCH 53/73] Update Python version to 3.12 --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 457634a..6bd7a05 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -72,9 +72,9 @@ jobs: ) - uses: actions/setup-python@v5 - name: Setup Python 3.8 + name: Setup Python 3.12 with: - python-version: 3.8 + python-version: 3.12 - name: Install Python dependencies run: | python -m pip install --upgrade pip setuptools wheel From 95db5f3a0a6311804a4d3112517fe15e8ac687c6 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Sun, 7 Dec 2025 17:28:31 +0800 Subject: [PATCH 54/73] Revert "Enable Valve's memory override" This reverts commit eca27e229ba019e764f2a507adcfd5fe0e1fb13f. --- extension/AMBuilder | 2 -- 1 file changed, 2 deletions(-) diff --git a/extension/AMBuilder b/extension/AMBuilder index f7c524c..a9053eb 100644 --- a/extension/AMBuilder +++ b/extension/AMBuilder @@ -33,7 +33,5 @@ for sdk_name in Extension.sdks: sdk = Extension.sdks[sdk_name] binary = Extension.HL2Config(project, projectName + '.ext.' + sdk.ext, sdk) - # Memory management via g_pMemAlloc - binary.sources += [os.path.join(sdk.path, 'public', 'tier0', 'memoverride.cpp')] Extension.extensions = builder.Add(project) From 0d4368d06604f4f515e1dba2f4828028a155bac6 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Sun, 7 Dec 2025 17:32:16 +0800 Subject: [PATCH 55/73] Attempt to fix invalid memory access on windows --- addons/sourcemod/gamedata/sendproxy.txt | 8 ++++++++ extension/clientpacks_detours.cpp | 14 +++----------- extension/extension.cpp | 18 +++++++++--------- extension/util.h | 11 ++++++----- extension/wrappers.h | 11 +++++------ 5 files changed, 31 insertions(+), 31 deletions(-) diff --git a/addons/sourcemod/gamedata/sendproxy.txt b/addons/sourcemod/gamedata/sendproxy.txt index 9f17b2c..53b04df 100644 --- a/addons/sourcemod/gamedata/sendproxy.txt +++ b/addons/sourcemod/gamedata/sendproxy.txt @@ -75,6 +75,14 @@ // Search string "Sending full update to Client %s (%s)", the called function with arg2 is 2048 } + "CFrameSnapshotManager::TakeTickSnapshot" + { + "library" "engine" + "linux" "@_ZN21CFrameSnapshotManager16TakeTickSnapshotEi" + "windows" "\x55\x8B\xEC\xB8\x10\x10\x00\x00\xE8\x2A\x2A\x2A\x2A\xA1\x2A\x2A\x2A\x2A\x33\xC5\x89\x45\xFC\x8B\x15" + // 55 8B EC B8 10 10 00 00 E8 ? ? ? ? A1 ? ? ? ? 33 C5 89 45 FC 8B 15 + } + "CFrameSnapshotManager::RemoveEntityReference" { "library" "engine" diff --git a/extension/clientpacks_detours.cpp b/extension/clientpacks_detours.cpp index 9ff6eb8..8ab59c1 100644 --- a/extension/clientpacks_detours.cpp +++ b/extension/clientpacks_detours.cpp @@ -123,7 +123,7 @@ DETOUR_DECL_STATIC3(SV_ComputeClientPacks, void, int, iClientCount, CGameClient clientSnapshots[0] = pSnapShot; for (int i = 1; i < iClientCount; ++i) { - clientSnapshots[i] = framesnapshotmanager->CreateEmptySnapshot(pSnapShot->m_nTickCount, pSnapShot->m_nNumEntities); + clientSnapshots[i] = framesnapshotmanager->TakeTickSnapshot(pSnapShot->m_nTickCount); CopyFrameSnapshot(clientSnapshots[i], pSnapShot); } @@ -154,7 +154,7 @@ DETOUR_DECL_STATIC3(SV_ComputeClientPacks, void, int, iClientCount, CGameClient // Pack hooked entities for each client { - ConVarSaveSet linearpack(sv_parallel_packentities, "0"); + ConVarScopedSet linearpack(sv_parallel_packentities, "0"); for (int i = 0; i < iClientCount; ++i) { @@ -287,17 +287,9 @@ static void CopyFrameSnapshot(CFrameSnapshot *dest, const CFrameSnapshot *src) Assert(dest->m_nNumEntities == src->m_nNumEntities); Q_memcpy(dest->m_pEntities, src->m_pEntities, dest->m_nNumEntities * sizeof(CFrameSnapshotEntry)); - dest->m_nValidEntities = src->m_nValidEntities; - dest->m_pValidEntities = new unsigned short[dest->m_nValidEntities]; + Assert(dest->m_nValidEntities == src->m_nValidEntities); Q_memcpy(dest->m_pValidEntities, src->m_pValidEntities, dest->m_nValidEntities * sizeof(unsigned short)); - if (src->m_pHLTVEntityData != NULL) - { - Assert(dest->m_pHLTVEntityData == NULL); - dest->m_pHLTVEntityData = new CHLTVEntityData[dest->m_nValidEntities]; - Q_memset( dest->m_pHLTVEntityData, 0, dest->m_nValidEntities * sizeof(CHLTVEntityData) ); - } - dest->m_iExplicitDeleteSlots = src->m_iExplicitDeleteSlots; // FIXME: Copy temp entity data diff --git a/extension/extension.cpp b/extension/extension.cpp index a0aa634..20f50ad 100644 --- a/extension/extension.cpp +++ b/extension/extension.cpp @@ -49,8 +49,8 @@ ISDKHooks * sdkhooks = nullptr; ConVar *sv_parallel_packentities = nullptr; CFrameSnapshotManager* framesnapshotmanager = nullptr; -void* CFrameSnapshotManager::s_pfnCreateEmptySnapshot = nullptr; -ICallWrapper* CFrameSnapshotManager::s_callCreateEmptySnapshot = nullptr; +void* CFrameSnapshotManager::s_pfnTakeTickSnapshot = nullptr; +ICallWrapper* CFrameSnapshotManager::s_callTakeTickSnapshot = nullptr; void* CFrameSnapshotManager::s_pfnRemoveEntityReference = nullptr; ICallWrapper* CFrameSnapshotManager::s_callRemoveEntityReference = nullptr; void* CFrameSnapshot::s_pfnReleaseReference = nullptr; @@ -65,7 +65,7 @@ bool SendProxyManager::SDK_OnLoad(char *error, size_t maxlen, bool late) g_szGameRulesProxy = gc_sdktools->GetKeyValue("GameRulesProxy"); - GAMECONF_GETSIGNATURE(gc, "CFrameSnapshotManager::CreateEmptySnapshot", &CFrameSnapshotManager::s_pfnCreateEmptySnapshot); + GAMECONF_GETSIGNATURE(gc, "CFrameSnapshotManager::TakeTickSnapshot", &CFrameSnapshotManager::s_pfnTakeTickSnapshot); GAMECONF_GETSIGNATURE(gc, "CFrameSnapshotManager::RemoveEntityReference", &CFrameSnapshotManager::s_pfnRemoveEntityReference); GAMECONF_GETSIGNATURE(gc, "CFrameSnapshot::ReleaseReference", &CFrameSnapshot::s_pfnReleaseReference); GAMECONF_GETADDRESS(gc, "framesnapshotmanager", &framesnapshotmanager); @@ -110,9 +110,9 @@ void SendProxyManager::SDK_OnAllLoaded() return; } - CFrameSnapshotManager::s_callCreateEmptySnapshot = bintools->CreateCall(CFrameSnapshotManager::s_pfnCreateEmptySnapshot, CallConv_ThisCall, ¶ms[2], ¶ms[0], 2); - if (CFrameSnapshotManager::s_callCreateEmptySnapshot == NULL) { - smutils->LogError(myself, "Unable to create ICallWrapper for \"CFrameSnapshotManager::CreateEmptySnapshot\"!"); + CFrameSnapshotManager::s_callTakeTickSnapshot = bintools->CreateCall(CFrameSnapshotManager::s_pfnTakeTickSnapshot, CallConv_ThisCall, ¶ms[2], ¶ms[0], 2); + if (CFrameSnapshotManager::s_callTakeTickSnapshot == NULL) { + smutils->LogError(myself, "Unable to create ICallWrapper for \"CFrameSnapshotManager::TakeTickSnapshot\"!"); return; } @@ -186,10 +186,10 @@ void SendProxyManager::SDK_OnUnload() CFrameSnapshot::s_callReleaseReference = nullptr; } - if (CFrameSnapshotManager::s_callCreateEmptySnapshot != nullptr) + if (CFrameSnapshotManager::s_callTakeTickSnapshot != nullptr) { - CFrameSnapshotManager::s_callCreateEmptySnapshot->Destroy(); - CFrameSnapshotManager::s_callCreateEmptySnapshot = nullptr; + CFrameSnapshotManager::s_callTakeTickSnapshot->Destroy(); + CFrameSnapshotManager::s_callTakeTickSnapshot = nullptr; } if (CFrameSnapshotManager::s_callRemoveEntityReference != nullptr) diff --git a/extension/util.h b/extension/util.h index 40ba070..4a2c557 100644 --- a/extension/util.h +++ b/extension/util.h @@ -9,19 +9,19 @@ return false; \ } -class ConVarSaveSet +class ConVarScopedSet { public: - explicit ConVarSaveSet(ConVar *cvar, const char *value) + explicit ConVarScopedSet(ConVar *cvar, const char *value) : m_cvar(cvar), m_savevalue(m_cvar->GetString()) { m_cvar->SetValue(value); } - ConVarSaveSet() = delete; - ConVarSaveSet(const ConVarSaveSet &other) = delete; + ConVarScopedSet() = delete; + ConVarScopedSet(const ConVarScopedSet &other) = delete; - ~ConVarSaveSet() + ~ConVarScopedSet() { m_cvar->SetValue(m_savevalue.data()); } @@ -61,6 +61,7 @@ class AutoGameConfig { gc_ = other.gc_; other.gc_ = nullptr; + return *this; } AutoGameConfig(const AutoGameConfig &other) = delete; diff --git a/extension/wrappers.h b/extension/wrappers.h index 6c00934..fccd55e 100644 --- a/extension/wrappers.h +++ b/extension/wrappers.h @@ -221,18 +221,17 @@ class CFrameSnapshotManager public: virtual ~CFrameSnapshotManager( void ); - static void* s_pfnCreateEmptySnapshot; - static ICallWrapper* s_callCreateEmptySnapshot; - inline CFrameSnapshot* CreateEmptySnapshot(int tickcount, int maxEntities) + static void* s_pfnTakeTickSnapshot; + static ICallWrapper* s_callTakeTickSnapshot; + inline CFrameSnapshot* TakeTickSnapshot(int tickcount) { struct { CFrameSnapshotManager *pThis; int tickcount; - int maxEntities; - } stack{ this, tickcount, maxEntities }; + } stack{ this, tickcount }; CFrameSnapshot *ret; - s_callCreateEmptySnapshot->Execute(&stack, &ret); + s_callTakeTickSnapshot->Execute(&stack, &ret); return ret; } From e6e2b9319b1badcf32e56b32fa0366d1fc94c9be Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Thu, 11 Dec 2025 01:39:16 +0800 Subject: [PATCH 56/73] Revert "Attempt to fix invalid memory access on windows" This reverts commit 0d4368d06604f4f515e1dba2f4828028a155bac6. --- addons/sourcemod/gamedata/sendproxy.txt | 8 -------- extension/clientpacks_detours.cpp | 12 ++++++++++-- extension/extension.cpp | 18 +++++++++--------- extension/wrappers.h | 11 ++++++----- 4 files changed, 25 insertions(+), 24 deletions(-) diff --git a/addons/sourcemod/gamedata/sendproxy.txt b/addons/sourcemod/gamedata/sendproxy.txt index 53b04df..9f17b2c 100644 --- a/addons/sourcemod/gamedata/sendproxy.txt +++ b/addons/sourcemod/gamedata/sendproxy.txt @@ -75,14 +75,6 @@ // Search string "Sending full update to Client %s (%s)", the called function with arg2 is 2048 } - "CFrameSnapshotManager::TakeTickSnapshot" - { - "library" "engine" - "linux" "@_ZN21CFrameSnapshotManager16TakeTickSnapshotEi" - "windows" "\x55\x8B\xEC\xB8\x10\x10\x00\x00\xE8\x2A\x2A\x2A\x2A\xA1\x2A\x2A\x2A\x2A\x33\xC5\x89\x45\xFC\x8B\x15" - // 55 8B EC B8 10 10 00 00 E8 ? ? ? ? A1 ? ? ? ? 33 C5 89 45 FC 8B 15 - } - "CFrameSnapshotManager::RemoveEntityReference" { "library" "engine" diff --git a/extension/clientpacks_detours.cpp b/extension/clientpacks_detours.cpp index 8ab59c1..58e297f 100644 --- a/extension/clientpacks_detours.cpp +++ b/extension/clientpacks_detours.cpp @@ -123,7 +123,7 @@ DETOUR_DECL_STATIC3(SV_ComputeClientPacks, void, int, iClientCount, CGameClient clientSnapshots[0] = pSnapShot; for (int i = 1; i < iClientCount; ++i) { - clientSnapshots[i] = framesnapshotmanager->TakeTickSnapshot(pSnapShot->m_nTickCount); + clientSnapshots[i] = framesnapshotmanager->CreateEmptySnapshot(pSnapShot->m_nTickCount, pSnapShot->m_nNumEntities); CopyFrameSnapshot(clientSnapshots[i], pSnapShot); } @@ -287,9 +287,17 @@ static void CopyFrameSnapshot(CFrameSnapshot *dest, const CFrameSnapshot *src) Assert(dest->m_nNumEntities == src->m_nNumEntities); Q_memcpy(dest->m_pEntities, src->m_pEntities, dest->m_nNumEntities * sizeof(CFrameSnapshotEntry)); - Assert(dest->m_nValidEntities == src->m_nValidEntities); + dest->m_nValidEntities = src->m_nValidEntities; + dest->m_pValidEntities = new unsigned short[dest->m_nValidEntities]; Q_memcpy(dest->m_pValidEntities, src->m_pValidEntities, dest->m_nValidEntities * sizeof(unsigned short)); + if (src->m_pHLTVEntityData != NULL) + { + Assert(dest->m_pHLTVEntityData == NULL); + dest->m_pHLTVEntityData = new CHLTVEntityData[dest->m_nValidEntities]; + Q_memset( dest->m_pHLTVEntityData, 0, dest->m_nValidEntities * sizeof(CHLTVEntityData) ); + } + dest->m_iExplicitDeleteSlots = src->m_iExplicitDeleteSlots; // FIXME: Copy temp entity data diff --git a/extension/extension.cpp b/extension/extension.cpp index 20f50ad..a0aa634 100644 --- a/extension/extension.cpp +++ b/extension/extension.cpp @@ -49,8 +49,8 @@ ISDKHooks * sdkhooks = nullptr; ConVar *sv_parallel_packentities = nullptr; CFrameSnapshotManager* framesnapshotmanager = nullptr; -void* CFrameSnapshotManager::s_pfnTakeTickSnapshot = nullptr; -ICallWrapper* CFrameSnapshotManager::s_callTakeTickSnapshot = nullptr; +void* CFrameSnapshotManager::s_pfnCreateEmptySnapshot = nullptr; +ICallWrapper* CFrameSnapshotManager::s_callCreateEmptySnapshot = nullptr; void* CFrameSnapshotManager::s_pfnRemoveEntityReference = nullptr; ICallWrapper* CFrameSnapshotManager::s_callRemoveEntityReference = nullptr; void* CFrameSnapshot::s_pfnReleaseReference = nullptr; @@ -65,7 +65,7 @@ bool SendProxyManager::SDK_OnLoad(char *error, size_t maxlen, bool late) g_szGameRulesProxy = gc_sdktools->GetKeyValue("GameRulesProxy"); - GAMECONF_GETSIGNATURE(gc, "CFrameSnapshotManager::TakeTickSnapshot", &CFrameSnapshotManager::s_pfnTakeTickSnapshot); + GAMECONF_GETSIGNATURE(gc, "CFrameSnapshotManager::CreateEmptySnapshot", &CFrameSnapshotManager::s_pfnCreateEmptySnapshot); GAMECONF_GETSIGNATURE(gc, "CFrameSnapshotManager::RemoveEntityReference", &CFrameSnapshotManager::s_pfnRemoveEntityReference); GAMECONF_GETSIGNATURE(gc, "CFrameSnapshot::ReleaseReference", &CFrameSnapshot::s_pfnReleaseReference); GAMECONF_GETADDRESS(gc, "framesnapshotmanager", &framesnapshotmanager); @@ -110,9 +110,9 @@ void SendProxyManager::SDK_OnAllLoaded() return; } - CFrameSnapshotManager::s_callTakeTickSnapshot = bintools->CreateCall(CFrameSnapshotManager::s_pfnTakeTickSnapshot, CallConv_ThisCall, ¶ms[2], ¶ms[0], 2); - if (CFrameSnapshotManager::s_callTakeTickSnapshot == NULL) { - smutils->LogError(myself, "Unable to create ICallWrapper for \"CFrameSnapshotManager::TakeTickSnapshot\"!"); + CFrameSnapshotManager::s_callCreateEmptySnapshot = bintools->CreateCall(CFrameSnapshotManager::s_pfnCreateEmptySnapshot, CallConv_ThisCall, ¶ms[2], ¶ms[0], 2); + if (CFrameSnapshotManager::s_callCreateEmptySnapshot == NULL) { + smutils->LogError(myself, "Unable to create ICallWrapper for \"CFrameSnapshotManager::CreateEmptySnapshot\"!"); return; } @@ -186,10 +186,10 @@ void SendProxyManager::SDK_OnUnload() CFrameSnapshot::s_callReleaseReference = nullptr; } - if (CFrameSnapshotManager::s_callTakeTickSnapshot != nullptr) + if (CFrameSnapshotManager::s_callCreateEmptySnapshot != nullptr) { - CFrameSnapshotManager::s_callTakeTickSnapshot->Destroy(); - CFrameSnapshotManager::s_callTakeTickSnapshot = nullptr; + CFrameSnapshotManager::s_callCreateEmptySnapshot->Destroy(); + CFrameSnapshotManager::s_callCreateEmptySnapshot = nullptr; } if (CFrameSnapshotManager::s_callRemoveEntityReference != nullptr) diff --git a/extension/wrappers.h b/extension/wrappers.h index fccd55e..6c00934 100644 --- a/extension/wrappers.h +++ b/extension/wrappers.h @@ -221,17 +221,18 @@ class CFrameSnapshotManager public: virtual ~CFrameSnapshotManager( void ); - static void* s_pfnTakeTickSnapshot; - static ICallWrapper* s_callTakeTickSnapshot; - inline CFrameSnapshot* TakeTickSnapshot(int tickcount) + static void* s_pfnCreateEmptySnapshot; + static ICallWrapper* s_callCreateEmptySnapshot; + inline CFrameSnapshot* CreateEmptySnapshot(int tickcount, int maxEntities) { struct { CFrameSnapshotManager *pThis; int tickcount; - } stack{ this, tickcount }; + int maxEntities; + } stack{ this, tickcount, maxEntities }; CFrameSnapshot *ret; - s_callTakeTickSnapshot->Execute(&stack, &ret); + s_callCreateEmptySnapshot->Execute(&stack, &ret); return ret; } From ef1cce429b247cfe86d68c5d831e0e6c7ae88182 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Thu, 11 Dec 2025 17:05:14 +0800 Subject: [PATCH 57/73] Enable Valve's memory override --- extension/AMBuilder | 1 + extension/sdk/memoverride.cpp | 1589 +++++++++++++++++++++++++++++++++ 2 files changed, 1590 insertions(+) create mode 100644 extension/sdk/memoverride.cpp diff --git a/extension/AMBuilder b/extension/AMBuilder index a9053eb..d2a3e24 100644 --- a/extension/AMBuilder +++ b/extension/AMBuilder @@ -5,6 +5,7 @@ projectName = 'sendproxy' # smsdk_ext.cpp will be automatically added later sourceFiles = [ + 'sdk/memoverride.cpp', 'extension.cpp', 'CDetour/detours.cpp', 'asm/asm.c', diff --git a/extension/sdk/memoverride.cpp b/extension/sdk/memoverride.cpp new file mode 100644 index 0000000..14704c6 --- /dev/null +++ b/extension/sdk/memoverride.cpp @@ -0,0 +1,1589 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: Insert this file into all projects using the memory system +// It will cause that project to use the shader memory allocator +// +// $NoKeywords: $ +//=============================================================================// + + +#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) + +#undef PROTECTED_THINGS_ENABLE // allow use of _vsnprintf + +#if defined( _WIN32 ) && !defined( _X360 ) +#define WIN_32_LEAN_AND_MEAN +#include +#endif + +#include "tier0/dbg.h" +#include "tier0/memalloc.h" +#include +#include +#include "memdbgoff.h" + +// Tags this DLL as debug +#if defined( _DEBUG ) && !defined( _X360 ) +DLL_EXPORT void BuiltDebug() {} +#endif + +#ifdef _WIN32 +// ARG: crtdbg is necessary for certain definitions below, +// but it also redefines malloc as a macro in release. +// To disable this, we gotta define _DEBUG before including it.. BLEAH! +#define _DEBUG 1 +#include "crtdbg.h" +#ifdef NDEBUG +#undef _DEBUG +#endif + +// Turn this back off in release mode. +#ifdef NDEBUG +#undef _DEBUG +#endif +#elif _LINUX +#define __cdecl +#endif + +#if defined( _WIN32 ) && !defined( _X360 ) + + +// Need to be able to define these if we link to a debug static lib! +#undef _malloc_dbg +#undef _calloc_dbg +#undef _realloc_dbg +#undef _expand_dbg +#undef _free_dbg +#undef _msize_dbg +#undef _CrtDbgReport + + + +const char *MakeModuleFileName() +{ + if ( g_pMemAlloc->IsDebugHeap() ) + { + char *pszModuleName = (char *)HeapAlloc( GetProcessHeap(), 0, MAX_PATH ); // small leak, debug only + + MEMORY_BASIC_INFORMATION mbi; + static int dummy; + VirtualQuery( &dummy, &mbi, sizeof(mbi) ); + + GetModuleFileName( reinterpret_cast(mbi.AllocationBase), pszModuleName, MAX_PATH ); + char *pDot = strrchr( pszModuleName, '.' ); + if ( pDot ) + { + char *pSlash = strrchr( pszModuleName, '\\' ); + if ( pSlash ) + { + pszModuleName = pSlash + 1; + *pDot = 0; + } + } + + return pszModuleName; + } + return NULL; +} + +static void *AllocUnattributed( size_t nSize ) +{ + static const char *pszOwner = MakeModuleFileName(); + + if ( !pszOwner ) + return g_pMemAlloc->Alloc(nSize); + else + return g_pMemAlloc->Alloc(nSize, pszOwner, 0); +} + +static void *ReallocUnattributed( void *pMem, size_t nSize ) +{ + static const char *pszOwner = MakeModuleFileName(); + + if ( !pszOwner ) + return g_pMemAlloc->Realloc(pMem, nSize); + else + return g_pMemAlloc->Realloc(pMem, nSize, pszOwner, 0); +} + +#else +#define MakeModuleFileName() NULL +inline void *AllocUnattributed( size_t nSize ) +{ + return g_pMemAlloc->Alloc(nSize); +} + +inline void *ReallocUnattributed( void *pMem, size_t nSize ) +{ + return g_pMemAlloc->Realloc(pMem, nSize); +} +#endif + +//----------------------------------------------------------------------------- +// Standard functions in the CRT that we're going to override to call our allocator +//----------------------------------------------------------------------------- +#if defined(_WIN32) && !defined(_STATIC_LINKED) +// this magic only works under win32 +// under linux this malloc() overrides the libc malloc() and so we +// end up in a recursion (as g_pMemAlloc->Alloc() calls malloc) +#if _MSC_VER >= 1930 +#define ALLOC_CALL _CRTRESTRICT +#define FREE_CALL +#elif _MSC_VER >= 1400 +#define ALLOC_CALL _CRTNOALIAS _CRTRESTRICT +#define FREE_CALL _CRTNOALIAS +#else +#define ALLOC_CALL +#define FREE_CALL +#endif + +extern "C" +{ + +ALLOC_CALL void *malloc( size_t nSize ) +{ + return AllocUnattributed( nSize ); +} + +FREE_CALL void free( void *pMem ) +{ + g_pMemAlloc->Free(pMem); +} + +ALLOC_CALL void *realloc( void *pMem, size_t nSize ) +{ + return ReallocUnattributed( pMem, nSize ); +} + +ALLOC_CALL void *calloc( size_t nCount, size_t nElementSize ) +{ + void *pMem = AllocUnattributed( nElementSize * nCount ); + memset(pMem, 0, nElementSize * nCount); + return pMem; +} + +} // end extern "C" + +//----------------------------------------------------------------------------- +// Non-standard MSVC functions that we're going to override to call our allocator +//----------------------------------------------------------------------------- +extern "C" +{ +#if _MSC_VER >= 1930 + + +_CRTRESTRICT void* __cdecl _malloc_base(size_t nSize) +{ + return AllocUnattributed(nSize); +} + +_CRTRESTRICT void* __cdecl _calloc_base(size_t nCount, size_t nSize) +{ + size_t nBytes = nCount * nSize; + void* pMem = AllocUnattributed(nBytes); + memset(pMem, 0, nBytes); + return pMem; +} + +_CRTRESTRICT void* __cdecl _realloc_base( void *pMem, size_t nSize ) +{ + return ReallocUnattributed( pMem, nSize ); +} + +_CRTRESTRICT void* __cdecl _recalloc_base( void *pMem, size_t nCount, size_t nSize) +{ + size_t nBytes = nCount * nSize; + size_t nBytesOld = _msize(pMem); + + void *pMemOut = ReallocUnattributed( pMem, nBytes ); + if(nBytes > nBytesOld) + memset(reinterpret_cast(pMemOut) + nBytesOld , 0, nBytes - nBytesOld); + + return pMemOut; +} + +#else + +// 64-bit +#ifdef _WIN64 +void* __cdecl _malloc_base( size_t nSize ) +{ + return AllocUnattributed( nSize ); +} +#else +void *_malloc_base( size_t nSize ) +{ + return AllocUnattributed( nSize ); +} +#endif + +void *_calloc_base( size_t nSize ) +{ + void *pMem = AllocUnattributed( nSize ); + memset(pMem, 0, nSize); + return pMem; +} + +void *_realloc_base( void *pMem, size_t nSize ) +{ + return ReallocUnattributed( pMem, nSize ); +} + +void *_recalloc_base( void *pMem, size_t nSize ) +{ + void *pMemOut = ReallocUnattributed( pMem, nSize ); + memset(pMemOut, 0, nSize); + return pMemOut; +} +#endif + + +void _free_base( void *pMem ) +{ + g_pMemAlloc->Free(pMem); +} + +void *__cdecl _expand_base( void *pMem, size_t nNewSize, int nBlockUse ) +{ + Assert( 0 ); + return NULL; +} + +// crt +void * __cdecl _malloc_crt(size_t size) +{ + return AllocUnattributed( size ); +} + +void * __cdecl _calloc_crt(size_t count, size_t size) +{ +#if _MSC_VER >= 1930 + return _calloc_base( count, size ); +#else + return _calloc_base( count * size ); +#endif +} + +void * __cdecl _realloc_crt(void *ptr, size_t size) +{ + return _realloc_base( ptr, size ); +} + +void * __cdecl _recalloc_crt(void *ptr, size_t count, size_t size) +{ +#if _MSC_VER >= 1930 + return _recalloc_base(ptr, count, size); +#else + return _recalloc_base( ptr, count * size ); +#endif +} + +ALLOC_CALL void * __cdecl _recalloc ( void * memblock, size_t count, size_t size ) +{ + void *pMem = ReallocUnattributed( memblock, size * count ); + memset( pMem, 0, size * count ); + return pMem; +} + +size_t _msize_base( void *pMem ) +#ifdef _CRT_NOEXCEPT +_CRT_NOEXCEPT +#endif +{ + return g_pMemAlloc->GetSize(pMem); +} + +size_t _msize( void *pMem ) +{ + return _msize_base(pMem); +} + +size_t msize( void *pMem ) +{ + return g_pMemAlloc->GetSize(pMem); +} + +void *__cdecl _heap_alloc( size_t nSize ) +{ + return AllocUnattributed( nSize ); +} + +void *__cdecl _nh_malloc( size_t nSize, int ) +{ + return AllocUnattributed( nSize ); +} + +void *__cdecl _expand( void *pMem, size_t nSize ) +{ + Assert( 0 ); + return NULL; +} + +unsigned int _amblksiz = 16; //BYTES_PER_PARA; + +#if _MSC_VER >= 1400 +HANDLE _crtheap = (HANDLE)1; // PatM Can't be 0 or CRT pukes +int __active_heap = 1; +#endif // _MSC_VER >= 1400 + +size_t __cdecl _get_sbh_threshold( void ) +{ + return 0; +} + +int __cdecl _set_sbh_threshold( size_t ) +{ + return 0; +} + +int _heapchk() +{ + return g_pMemAlloc->heapchk(); +} + +int _heapmin() +{ + return 1; +} + +int __cdecl _heapadd( void *, size_t ) +{ + return 0; +} + +int __cdecl _heapset( unsigned int ) +{ + return 0; +} + +size_t __cdecl _heapused( size_t *, size_t * ) +{ + return 0; +} + +#ifdef _WIN32 +int __cdecl _heapwalk( _HEAPINFO * ) +{ + return 0; +} +#endif + +} // end extern "C" + + +//----------------------------------------------------------------------------- +// Debugging functions that we're going to override to call our allocator +// NOTE: These have to be here for release + debug builds in case we +// link to a debug static lib!!! +//----------------------------------------------------------------------------- + +extern "C" +{ + +void *malloc_db( size_t nSize, const char *pFileName, int nLine ) +{ + return g_pMemAlloc->Alloc(nSize, pFileName, nLine); +} + +void free_db( void *pMem, const char *pFileName, int nLine ) +{ + g_pMemAlloc->Free(pMem, pFileName, nLine); +} + +void *realloc_db( void *pMem, size_t nSize, const char *pFileName, int nLine ) +{ + return g_pMemAlloc->Realloc(pMem, nSize, pFileName, nLine); +} + +} // end extern "C" + +//----------------------------------------------------------------------------- +// These methods are standard MSVC heap initialization + shutdown methods +//----------------------------------------------------------------------------- +extern "C" +{ + +#if !defined( _X360 ) + int __cdecl _heap_init() + { + return g_pMemAlloc != NULL; + } + + void __cdecl _heap_term() + { + } +#endif + +} +#endif + + +//----------------------------------------------------------------------------- +// Prevents us from using an inappropriate new or delete method, +// ensures they are here even when linking against debug or release static libs +//----------------------------------------------------------------------------- +#ifndef NO_MEMOVERRIDE_NEW_DELETE +void *__cdecl operator new( unsigned int nSize ) +{ + return AllocUnattributed( nSize ); +} + +void *__cdecl operator new( unsigned int nSize, int nBlockUse, const char *pFileName, int nLine ) +{ + return g_pMemAlloc->Alloc(nSize, pFileName, nLine); +} + +void __cdecl operator delete( void *pMem ) +{ + g_pMemAlloc->Free( pMem ); +} + +void *__cdecl operator new[] ( unsigned int nSize ) +{ + return AllocUnattributed( nSize ); +} + +void *__cdecl operator new[] ( unsigned int nSize, int nBlockUse, const char *pFileName, int nLine ) +{ + return g_pMemAlloc->Alloc(nSize, pFileName, nLine); +} + +void __cdecl operator delete[] ( void *pMem ) +{ + g_pMemAlloc->Free( pMem ); +} +#endif + + +//----------------------------------------------------------------------------- +// Override some debugging allocation methods in MSVC +// NOTE: These have to be here for release + debug builds in case we +// link to a debug static lib!!! +//----------------------------------------------------------------------------- +#ifndef _STATIC_LINKED +#ifdef _WIN32 + +// This here just hides the internal file names, etc of allocations +// made in the c runtime library +#define CRT_INTERNAL_FILE_NAME "C-runtime internal" + +class CAttibCRT +{ +public: + CAttibCRT(int nBlockUse) : m_nBlockUse(nBlockUse) + { + if (m_nBlockUse == _CRT_BLOCK) + { + g_pMemAlloc->PushAllocDbgInfo(CRT_INTERNAL_FILE_NAME, 0); + } + } + + ~CAttibCRT() + { + if (m_nBlockUse == _CRT_BLOCK) + { + g_pMemAlloc->PopAllocDbgInfo(); + } + } + +private: + int m_nBlockUse; +}; + + +#define AttribIfCrt() CAttibCRT _attrib(nBlockUse) +#elif defined(_LINUX) +#define AttribIfCrt() +#endif // _WIN32 + + +extern "C" +{ + +void *__cdecl _nh_malloc_dbg( size_t nSize, int nFlag, int nBlockUse, + const char *pFileName, int nLine ) +{ + AttribIfCrt(); + return g_pMemAlloc->Alloc(nSize, pFileName, nLine); +} + +void *__cdecl _malloc_dbg( size_t nSize, int nBlockUse, + const char *pFileName, int nLine ) +{ + AttribIfCrt(); + return g_pMemAlloc->Alloc(nSize, pFileName, nLine); +} + +void *__cdecl _calloc_dbg( size_t nNum, size_t nSize, int nBlockUse, + const char *pFileName, int nLine ) +{ + AttribIfCrt(); + void *pMem = g_pMemAlloc->Alloc(nSize * nNum, pFileName, nLine); + memset(pMem, 0, nSize * nNum); + return pMem; +} + +void *__cdecl _realloc_dbg( void *pMem, size_t nNewSize, int nBlockUse, + const char *pFileName, int nLine ) +{ + AttribIfCrt(); + return g_pMemAlloc->Realloc(pMem, nNewSize, pFileName, nLine); +} + +void *__cdecl _expand_dbg( void *pMem, size_t nNewSize, int nBlockUse, + const char *pFileName, int nLine ) +{ + Assert( 0 ); + return NULL; +} + +void __cdecl _free_dbg( void *pMem, int nBlockUse ) +{ + AttribIfCrt(); + g_pMemAlloc->Free(pMem); +} + +size_t __cdecl _msize_dbg( void *pMem, int nBlockUse ) +{ +#ifdef _WIN32 + return _msize(pMem); +#elif _LINUX + Assert( "_msize_dbg unsupported" ); + return 0; +#endif +} + + +#ifdef _WIN32 + +#if defined(_DEBUG) && _MSC_VER >= 1300 +// X360TBD: aligned and offset allocations may be important on the 360 + +// aligned base +ALLOC_CALL void *__cdecl _aligned_malloc_base( size_t size, size_t align ) +{ + return MemAlloc_AllocAligned( size, align ); +} + +ALLOC_CALL void *__cdecl _aligned_realloc_base( void *ptr, size_t size, size_t align ) +{ + return MemAlloc_ReallocAligned( ptr, size, align ); +} + +ALLOC_CALL void *__cdecl _aligned_recalloc_base( void *ptr, size_t size, size_t align ) +{ + Error( "Unsupported function\n" ); + return NULL; +} + +FREE_CALL void __cdecl _aligned_free_base( void *ptr ) +{ + MemAlloc_FreeAligned( ptr ); +} + +// aligned +ALLOC_CALL void * __cdecl _aligned_malloc( size_t size, size_t align ) +{ + return _aligned_malloc_base(size, align); +} + +ALLOC_CALL void *__cdecl _aligned_realloc(void *memblock, size_t size, size_t align) +{ + return _aligned_realloc_base(memblock, size, align); +} + +ALLOC_CALL void * __cdecl _aligned_recalloc( void * memblock, size_t count, size_t size, size_t align ) +{ + return _aligned_recalloc_base(memblock, count * size, align); +} + +FREE_CALL void __cdecl _aligned_free( void *memblock ) +{ + _aligned_free_base(memblock); +} + +// aligned offset base +ALLOC_CALL void * __cdecl _aligned_offset_malloc_base( size_t size, size_t align, size_t offset ) +{ + Assert( IsPC() || 0 ); + return NULL; +} + +ALLOC_CALL void * __cdecl _aligned_offset_realloc_base( void * memblock, size_t size, size_t align, size_t offset) +{ + Assert( IsPC() || 0 ); + return NULL; +} + +ALLOC_CALL void * __cdecl _aligned_offset_recalloc_base( void * memblock, size_t size, size_t align, size_t offset) +{ + Assert( IsPC() || 0 ); + return NULL; +} + +// aligned offset +ALLOC_CALL void *__cdecl _aligned_offset_malloc(size_t size, size_t align, size_t offset) +{ + return _aligned_offset_malloc_base( size, align, offset ); +} + +ALLOC_CALL void *__cdecl _aligned_offset_realloc(void *memblock, size_t size, size_t align, size_t offset) +{ + return _aligned_offset_realloc_base( memblock, size, align, offset ); +} + +ALLOC_CALL void * __cdecl _aligned_offset_recalloc( void * memblock, size_t count, size_t size, size_t align, size_t offset ) +{ + return _aligned_offset_recalloc_base( memblock, count * size, align, offset ); +} + +#endif // _MSC_VER >= 1400 + +#endif + +} // end extern "C" + + +//----------------------------------------------------------------------------- +// Override some the _CRT debugging allocation methods in MSVC +//----------------------------------------------------------------------------- +#ifdef _WIN32 + +extern "C" +{ + +int _CrtDumpMemoryLeaks(void) +{ + return 0; +} + +_CRT_DUMP_CLIENT _CrtSetDumpClient( _CRT_DUMP_CLIENT dumpClient ) +{ + return NULL; +} + +int _CrtSetDbgFlag( int nNewFlag ) +{ + return g_pMemAlloc->CrtSetDbgFlag( nNewFlag ); +} + +// 64-bit port. +#define AFNAME(var) __p_ ## var +#define AFRET(var) &var +#if _MSC_VER >= 1930 +#undef _crtDbgFlag +#undef _crtBreakAlloc +#endif + +int _crtDbgFlag = _CRTDBG_ALLOC_MEM_DF; +int* AFNAME(_crtDbgFlag)(void) +{ + return AFRET(_crtDbgFlag); +} + +long _crtBreakAlloc; /* Break on this allocation */ +long* AFNAME(_crtBreakAlloc) (void) +{ + return AFRET(_crtBreakAlloc); +} + +void __cdecl _CrtSetDbgBlockType( void *pMem, int nBlockUse ) +{ + DebuggerBreak(); +} + +_CRT_ALLOC_HOOK __cdecl _CrtSetAllocHook( _CRT_ALLOC_HOOK pfnNewHook ) +{ + DebuggerBreak(); + return NULL; +} + +long __cdecl _CrtSetBreakAlloc( long lNewBreakAlloc ) +{ + return g_pMemAlloc->CrtSetBreakAlloc( lNewBreakAlloc ); +} + +int __cdecl _CrtIsValidHeapPointer( const void *pMem ) +{ + return g_pMemAlloc->CrtIsValidHeapPointer( pMem ); +} + +int __cdecl _CrtIsValidPointer( const void *pMem, unsigned int size, int access ) +{ + return g_pMemAlloc->CrtIsValidPointer( pMem, size, access ); +} + +int __cdecl _CrtCheckMemory( void ) +{ + // FIXME: Remove this when we re-implement the heap + return g_pMemAlloc->CrtCheckMemory( ); +} + +int __cdecl _CrtIsMemoryBlock( const void *pMem, unsigned int nSize, + long *plRequestNumber, char **ppFileName, int *pnLine ) +{ + DebuggerBreak(); + return 1; +} + +int __cdecl _CrtMemDifference( _CrtMemState *pState, const _CrtMemState * oldState, const _CrtMemState * newState ) +{ + DebuggerBreak(); + return FALSE; +} + +void __cdecl _CrtMemDumpStatistics( const _CrtMemState *pState ) +{ + DebuggerBreak(); +} + +void __cdecl _CrtMemCheckpoint( _CrtMemState *pState ) +{ + // FIXME: Remove this when we re-implement the heap + g_pMemAlloc->CrtMemCheckpoint( pState ); +} + +void __cdecl _CrtMemDumpAllObjectsSince( const _CrtMemState *pState ) +{ + DebuggerBreak(); +} + +void __cdecl _CrtDoForAllClientObjects( void (*pfn)(void *, void *), void * pContext ) +{ + DebuggerBreak(); +} + + +//----------------------------------------------------------------------------- +// Methods in dbgrpt.cpp +//----------------------------------------------------------------------------- +long _crtAssertBusy = -1; + +int __cdecl _CrtSetReportMode( int nReportType, int nReportMode ) +{ + return g_pMemAlloc->CrtSetReportMode( nReportType, nReportMode ); +} + +_HFILE __cdecl _CrtSetReportFile( int nRptType, _HFILE hFile ) +{ + return (_HFILE)g_pMemAlloc->CrtSetReportFile( nRptType, hFile ); +} + +_CRT_REPORT_HOOK __cdecl _CrtSetReportHook( _CRT_REPORT_HOOK pfnNewHook ) +{ + return (_CRT_REPORT_HOOK)g_pMemAlloc->CrtSetReportHook( pfnNewHook ); +} + +int __cdecl _CrtDbgReport( int nRptType, const char * szFile, + int nLine, const char * szModule, const char * szFormat, ... ) +{ + static char output[1024]; + va_list args; + va_start( args, szFormat ); + _vsnprintf( output, sizeof( output )-1, szFormat, args ); + va_end( args ); + + return g_pMemAlloc->CrtDbgReport( nRptType, szFile, nLine, szModule, output ); +} + +#if _MSC_VER >= 1400 + +#if defined( _DEBUG ) + +// wrapper which passes no debug info; not available in debug +void __cdecl _invalid_parameter_noinfo(void) +{ + Assert(0); +} + +#endif /* defined( _DEBUG ) */ + +#if defined( _DEBUG ) || defined( USE_MEM_DEBUG ) + +int __cdecl __crtMessageWindowW( int nRptType, const wchar_t * szFile, const wchar_t * szLine, + const wchar_t * szModule, const wchar_t * szUserMessage ) +{ + Assert(0); + return 0; +} + +int __cdecl _CrtDbgReportV( int nRptType, const wchar_t *szFile, int nLine, + const wchar_t *szModule, const wchar_t *szFormat, va_list arglist ) +{ + Assert(0); + return 0; +} + +int __cdecl _CrtDbgReportW( int nRptType, const wchar_t *szFile, int nLine, + const wchar_t *szModule, const wchar_t *szFormat, ...) +{ + Assert(0); + return 0; +} + +int __cdecl _VCrtDbgReportA( int nRptType, const wchar_t * szFile, int nLine, + const wchar_t * szModule, const wchar_t * szFormat, va_list arglist ) +{ + Assert(0); + return 0; +} + +int __cdecl _CrtSetReportHook2( int mode, _CRT_REPORT_HOOK pfnNewHook ) +{ + _CrtSetReportHook( pfnNewHook ); + return 0; +} + + +#endif /* defined( _DEBUG ) || defined( USE_MEM_DEBUG ) */ + +extern "C" int __crtDebugCheckCount = FALSE; + +extern "C" int __cdecl _CrtSetCheckCount( int fCheckCount ) +{ + int oldCheckCount = __crtDebugCheckCount; + return oldCheckCount; +} + +extern "C" int __cdecl _CrtGetCheckCount( void ) +{ + return __crtDebugCheckCount; +} + +// aligned offset debug +extern "C" void * __cdecl _aligned_offset_recalloc_dbg( void * memblock, size_t count, size_t size, size_t align, size_t offset, const char * f_name, int line_n ) +{ + Assert( IsPC() || 0 ); + void *pMem = ReallocUnattributed( memblock, size * count ); + memset( pMem, 0, size * count ); + return pMem; +} + +extern "C" void * __cdecl _aligned_recalloc_dbg( void *memblock, size_t count, size_t size, size_t align, const char * f_name, int line_n ) +{ + return _aligned_offset_recalloc_dbg(memblock, count, size, align, 0, f_name, line_n); +} + +extern "C" void * __cdecl _recalloc_dbg ( void * memblock, size_t count, size_t size, int nBlockUse, const char * szFileName, int nLine ) +{ + return _aligned_offset_recalloc_dbg(memblock, count, size, 0, 0, szFileName, nLine); +} + +_CRT_REPORT_HOOK __cdecl _CrtGetReportHook( void ) +{ + return NULL; +} + +#endif +int __cdecl _CrtReportBlockType(const void * pUserData) +{ + return 0; +} + + +} // end extern "C" +#endif // _WIN32 + +// Most files include this file, so when it's used it adds an extra .ValveDbg section, +// to help identify debug binaries. +#ifdef _WIN32 + #ifndef NDEBUG // _DEBUG + #pragma data_seg("ValveDBG") + volatile const char* DBG = "*** DEBUG STUB ***"; + #endif +#endif + +#endif + +// Extras added prevent dbgheap.obj from being included - DAL +#ifdef _WIN32 + +extern "C" +{ +size_t __crtDebugFillThreshold = 0; + +extern "C" void * __cdecl _heap_alloc_base (size_t size) { + assert(0); + return NULL; +} + + +void * __cdecl _heap_alloc_dbg( size_t nSize, int nBlockUse, const char * szFileName, int nLine) +{ + return _heap_alloc(nSize); +} + +// 64-bit +#ifdef _WIN64 +static void * __cdecl realloc_help( void * pUserData, size_t * pnNewSize, int nBlockUse,const char * szFileName, + int nLine, int fRealloc ) +{ + assert(0); // Shouldn't be needed + return NULL; +} +#else +static void * __cdecl realloc_help( void * pUserData, size_t nNewSize, int nBlockUse, const char * szFileName, + int nLine, int fRealloc) +{ + assert(0); // Shouldn't be needed + return NULL; +} +#endif + +void __cdecl _free_nolock( void * pUserData) +{ + // I don't think the second param is used in memoverride + _free_dbg(pUserData, 0); +} + +void __cdecl _free_dbg_nolock( void * pUserData, int nBlockUse) +{ + _free_dbg(pUserData, 0); +} + +_CRT_ALLOC_HOOK __cdecl _CrtGetAllocHook ( void) +{ + assert(0); + return NULL; +} + +static int __cdecl CheckBytes( unsigned char * pb, unsigned char bCheck, size_t nSize) +{ + int bOkay = TRUE; + return bOkay; +} + + +_CRT_DUMP_CLIENT __cdecl _CrtGetDumpClient ( void) +{ + assert(0); + return NULL; +} + +#if _MSC_VER >= 1400 +static void __cdecl _printMemBlockData( _locale_t plocinfo, _CrtMemBlockHeader * pHead) +{ +} + +static void __cdecl _CrtMemDumpAllObjectsSince_stat( const _CrtMemState * state, _locale_t plocinfo) +{ +} +#endif +void * __cdecl _aligned_malloc_dbg( size_t size, size_t align, const char * f_name, int line_n) +{ + return _aligned_malloc(size, align); +} + +void * __cdecl _aligned_realloc_dbg( void *memblock, size_t size, size_t align, + const char * f_name, int line_n) +{ + return _aligned_realloc(memblock, size, align); +} + +void * __cdecl _aligned_offset_malloc_dbg( size_t size, size_t align, size_t offset, + const char * f_name, int line_n) +{ + return _aligned_offset_malloc(size, align, offset); +} + +void * __cdecl _aligned_offset_realloc_dbg( void * memblock, size_t size, size_t align, + size_t offset, const char * f_name, int line_n) +{ + return _aligned_offset_realloc(memblock, size, align, offset); +} + +void __cdecl _aligned_free_dbg( void * memblock) +{ + _aligned_free(memblock); +} + +size_t __cdecl _CrtSetDebugFillThreshold( size_t _NewDebugFillThreshold) +{ + assert(0); + return 0; +} + +//=========================================== +// NEW!!! 64-bit + +char * __cdecl _strdup ( const char * string ) +{ + int nSize = strlen(string) + 1; + char *pCopy = (char*)AllocUnattributed( nSize ); + if ( pCopy ) + memcpy( pCopy, string, nSize ); + return pCopy; +} + +#if 0 +_TSCHAR * __cdecl _tfullpath_dbg ( _TSCHAR *UserBuf, const _TSCHAR *path, size_t maxlen, int nBlockUse, const char * szFileName, int nLine ) +{ + Assert(0); + return NULL; +} + +_TSCHAR * __cdecl _tfullpath ( _TSCHAR *UserBuf, const _TSCHAR *path, size_t maxlen ) +{ + Assert(0); + return NULL; +} + +_TSCHAR * __cdecl _tgetdcwd_lk_dbg ( int drive, _TSCHAR *pnbuf, int maxlen, int nBlockUse, const char * szFileName, int nLine ) +{ + Assert(0); + return NULL; +} + +_TSCHAR * __cdecl _tgetdcwd_nolock ( int drive, _TSCHAR *pnbuf, int maxlen ) +{ + Assert(0); + return NULL; +} + +errno_t __cdecl _tdupenv_s_helper ( _TSCHAR **pBuffer, size_t *pBufferSizeInTChars, const _TSCHAR *varname, int nBlockUse, const char * szFileName, int nLine ) +{ + Assert(0); + return 0; +} + +errno_t __cdecl _tdupenv_s_helper ( _TSCHAR **pBuffer, size_t *pBufferSizeInTChars, const _TSCHAR *varname ) +{ + Assert(0); + return 0; +} + +_TSCHAR * __cdecl _ttempnam_dbg ( const _TSCHAR *dir, const _TSCHAR *pfx, int nBlockUse, const char * szFileName, int nLine ) +{ + Assert(0); + return 0; +} + +_TSCHAR * __cdecl _ttempnam ( const _TSCHAR *dir, const _TSCHAR *pfx ) +{ + Assert(0); + return 0; +} +#endif + +wchar_t * __cdecl _wcsdup_dbg ( const wchar_t * string, int nBlockUse, const char * szFileName, int nLine ) +{ + Assert(0); + return 0; +} + +wchar_t * __cdecl _wcsdup ( const wchar_t * string ) +{ + Assert(0); + return 0; +} + +} // end extern "C" + +#if _MSC_VER >= 1400 + +//----------------------------------------------------------------------------- +// XBox Memory Allocator Override +//----------------------------------------------------------------------------- +#if defined( _X360 ) +#if defined( _DEBUG ) || defined( USE_MEM_DEBUG ) +#include "UtlMap.h" + +MEMALLOC_DEFINE_EXTERNAL_TRACKING( XMem ); + +CThreadFastMutex g_XMemAllocMutex; + +void XMemAlloc_RegisterAllocation( void *p, DWORD dwAllocAttributes ) +{ + if ( !g_pMemAlloc ) + { + // core xallocs cannot be journaled until system is ready + return; + } + + AUTO_LOCK_FM( g_XMemAllocMutex ); + int size = XMemSize( p, dwAllocAttributes ); + MemAlloc_RegisterExternalAllocation( XMem, p, size ); +} + +void XMemAlloc_RegisterDeallocation( void *p, DWORD dwAllocAttributes ) +{ + if ( !g_pMemAlloc ) + { + // core xallocs cannot be journaled until system is ready + return; + } + + AUTO_LOCK_FM( g_XMemAllocMutex ); + int size = XMemSize( p, dwAllocAttributes ); + MemAlloc_RegisterExternalDeallocation( XMem, p, size ); +} + +#else + +#define XMemAlloc_RegisterAllocation( p, a ) ((void)0) +#define XMemAlloc_RegisterDeallocation( p, a ) ((void)0) + +#endif + +//----------------------------------------------------------------------------- +// XMemAlloc +// +// XBox Memory Allocator Override +//----------------------------------------------------------------------------- +LPVOID WINAPI XMemAlloc( SIZE_T dwSize, DWORD dwAllocAttributes ) +{ + LPVOID ptr; + XALLOC_ATTRIBUTES *pAttribs = (XALLOC_ATTRIBUTES *)&dwAllocAttributes; + bool bPhysical = ( pAttribs->dwMemoryType == XALLOC_MEMTYPE_PHYSICAL ); + + if ( !bPhysical && !pAttribs->dwHeapTracksAttributes && pAttribs->dwAllocatorId != eXALLOCAllocatorId_XUI ) + { + MEM_ALLOC_CREDIT(); + switch ( pAttribs->dwAlignment ) + { + case XALLOC_ALIGNMENT_4: + ptr = g_pMemAlloc->Alloc( dwSize ); + break; + case XALLOC_ALIGNMENT_8: + ptr = MemAlloc_AllocAligned( dwSize, 8 ); + break; + case XALLOC_ALIGNMENT_DEFAULT: + case XALLOC_ALIGNMENT_16: + default: + ptr = MemAlloc_AllocAligned( dwSize, 16 ); + break; + } + if ( pAttribs->dwZeroInitialize != 0 ) + { + memset( ptr, 0, XMemSize( ptr, dwAllocAttributes ) ); + } + return ptr; + } + + ptr = XMemAllocDefault( dwSize, dwAllocAttributes ); + if ( ptr ) + { + XMemAlloc_RegisterAllocation( ptr, dwAllocAttributes ); + } + + return ptr; +} + +//----------------------------------------------------------------------------- +// XMemFree +// +// XBox Memory Allocator Override +//----------------------------------------------------------------------------- +VOID WINAPI XMemFree( PVOID pAddress, DWORD dwAllocAttributes ) +{ + if ( !pAddress ) + { + return; + } + + XALLOC_ATTRIBUTES *pAttribs = (XALLOC_ATTRIBUTES *)&dwAllocAttributes; + bool bPhysical = ( pAttribs->dwMemoryType == XALLOC_MEMTYPE_PHYSICAL ); + + if ( !bPhysical && !pAttribs->dwHeapTracksAttributes && pAttribs->dwAllocatorId != eXALLOCAllocatorId_XUI ) + { + switch ( pAttribs->dwAlignment ) + { + case XALLOC_ALIGNMENT_4: + return g_pMemAlloc->Free( pAddress ); + default: + return MemAlloc_FreeAligned( pAddress ); + } + return; + } + + XMemAlloc_RegisterDeallocation( pAddress, dwAllocAttributes ); + + XMemFreeDefault( pAddress, dwAllocAttributes ); +} + +//----------------------------------------------------------------------------- +// XMemSize +// +// XBox Memory Allocator Override +//----------------------------------------------------------------------------- +SIZE_T WINAPI XMemSize( PVOID pAddress, DWORD dwAllocAttributes ) +{ + XALLOC_ATTRIBUTES *pAttribs = (XALLOC_ATTRIBUTES *)&dwAllocAttributes; + bool bPhysical = ( pAttribs->dwMemoryType == XALLOC_MEMTYPE_PHYSICAL ); + + if ( !bPhysical && !pAttribs->dwHeapTracksAttributes && pAttribs->dwAllocatorId != eXALLOCAllocatorId_XUI ) + { + switch ( pAttribs->dwAlignment ) + { + case XALLOC_ALIGNMENT_4: + return g_pMemAlloc->GetSize( pAddress ); + default: + return MemAlloc_GetSizeAligned( pAddress ); + } + } + + return XMemSizeDefault( pAddress, dwAllocAttributes ); +} +#endif // _X360 + +#define MAX_LANG_LEN 64 /* max language name length */ +#define MAX_CTRY_LEN 64 /* max country name length */ +#define MAX_MODIFIER_LEN 0 /* max modifier name length - n/a */ +#define MAX_LC_LEN (MAX_LANG_LEN+MAX_CTRY_LEN+MAX_MODIFIER_LEN+3) + +#if _MSC_VER >= 1700 // VS 11 +// Copied from C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\crt\src\mtdll.h +#ifndef _SETLOC_STRUCT_DEFINED +struct _is_ctype_compatible { + unsigned long id; + int is_clike; +}; + +typedef struct setloc_struct { + /* getqloc static variables */ + wchar_t *pchLanguage; + wchar_t *pchCountry; + int iLocState; + int iPrimaryLen; + BOOL bAbbrevLanguage; + BOOL bAbbrevCountry; + UINT _cachecp; + wchar_t _cachein[MAX_LC_LEN]; + wchar_t _cacheout[MAX_LC_LEN]; + /* _setlocale_set_cat (LC_CTYPE) static variable */ + struct _is_ctype_compatible _Loc_c[5]; + wchar_t _cacheLocaleName[LOCALE_NAME_MAX_LENGTH]; +} _setloc_struct, *_psetloc_struct; +#define _SETLOC_STRUCT_DEFINED +#endif /* _SETLOC_STRUCT_DEFINED */ + +_CRTIMP extern unsigned long __cdecl __threadid(void); +#define _threadid (__threadid()) +_CRTIMP extern uintptr_t __cdecl __threadhandle(void); +#define _threadhandle (__threadhandle()) + +/* Structure for each thread's data */ + +struct _tiddata { + unsigned long _tid; /* thread ID */ + + + uintptr_t _thandle; /* thread handle */ + + int _terrno; /* errno value */ + unsigned long _tdoserrno; /* _doserrno value */ + unsigned int _fpds; /* Floating Point data segment */ + unsigned long _holdrand; /* rand() seed value */ + char * _token; /* ptr to strtok() token */ + wchar_t * _wtoken; /* ptr to wcstok() token */ + unsigned char * _mtoken; /* ptr to _mbstok() token */ + + /* following pointers get malloc'd at runtime */ + char * _errmsg; /* ptr to strerror()/_strerror() buff */ + wchar_t * _werrmsg; /* ptr to _wcserror()/__wcserror() buff */ + char * _namebuf0; /* ptr to tmpnam() buffer */ + wchar_t * _wnamebuf0; /* ptr to _wtmpnam() buffer */ + char * _namebuf1; /* ptr to tmpfile() buffer */ + wchar_t * _wnamebuf1; /* ptr to _wtmpfile() buffer */ + char * _asctimebuf; /* ptr to asctime() buffer */ + wchar_t * _wasctimebuf; /* ptr to _wasctime() buffer */ + void * _gmtimebuf; /* ptr to gmtime() structure */ + char * _cvtbuf; /* ptr to ecvt()/fcvt buffer */ + unsigned char _con_ch_buf[MB_LEN_MAX]; + /* ptr to putch() buffer */ + unsigned short _ch_buf_used; /* if the _con_ch_buf is used */ + + /* following fields are needed by _beginthread code */ + void * _initaddr; /* initial user thread address */ + void * _initarg; /* initial user thread argument */ + + /* following three fields are needed to support signal handling and + * runtime errors */ + void * _pxcptacttab; /* ptr to exception-action table */ + void * _tpxcptinfoptrs; /* ptr to exception info pointers */ + int _tfpecode; /* float point exception code */ + +#if _MSC_VER >= 1930 + + __crt_locale_data* ptmbcinfo; + __crt_multibyte_data* ptlocinfo; +#else + + /* pointer to the copy of the multibyte character information used by + * the thread */ + pthreadmbcinfo ptmbcinfo; + + /* pointer to the copy of the locale informaton used by the thead */ + pthreadlocinfo ptlocinfo; +#endif + + int _ownlocale; /* if 1, this thread owns its own locale */ + + /* following field is needed by NLG routines */ + unsigned long _NLG_dwCode; + + /* + * Per-Thread data needed by C++ Exception Handling + */ + void * _terminate; /* terminate() routine */ + void * _unexpected; /* unexpected() routine */ + void * _translator; /* S.E. translator */ + void * _purecall; /* called when pure virtual happens */ + void * _curexception; /* current exception */ + void * _curcontext; /* current exception context */ + int _ProcessingThrow; /* for uncaught_exception */ + void * _curexcspec; /* for handling exceptions thrown from std::unexpected */ +#if defined (_M_X64) || defined (_M_ARM) + void * _pExitContext; + void * _pUnwindContext; + void * _pFrameInfoChain; +#if defined (_WIN64) + unsigned __int64 _ImageBase; + unsigned __int64 _ThrowImageBase; +#else /* defined (_WIN64) */ + unsigned __int32 _ImageBase; + unsigned __int32 _ThrowImageBase; +#endif /* defined (_WIN64) */ + void * _pForeignException; +#elif defined (_M_IX86) + void * _pFrameInfoChain; +#endif /* defined (_M_IX86) */ + _setloc_struct _setloc_data; + + void * _reserved1; /* nothing */ + void * _reserved2; /* nothing */ + void * _reserved3; /* nothing */ +#ifdef _M_IX86 + void * _reserved4; /* nothing */ + void * _reserved5; /* nothing */ +#endif /* _M_IX86 */ + + int _cxxReThrow; /* Set to True if it's a rethrown C++ Exception */ + + unsigned long __initDomain; /* initial domain used by _beginthread[ex] for managed function */ +}; +#else +struct _is_ctype_compatible { + unsigned long id; + int is_clike; +}; +typedef struct setloc_struct { + /* getqloc static variables */ + char *pchLanguage; + char *pchCountry; + int iLcidState; + int iPrimaryLen; + BOOL bAbbrevLanguage; + BOOL bAbbrevCountry; + LCID lcidLanguage; + LCID lcidCountry; + /* expand_locale static variables */ + LC_ID _cacheid; + UINT _cachecp; + char _cachein[MAX_LC_LEN]; + char _cacheout[MAX_LC_LEN]; + /* _setlocale_set_cat (LC_CTYPE) static variable */ + struct _is_ctype_compatible _Lcid_c[5]; +} _setloc_struct, *_psetloc_struct; + +struct _tiddata { + unsigned long _tid; /* thread ID */ + + + uintptr_t _thandle; /* thread handle */ + + int _terrno; /* errno value */ + unsigned long _tdoserrno; /* _doserrno value */ + unsigned int _fpds; /* Floating Point data segment */ + unsigned long _holdrand; /* rand() seed value */ + char * _token; /* ptr to strtok() token */ + wchar_t * _wtoken; /* ptr to wcstok() token */ + unsigned char * _mtoken; /* ptr to _mbstok() token */ + + /* following pointers get malloc'd at runtime */ + char * _errmsg; /* ptr to strerror()/_strerror() buff */ + wchar_t * _werrmsg; /* ptr to _wcserror()/__wcserror() buff */ + char * _namebuf0; /* ptr to tmpnam() buffer */ + wchar_t * _wnamebuf0; /* ptr to _wtmpnam() buffer */ + char * _namebuf1; /* ptr to tmpfile() buffer */ + wchar_t * _wnamebuf1; /* ptr to _wtmpfile() buffer */ + char * _asctimebuf; /* ptr to asctime() buffer */ + wchar_t * _wasctimebuf; /* ptr to _wasctime() buffer */ + void * _gmtimebuf; /* ptr to gmtime() structure */ + char * _cvtbuf; /* ptr to ecvt()/fcvt buffer */ + unsigned char _con_ch_buf[MB_LEN_MAX]; + /* ptr to putch() buffer */ + unsigned short _ch_buf_used; /* if the _con_ch_buf is used */ + + /* following fields are needed by _beginthread code */ + void * _initaddr; /* initial user thread address */ + void * _initarg; /* initial user thread argument */ + + /* following three fields are needed to support signal handling and + * runtime errors */ + void * _pxcptacttab; /* ptr to exception-action table */ + void * _tpxcptinfoptrs; /* ptr to exception info pointers */ + int _tfpecode; /* float point exception code */ + + /* pointer to the copy of the multibyte character information used by + * the thread */ + pthreadmbcinfo ptmbcinfo; + + /* pointer to the copy of the locale informaton used by the thead */ + pthreadlocinfo ptlocinfo; + int _ownlocale; /* if 1, this thread owns its own locale */ + + /* following field is needed by NLG routines */ + unsigned long _NLG_dwCode; + + /* + * Per-Thread data needed by C++ Exception Handling + */ + void * _terminate; /* terminate() routine */ + void * _unexpected; /* unexpected() routine */ + void * _translator; /* S.E. translator */ + void * _purecall; /* called when pure virtual happens */ + void * _curexception; /* current exception */ + void * _curcontext; /* current exception context */ + int _ProcessingThrow; /* for uncaught_exception */ + void * _curexcspec; /* for handling exceptions thrown from std::unexpected */ +#if defined (_M_IA64) || defined (_M_AMD64) + void * _pExitContext; + void * _pUnwindContext; + void * _pFrameInfoChain; + unsigned __int64 _ImageBase; +#if defined (_M_IA64) + unsigned __int64 _TargetGp; +#endif /* defined (_M_IA64) */ + unsigned __int64 _ThrowImageBase; + void * _pForeignException; +#elif defined (_M_IX86) + void * _pFrameInfoChain; +#endif /* defined (_M_IX86) */ + _setloc_struct _setloc_data; + + void * _encode_ptr; /* EncodePointer() routine */ + void * _decode_ptr; /* DecodePointer() routine */ + + void * _reserved1; /* nothing */ + void * _reserved2; /* nothing */ + void * _reserved3; /* nothing */ + + int _cxxReThrow; /* Set to True if it's a rethrown C++ Exception */ + + unsigned long __initDomain; /* initial domain used by _beginthread[ex] for managed function */ +}; +#endif + +typedef struct _tiddata * _ptiddata; + +class _LocaleUpdate +{ +#if _MSC_VER >= 1930 + __crt_locale_pointers localeinfo; +#else + _locale_tstruct localeinfo; +#endif + _ptiddata ptd; + bool updated; + public: + _LocaleUpdate(_locale_t plocinfo) + : updated(false) + { + /* + if (plocinfo == NULL) + { + ptd = _getptd(); + localeinfo.locinfo = ptd->ptlocinfo; + localeinfo.mbcinfo = ptd->ptmbcinfo; + + __UPDATE_LOCALE(ptd, localeinfo.locinfo); + __UPDATE_MBCP(ptd, localeinfo.mbcinfo); + if (!(ptd->_ownlocale & _PER_THREAD_LOCALE_BIT)) + { + ptd->_ownlocale |= _PER_THREAD_LOCALE_BIT; + updated = true; + } + } + else + { + localeinfo=*plocinfo; + } + */ + } + ~_LocaleUpdate() + { +// if (updated) +// ptd->_ownlocale = ptd->_ownlocale & ~_PER_THREAD_LOCALE_BIT; + } + _locale_t GetLocaleT() + { + return &localeinfo; + } +}; + + +#pragma warning(push) +#pragma warning(disable: 4483) +#if _MSC_FULL_VER >= 140050415 +#define _NATIVE_STARTUP_NAMESPACE __identifier("") +#else /* _MSC_FULL_VER >= 140050415 */ +#define _NATIVE_STARTUP_NAMESPACE __CrtImplementationDetails +#endif /* _MSC_FULL_VER >= 140050415 */ + +namespace _NATIVE_STARTUP_NAMESPACE +{ + class NativeDll + { + private: + static const unsigned int ProcessDetach = 0; + static const unsigned int ProcessAttach = 1; + static const unsigned int ThreadAttach = 2; + static const unsigned int ThreadDetach = 3; + static const unsigned int ProcessVerifier = 4; + + public: + + inline static bool IsInDllMain() + { + return false; + } + + inline static bool IsInProcessAttach() + { + return false; + } + + inline static bool IsInProcessDetach() + { + return false; + } + + inline static bool IsInVcclrit() + { + return false; + } + + inline static bool IsSafeForManagedCode() + { + if (!IsInDllMain()) + { + return true; + } + + if (IsInVcclrit()) + { + return true; + } + + return !IsInProcessAttach() && !IsInProcessDetach(); + } + }; +} +#pragma warning(pop) + +#endif // _MSC_VER >= 1400 + +#endif // !STEAM && !NO_MALLOC_OVERRIDE + +#endif // _WIN32 From e759314383405e247e431eab2270408c8a32eaea Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Thu, 11 Dec 2025 17:06:49 +0800 Subject: [PATCH 58/73] Disable debug build for Windows due to unhandled error LNK2005 with VS2022. --- .github/workflows/build.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6bd7a05..c31a650 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,14 +17,17 @@ jobs: - os: windows-latest os_short: win compiler_cc: msvc + dbgopt: - os: ubuntu-latest os_short: linux compiler_cc: clang compiler_cxx: clang++ + dbgopt: --enable-debug - os: ubuntu-22.04 os_short: oldlinux compiler_cc: clang-14 compiler_cxx: clang++-14 + dbgopt: --enable-debug fail-fast: false name: Build Project ${{ matrix.os_short }} @@ -125,7 +128,7 @@ jobs: --mms-path="${{ github.workspace }}/alliedmodders/mmsource-1.10" \ --sdks=${{ join(fromJSON(env.SDKS)) }} \ --enable-optimize \ - --enable-debug + ${{ matrix.dbgopt }} ambuild - name: Copy to addons directory From 888611b17623f8686e2d141cf80fd190d07d2d18 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Thu, 11 Dec 2025 20:13:03 +0800 Subject: [PATCH 59/73] Reconcile folder structure --- extension/AMBuildScript => AMBuildScript | 906 +++++++++--------- extension/PackageScript => PackageScript | 96 +- extension/configure.py => configure.py | 0 .../gamedata => gamedata}/sendproxy.txt | 190 ++-- .../include/sendproxy.inc | 246 ++--- 5 files changed, 719 insertions(+), 719 deletions(-) rename extension/AMBuildScript => AMBuildScript (97%) rename extension/PackageScript => PackageScript (83%) rename extension/configure.py => configure.py (100%) rename {addons/sourcemod/gamedata => gamedata}/sendproxy.txt (96%) rename {addons/sourcemod/scripting => scripting}/include/sendproxy.inc (97%) diff --git a/extension/AMBuildScript b/AMBuildScript similarity index 97% rename from extension/AMBuildScript rename to AMBuildScript index 4648e0b..5da59cc 100644 --- a/extension/AMBuildScript +++ b/AMBuildScript @@ -1,453 +1,453 @@ -# vim: set sts=2 ts=8 sw=2 tw=99 et ft=python: -import os, sys - -# Simple extensions do not need to modify this file. - -class SDK(object): - def __init__(self, sdk, ext, aDef, name, platform, dir): - self.folder = 'hl2sdk-' + dir - self.envvar = sdk - self.ext = ext - self.code = aDef - self.define = name - self.platform = platform - self.name = dir - self.path = None # Actual path - -WinOnly = ['windows'] -WinLinux = ['windows', 'linux'] -WinLinuxMac = ['windows', 'linux', 'mac'] - -PossibleSDKs = { - 'episode1': SDK('HL2SDK', '1.ep1', '1', 'EPISODEONE', WinLinux, 'episode1'), - 'ep2': SDK('HL2SDKOB', '2.ep2', '3', 'ORANGEBOX', WinLinux, 'orangebox'), - 'css': SDK('HL2SDKCSS', '2.css', '6', 'CSS', WinLinuxMac, 'css'), - 'hl2dm': SDK('HL2SDKHL2DM', '2.hl2dm', '7', 'HL2DM', WinLinuxMac, 'hl2dm'), - 'dods': SDK('HL2SDKDODS', '2.dods', '8', 'DODS', WinLinuxMac, 'dods'), - 'sdk2013': SDK('HL2SDK2013', '2.sdk2013', '9', 'SDK2013', WinLinuxMac, 'sdk2013'), - 'tf2': SDK('HL2SDKTF2', '2.tf2', '11', 'TF2', WinLinuxMac, 'tf2'), - 'l4d': SDK('HL2SDKL4D', '2.l4d', '12', 'LEFT4DEAD', WinLinuxMac, 'l4d'), - 'nucleardawn': SDK('HL2SDKND', '2.nd', '13', 'NUCLEARDAWN', WinLinuxMac, 'nucleardawn'), - 'l4d2': SDK('HL2SDKL4D2', '2.l4d2', '15', 'LEFT4DEAD2', WinLinuxMac, 'l4d2'), - 'darkm': SDK('HL2SDK-DARKM', '2.darkm', '2', 'DARKMESSIAH', WinOnly, 'darkm'), - 'swarm': SDK('HL2SDK-SWARM', '2.swarm', '16', 'ALIENSWARM', WinOnly, 'swarm'), - 'bgt': SDK('HL2SDK-BGT', '2.bgt', '4', 'BLOODYGOODTIME', WinOnly, 'bgt'), - 'eye': SDK('HL2SDK-EYE', '2.eye', '5', 'EYE', WinOnly, 'eye'), - 'csgo': SDK('HL2SDKCSGO', '2.csgo', '21', 'CSGO', WinLinuxMac, 'csgo'), - 'portal2': SDK('HL2SDKPORTAL2', '2.portal2', '17', 'PORTAL2', [], 'portal2'), - 'blade': SDK('HL2SDKBLADE', '2.blade', '18', 'BLADE', WinLinux, 'blade'), - 'insurgency': SDK('HL2SDKINSURGENCY', '2.insurgency', '19', 'INSURGENCY', WinLinuxMac, 'insurgency'), - 'contagion': SDK('HL2SDKCONTAGION', '2.contagion', '14', 'CONTAGION', WinOnly, 'contagion'), - 'bms': SDK('HL2SDKBMS', '2.bms', '10', 'BMS', WinLinux, 'bms'), - 'doi': SDK('HL2SDKDOI', '2.doi', '20', 'DOI', WinLinuxMac, 'doi'), -} - -def ResolveEnvPath(env, folder): - if env in os.environ: - path = os.environ[env] - if os.path.isdir(path): - return path - return None - - head = os.getcwd() - oldhead = None - while head != None and head != oldhead: - path = os.path.join(head, folder) - if os.path.isdir(path): - return path - oldhead = head - head, tail = os.path.split(head) - - return None - -def Normalize(path): - return os.path.abspath(os.path.normpath(path)) - -class ExtensionConfig(object): - def __init__(self): - self.sdks = {} - self.binaries = [] - self.extensions = [] - self.generated_headers = None - self.mms_root = None - self.sm_root = None - - @property - def tag(self): - if builder.options.debug == '1': - return 'Debug' - return 'Release' - - def detectSDKs(self): - sdk_list = builder.options.sdks.split(',') - use_all = sdk_list[0] == 'all' - use_present = sdk_list[0] == 'present' - - for sdk_name in PossibleSDKs: - sdk = PossibleSDKs[sdk_name] - if builder.target_platform in sdk.platform: - if builder.options.hl2sdk_root: - sdk_path = os.path.join(builder.options.hl2sdk_root, sdk.folder) - else: - sdk_path = ResolveEnvPath(sdk.envvar, sdk.folder) - if sdk_path is None or not os.path.isdir(sdk_path): - if use_all or sdk_name in sdk_list: - raise Exception('Could not find a valid path for {0}'.format(sdk.envvar)) - continue - if use_all or use_present or sdk_name in sdk_list: - sdk.path = Normalize(sdk_path) - self.sdks[sdk_name] = sdk - - if len(self.sdks) < 1: - raise Exception('At least one SDK must be available.') - - if builder.options.sm_path: - self.sm_root = builder.options.sm_path - else: - self.sm_root = ResolveEnvPath('SOURCEMOD18', 'sourcemod-1.8') - if not self.sm_root: - self.sm_root = ResolveEnvPath('SOURCEMOD', 'sourcemod') - if not self.sm_root: - self.sm_root = ResolveEnvPath('SOURCEMOD_DEV', 'sourcemod-central') - - if not self.sm_root or not os.path.isdir(self.sm_root): - raise Exception('Could not find a source copy of SourceMod') - self.sm_root = Normalize(self.sm_root) - - if builder.options.mms_path: - self.mms_root = builder.options.mms_path - else: - self.mms_root = ResolveEnvPath('MMSOURCE110', 'mmsource-1.10') - if not self.mms_root: - self.mms_root = ResolveEnvPath('MMSOURCE', 'metamod-source') - if not self.mms_root: - self.mms_root = ResolveEnvPath('MMSOURCE_DEV', 'mmsource-central') - - if not self.mms_root or not os.path.isdir(self.mms_root): - raise Exception('Could not find a source copy of Metamod:Source') - self.mms_root = Normalize(self.mms_root) - - def configure(self): - cxx = builder.DetectCompilers() - - if cxx.like('gcc'): - self.configure_gcc(cxx) - elif cxx.vendor == 'msvc': - self.configure_msvc(cxx) - - # Optimization - if builder.options.opt == '1': - cxx.defines += ['NDEBUG'] - - # Debugging - if builder.options.debug == '1': - cxx.defines += ['DEBUG', '_DEBUG'] - - # Platform-specifics - if builder.target_platform == 'linux': - self.configure_linux(cxx) - elif builder.target_platform == 'mac': - self.configure_mac(cxx) - elif builder.target_platform == 'windows': - self.configure_windows(cxx) - - # Finish up. - cxx.includes += [ - os.path.join(self.sm_root, 'public'), - ] - - def configure_gcc(self, cxx): - cxx.defines += [ - 'stricmp=strcasecmp', - '_stricmp=strcasecmp', - '_snprintf=snprintf', - '_vsnprintf=vsnprintf', - 'HAVE_STDINT_H', - 'GNUC', - ] - cxx.cflags += [ - '-pipe', - '-fno-strict-aliasing', - '-Wall', - #'-Werror', - '-Wno-unused', - '-Wno-switch', - '-Wno-array-bounds', - '-msse', - '-m32', - '-fvisibility=hidden', - '-Wno-implicit-int-float-conversion', - '-fno-omit-frame-pointer', - ] - cxx.cxxflags += [ - '-fno-exceptions', - '-fno-threadsafe-statics', - '-Wno-non-virtual-dtor', - '-Wno-overloaded-virtual', - '-fvisibility-inlines-hidden', - ] - cxx.linkflags += ['-m32'] - - if cxx.version >= 'clang-5': - cxx.cxxflags += ['-Wno-register','-std=c++17'] - else: - cxx.cxxflags += ['-std=c++17'] - - have_gcc = cxx.vendor == 'gcc' - have_clang = cxx.vendor == 'clang' - if cxx.version >= 'clang-3.6': - cxx.cxxflags += ['-Wno-inconsistent-missing-override'] - if have_clang or (cxx.version >= 'gcc-4.6'): - cxx.cflags += ['-Wno-narrowing'] - if have_clang or (cxx.version >= 'gcc-4.7'): - cxx.cxxflags += ['-Wno-delete-non-virtual-dtor'] - if cxx.version >= 'gcc-4.8': - cxx.cflags += ['-Wno-unused-result'] - - if have_clang: - cxx.cxxflags += ['-Wno-implicit-exception-spec-mismatch'] - if cxx.version >= 'apple-clang-5.1' or cxx.version >= 'clang-3.4': - cxx.cxxflags += ['-Wno-deprecated-register'] - else: - cxx.cxxflags += ['-Wno-deprecated'] - cxx.cflags += ['-Wno-sometimes-uninitialized'] - - if have_gcc: - cxx.cflags += ['-mfpmath=sse'] - - if builder.options.opt == '1': - cxx.cflags += ['-O3'] - - def configure_msvc(self, cxx): - if builder.options.debug == '1': - cxx.cflags += ['/MTd'] - cxx.linkflags += ['/NODEFAULTLIB:libcmt'] - else: - cxx.cflags += ['/MT'] - cxx.defines += [ - '_CRT_SECURE_NO_DEPRECATE', - '_CRT_SECURE_NO_WARNINGS', - '_CRT_NONSTDC_NO_DEPRECATE', - '_ITERATOR_DEBUG_LEVEL=0', - ] - cxx.cflags += [ - '/W3', - ] - cxx.cxxflags += [ - '/EHsc', - '/GR-', - '/TP', - '/std:c++17', - ] - cxx.linkflags += [ - 'kernel32.lib', - 'user32.lib', - 'gdi32.lib', - 'winspool.lib', - 'comdlg32.lib', - 'advapi32.lib', - 'shell32.lib', - 'ole32.lib', - 'oleaut32.lib', - 'uuid.lib', - 'odbc32.lib', - 'odbccp32.lib', - ] - - if builder.options.opt == '1': - cxx.cflags += ['/Ox', '/Zo'] - cxx.linkflags += ['/OPT:ICF', '/OPT:REF'] - - if builder.options.debug == '1': - cxx.cflags += ['/Od', '/RTC1'] - - # This needs to be after our optimization flags which could otherwise disable it. - # Don't omit the frame pointer. - cxx.cflags += ['/Oy-'] - - def configure_linux(self, cxx): - cxx.defines += ['LINUX', '_LINUX', 'POSIX', '_FILE_OFFSET_BITS=64'] - cxx.linkflags += ['-lm'] - if cxx.vendor == 'gcc': - cxx.linkflags += ['-static-libgcc'] - elif cxx.vendor == 'clang': - cxx.linkflags += ['-lgcc_eh'] - cxx.linkflags += ['-static-libstdc++'] - - def configure_mac(self, cxx): - cxx.defines += ['OSX', '_OSX', 'POSIX'] - cxx.cflags += ['-mmacosx-version-min=10.5'] - cxx.linkflags += [ - '-mmacosx-version-min=10.5', - '-arch', 'i386', - '-lstdc++', - '-stdlib=libstdc++', - ] - cxx.cxxflags += ['-stdlib=libstdc++'] - - def configure_windows(self, cxx): - cxx.defines += ['WIN32', '_WINDOWS'] - - def ConfigureForExtension(self, context, compiler): - compiler.cxxincludes += [ - os.path.join(context.currentSourcePath), - os.path.join(context.currentSourcePath, 'sdk'), - os.path.join(self.sm_root, 'public'), - os.path.join(self.sm_root, 'public', 'extensions'), - os.path.join(self.sm_root, 'sourcepawn', 'include'), - os.path.join(self.sm_root, 'public', 'amtl', 'amtl'), - os.path.join(self.sm_root, 'public', 'amtl'), - ] - return compiler - - def ConfigureForHL2(self, binary, sdk): - compiler = binary.compiler - - if sdk.name == 'episode1': - mms_path = os.path.join(self.mms_root, 'core-legacy') - else: - mms_path = os.path.join(self.mms_root, 'core') - - compiler.cxxincludes += [ - os.path.join(mms_path), - os.path.join(mms_path, 'sourcehook'), - ] - - defines = ['SE_' + PossibleSDKs[i].define + '=' + PossibleSDKs[i].code for i in PossibleSDKs] - compiler.defines += defines - - paths = [ - ['public'], - ['public', 'engine'], - ['public', 'mathlib'], - ['public', 'vstdlib'], - ['public', 'tier0'], - ['public', 'tier1'] - ] - if sdk.name == 'episode1' or sdk.name == 'darkm': - paths.append(['public', 'dlls']) - paths.append(['game_shared']) - else: - paths.append(['public', 'game', 'server']) - paths.append(['public', 'toolframework']) - paths.append(['game', 'shared']) - paths.append(['common']) - - compiler.defines += ['SOURCE_ENGINE=' + sdk.code] - - if sdk.name in ['sdk2013', 'bms'] and compiler.like('gcc'): - # The 2013 SDK already has these in public/tier0/basetypes.h - compiler.defines.remove('stricmp=strcasecmp') - compiler.defines.remove('_stricmp=strcasecmp') - compiler.defines.remove('_snprintf=snprintf') - compiler.defines.remove('_vsnprintf=vsnprintf') - - if compiler.like('msvc'): - if compiler.version >= 1900: - compiler.linkflags += ['legacy_stdio_definitions.lib'] - compiler.defines += ['COMPILER_MSVC', 'COMPILER_MSVC32'] - else: - compiler.defines += ['COMPILER_GCC'] - - # For everything after Swarm, this needs to be defined for entity networking - # to work properly with sendprop value changes. - if sdk.name in ['blade', 'insurgency', 'doi', 'csgo']: - compiler.defines += ['NETWORK_VARS_ENABLED'] - - if sdk.name in ['css', 'hl2dm', 'dods', 'sdk2013', 'bms', 'tf2', 'l4d', 'nucleardawn', 'l4d2']: - if builder.target_platform in ['linux', 'mac']: - compiler.defines += ['NO_HOOK_MALLOC', 'NO_MALLOC_OVERRIDE'] - - if sdk.name == 'csgo' and builder.target_platform == 'linux': - compiler.linkflags += ['-lstdc++'] - - for path in paths: - compiler.cxxincludes += [os.path.join(sdk.path, *path)] - - if builder.target_platform == 'linux': - if sdk.name == 'episode1': - lib_folder = os.path.join(sdk.path, 'linux_sdk') - elif sdk.name in ['sdk2013', 'bms']: - lib_folder = os.path.join(sdk.path, 'lib', 'public', 'linux32') - else: - lib_folder = os.path.join(sdk.path, 'lib', 'linux') - elif builder.target_platform == 'mac': - if sdk.name in ['sdk2013', 'bms']: - lib_folder = os.path.join(sdk.path, 'lib', 'public', 'osx32') - else: - lib_folder = os.path.join(sdk.path, 'lib', 'mac') - - if builder.target_platform in ['linux', 'mac']: - if sdk.name in ['sdk2013', 'bms']: - compiler.postlink += [ - compiler.Dep(os.path.join(lib_folder, 'tier1.a')), - compiler.Dep(os.path.join(lib_folder, 'mathlib.a')) - ] - else: - compiler.postlink += [ - compiler.Dep(os.path.join(lib_folder, 'tier1_i486.a')), - compiler.Dep(os.path.join(lib_folder, 'mathlib_i486.a')) - ] - - if sdk.name in ['blade', 'insurgency', 'doi', 'csgo']: - compiler.postlink += [compiler.Dep(os.path.join(lib_folder, 'interfaces_i486.a'))] - - dynamic_libs = [] - if builder.target_platform == 'linux': - if sdk.name in ['css', 'hl2dm', 'dods', 'tf2', 'sdk2013', 'bms', 'nucleardawn', 'l4d2', 'insurgency', 'doi']: - dynamic_libs = ['libtier0_srv.so', 'libvstdlib_srv.so'] - elif sdk.name in ['l4d', 'blade', 'insurgency', 'doi', 'csgo']: - dynamic_libs = ['libtier0.so', 'libvstdlib.so'] - else: - dynamic_libs = ['tier0_i486.so', 'vstdlib_i486.so'] - elif builder.target_platform == 'mac': - compiler.linkflags.append('-liconv') - dynamic_libs = ['libtier0.dylib', 'libvstdlib.dylib'] - elif builder.target_platform == 'windows': - libs = ['tier0', 'tier1', 'vstdlib', 'mathlib'] - if sdk.name in ['swarm', 'blade', 'insurgency', 'doi', 'csgo']: - libs.append('interfaces') - for lib in libs: - lib_path = os.path.join(sdk.path, 'lib', 'public', lib) + '.lib' - compiler.linkflags.append(compiler.Dep(lib_path)) - - for library in dynamic_libs: - source_path = os.path.join(lib_folder, library) - output_path = os.path.join(binary.localFolder, library) - - def make_linker(source_path, output_path): - def link(context, binary): - cmd_node, (output,) = context.AddSymlink(source_path, output_path) - return output - return link - - linker = make_linker(source_path, output_path) - compiler.linkflags[0:0] = [compiler.Dep(library, linker)] - - return binary - - def HL2Library(self, context, name, sdk): - binary = context.compiler.Library(name) - self.ConfigureForExtension(context, binary.compiler) - return self.ConfigureForHL2(binary, sdk) - - def HL2Project(self, context, name): - project = context.compiler.LibraryProject(name) - self.ConfigureForExtension(context, project.compiler) - return project - - def HL2Config(self, project, name, sdk): - binary = project.Configure(name, '{0} - {1}'.format(self.tag, sdk.name)) - return self.ConfigureForHL2(binary, sdk) - -Extension = ExtensionConfig() -Extension.detectSDKs() -Extension.configure() - -# Add additional buildscripts here -BuildScripts = [ - 'AMBuilder', - 'PackageScript', -] - -builder.RunBuildScripts(BuildScripts, { 'Extension': Extension}) +# vim: set sts=2 ts=8 sw=2 tw=99 et ft=python: +import os, sys + +# Simple extensions do not need to modify this file. + +class SDK(object): + def __init__(self, sdk, ext, aDef, name, platform, dir): + self.folder = 'hl2sdk-' + dir + self.envvar = sdk + self.ext = ext + self.code = aDef + self.define = name + self.platform = platform + self.name = dir + self.path = None # Actual path + +WinOnly = ['windows'] +WinLinux = ['windows', 'linux'] +WinLinuxMac = ['windows', 'linux', 'mac'] + +PossibleSDKs = { + 'episode1': SDK('HL2SDK', '1.ep1', '1', 'EPISODEONE', WinLinux, 'episode1'), + 'ep2': SDK('HL2SDKOB', '2.ep2', '3', 'ORANGEBOX', WinLinux, 'orangebox'), + 'css': SDK('HL2SDKCSS', '2.css', '6', 'CSS', WinLinuxMac, 'css'), + 'hl2dm': SDK('HL2SDKHL2DM', '2.hl2dm', '7', 'HL2DM', WinLinuxMac, 'hl2dm'), + 'dods': SDK('HL2SDKDODS', '2.dods', '8', 'DODS', WinLinuxMac, 'dods'), + 'sdk2013': SDK('HL2SDK2013', '2.sdk2013', '9', 'SDK2013', WinLinuxMac, 'sdk2013'), + 'tf2': SDK('HL2SDKTF2', '2.tf2', '11', 'TF2', WinLinuxMac, 'tf2'), + 'l4d': SDK('HL2SDKL4D', '2.l4d', '12', 'LEFT4DEAD', WinLinuxMac, 'l4d'), + 'nucleardawn': SDK('HL2SDKND', '2.nd', '13', 'NUCLEARDAWN', WinLinuxMac, 'nucleardawn'), + 'l4d2': SDK('HL2SDKL4D2', '2.l4d2', '15', 'LEFT4DEAD2', WinLinuxMac, 'l4d2'), + 'darkm': SDK('HL2SDK-DARKM', '2.darkm', '2', 'DARKMESSIAH', WinOnly, 'darkm'), + 'swarm': SDK('HL2SDK-SWARM', '2.swarm', '16', 'ALIENSWARM', WinOnly, 'swarm'), + 'bgt': SDK('HL2SDK-BGT', '2.bgt', '4', 'BLOODYGOODTIME', WinOnly, 'bgt'), + 'eye': SDK('HL2SDK-EYE', '2.eye', '5', 'EYE', WinOnly, 'eye'), + 'csgo': SDK('HL2SDKCSGO', '2.csgo', '21', 'CSGO', WinLinuxMac, 'csgo'), + 'portal2': SDK('HL2SDKPORTAL2', '2.portal2', '17', 'PORTAL2', [], 'portal2'), + 'blade': SDK('HL2SDKBLADE', '2.blade', '18', 'BLADE', WinLinux, 'blade'), + 'insurgency': SDK('HL2SDKINSURGENCY', '2.insurgency', '19', 'INSURGENCY', WinLinuxMac, 'insurgency'), + 'contagion': SDK('HL2SDKCONTAGION', '2.contagion', '14', 'CONTAGION', WinOnly, 'contagion'), + 'bms': SDK('HL2SDKBMS', '2.bms', '10', 'BMS', WinLinux, 'bms'), + 'doi': SDK('HL2SDKDOI', '2.doi', '20', 'DOI', WinLinuxMac, 'doi'), +} + +def ResolveEnvPath(env, folder): + if env in os.environ: + path = os.environ[env] + if os.path.isdir(path): + return path + return None + + head = os.getcwd() + oldhead = None + while head != None and head != oldhead: + path = os.path.join(head, folder) + if os.path.isdir(path): + return path + oldhead = head + head, tail = os.path.split(head) + + return None + +def Normalize(path): + return os.path.abspath(os.path.normpath(path)) + +class ExtensionConfig(object): + def __init__(self): + self.sdks = {} + self.binaries = [] + self.extensions = [] + self.generated_headers = None + self.mms_root = None + self.sm_root = None + + @property + def tag(self): + if builder.options.debug == '1': + return 'Debug' + return 'Release' + + def detectSDKs(self): + sdk_list = builder.options.sdks.split(',') + use_all = sdk_list[0] == 'all' + use_present = sdk_list[0] == 'present' + + for sdk_name in PossibleSDKs: + sdk = PossibleSDKs[sdk_name] + if builder.target_platform in sdk.platform: + if builder.options.hl2sdk_root: + sdk_path = os.path.join(builder.options.hl2sdk_root, sdk.folder) + else: + sdk_path = ResolveEnvPath(sdk.envvar, sdk.folder) + if sdk_path is None or not os.path.isdir(sdk_path): + if use_all or sdk_name in sdk_list: + raise Exception('Could not find a valid path for {0}'.format(sdk.envvar)) + continue + if use_all or use_present or sdk_name in sdk_list: + sdk.path = Normalize(sdk_path) + self.sdks[sdk_name] = sdk + + if len(self.sdks) < 1: + raise Exception('At least one SDK must be available.') + + if builder.options.sm_path: + self.sm_root = builder.options.sm_path + else: + self.sm_root = ResolveEnvPath('SOURCEMOD18', 'sourcemod-1.8') + if not self.sm_root: + self.sm_root = ResolveEnvPath('SOURCEMOD', 'sourcemod') + if not self.sm_root: + self.sm_root = ResolveEnvPath('SOURCEMOD_DEV', 'sourcemod-central') + + if not self.sm_root or not os.path.isdir(self.sm_root): + raise Exception('Could not find a source copy of SourceMod') + self.sm_root = Normalize(self.sm_root) + + if builder.options.mms_path: + self.mms_root = builder.options.mms_path + else: + self.mms_root = ResolveEnvPath('MMSOURCE110', 'mmsource-1.10') + if not self.mms_root: + self.mms_root = ResolveEnvPath('MMSOURCE', 'metamod-source') + if not self.mms_root: + self.mms_root = ResolveEnvPath('MMSOURCE_DEV', 'mmsource-central') + + if not self.mms_root or not os.path.isdir(self.mms_root): + raise Exception('Could not find a source copy of Metamod:Source') + self.mms_root = Normalize(self.mms_root) + + def configure(self): + cxx = builder.DetectCompilers() + + if cxx.like('gcc'): + self.configure_gcc(cxx) + elif cxx.vendor == 'msvc': + self.configure_msvc(cxx) + + # Optimization + if builder.options.opt == '1': + cxx.defines += ['NDEBUG'] + + # Debugging + if builder.options.debug == '1': + cxx.defines += ['DEBUG', '_DEBUG'] + + # Platform-specifics + if builder.target_platform == 'linux': + self.configure_linux(cxx) + elif builder.target_platform == 'mac': + self.configure_mac(cxx) + elif builder.target_platform == 'windows': + self.configure_windows(cxx) + + # Finish up. + cxx.includes += [ + os.path.join(self.sm_root, 'public'), + ] + + def configure_gcc(self, cxx): + cxx.defines += [ + 'stricmp=strcasecmp', + '_stricmp=strcasecmp', + '_snprintf=snprintf', + '_vsnprintf=vsnprintf', + 'HAVE_STDINT_H', + 'GNUC', + ] + cxx.cflags += [ + '-pipe', + '-fno-strict-aliasing', + '-Wall', + #'-Werror', + '-Wno-unused', + '-Wno-switch', + '-Wno-array-bounds', + '-msse', + '-m32', + '-fvisibility=hidden', + '-Wno-implicit-int-float-conversion', + '-fno-omit-frame-pointer', + ] + cxx.cxxflags += [ + '-fno-exceptions', + '-fno-threadsafe-statics', + '-Wno-non-virtual-dtor', + '-Wno-overloaded-virtual', + '-fvisibility-inlines-hidden', + ] + cxx.linkflags += ['-m32'] + + if cxx.version >= 'clang-5': + cxx.cxxflags += ['-Wno-register','-std=c++17'] + else: + cxx.cxxflags += ['-std=c++17'] + + have_gcc = cxx.vendor == 'gcc' + have_clang = cxx.vendor == 'clang' + if cxx.version >= 'clang-3.6': + cxx.cxxflags += ['-Wno-inconsistent-missing-override'] + if have_clang or (cxx.version >= 'gcc-4.6'): + cxx.cflags += ['-Wno-narrowing'] + if have_clang or (cxx.version >= 'gcc-4.7'): + cxx.cxxflags += ['-Wno-delete-non-virtual-dtor'] + if cxx.version >= 'gcc-4.8': + cxx.cflags += ['-Wno-unused-result'] + + if have_clang: + cxx.cxxflags += ['-Wno-implicit-exception-spec-mismatch'] + if cxx.version >= 'apple-clang-5.1' or cxx.version >= 'clang-3.4': + cxx.cxxflags += ['-Wno-deprecated-register'] + else: + cxx.cxxflags += ['-Wno-deprecated'] + cxx.cflags += ['-Wno-sometimes-uninitialized'] + + if have_gcc: + cxx.cflags += ['-mfpmath=sse'] + + if builder.options.opt == '1': + cxx.cflags += ['-O3'] + + def configure_msvc(self, cxx): + if builder.options.debug == '1': + cxx.cflags += ['/MTd'] + cxx.linkflags += ['/NODEFAULTLIB:libcmt'] + else: + cxx.cflags += ['/MT'] + cxx.defines += [ + '_CRT_SECURE_NO_DEPRECATE', + '_CRT_SECURE_NO_WARNINGS', + '_CRT_NONSTDC_NO_DEPRECATE', + '_ITERATOR_DEBUG_LEVEL=0', + ] + cxx.cflags += [ + '/W3', + ] + cxx.cxxflags += [ + '/EHsc', + '/GR-', + '/TP', + '/std:c++17', + ] + cxx.linkflags += [ + 'kernel32.lib', + 'user32.lib', + 'gdi32.lib', + 'winspool.lib', + 'comdlg32.lib', + 'advapi32.lib', + 'shell32.lib', + 'ole32.lib', + 'oleaut32.lib', + 'uuid.lib', + 'odbc32.lib', + 'odbccp32.lib', + ] + + if builder.options.opt == '1': + cxx.cflags += ['/Ox', '/Zo'] + cxx.linkflags += ['/OPT:ICF', '/OPT:REF'] + + if builder.options.debug == '1': + cxx.cflags += ['/Od', '/RTC1'] + + # This needs to be after our optimization flags which could otherwise disable it. + # Don't omit the frame pointer. + cxx.cflags += ['/Oy-'] + + def configure_linux(self, cxx): + cxx.defines += ['LINUX', '_LINUX', 'POSIX', '_FILE_OFFSET_BITS=64'] + cxx.linkflags += ['-lm'] + if cxx.vendor == 'gcc': + cxx.linkflags += ['-static-libgcc'] + elif cxx.vendor == 'clang': + cxx.linkflags += ['-lgcc_eh'] + cxx.linkflags += ['-static-libstdc++'] + + def configure_mac(self, cxx): + cxx.defines += ['OSX', '_OSX', 'POSIX'] + cxx.cflags += ['-mmacosx-version-min=10.5'] + cxx.linkflags += [ + '-mmacosx-version-min=10.5', + '-arch', 'i386', + '-lstdc++', + '-stdlib=libstdc++', + ] + cxx.cxxflags += ['-stdlib=libstdc++'] + + def configure_windows(self, cxx): + cxx.defines += ['WIN32', '_WINDOWS'] + + def ConfigureForExtension(self, context, compiler): + compiler.cxxincludes += [ + os.path.join(context.currentSourcePath), + os.path.join(context.currentSourcePath, 'sdk'), + os.path.join(self.sm_root, 'public'), + os.path.join(self.sm_root, 'public', 'extensions'), + os.path.join(self.sm_root, 'sourcepawn', 'include'), + os.path.join(self.sm_root, 'public', 'amtl', 'amtl'), + os.path.join(self.sm_root, 'public', 'amtl'), + ] + return compiler + + def ConfigureForHL2(self, binary, sdk): + compiler = binary.compiler + + if sdk.name == 'episode1': + mms_path = os.path.join(self.mms_root, 'core-legacy') + else: + mms_path = os.path.join(self.mms_root, 'core') + + compiler.cxxincludes += [ + os.path.join(mms_path), + os.path.join(mms_path, 'sourcehook'), + ] + + defines = ['SE_' + PossibleSDKs[i].define + '=' + PossibleSDKs[i].code for i in PossibleSDKs] + compiler.defines += defines + + paths = [ + ['public'], + ['public', 'engine'], + ['public', 'mathlib'], + ['public', 'vstdlib'], + ['public', 'tier0'], + ['public', 'tier1'] + ] + if sdk.name == 'episode1' or sdk.name == 'darkm': + paths.append(['public', 'dlls']) + paths.append(['game_shared']) + else: + paths.append(['public', 'game', 'server']) + paths.append(['public', 'toolframework']) + paths.append(['game', 'shared']) + paths.append(['common']) + + compiler.defines += ['SOURCE_ENGINE=' + sdk.code] + + if sdk.name in ['sdk2013', 'bms'] and compiler.like('gcc'): + # The 2013 SDK already has these in public/tier0/basetypes.h + compiler.defines.remove('stricmp=strcasecmp') + compiler.defines.remove('_stricmp=strcasecmp') + compiler.defines.remove('_snprintf=snprintf') + compiler.defines.remove('_vsnprintf=vsnprintf') + + if compiler.like('msvc'): + if compiler.version >= 1900: + compiler.linkflags += ['legacy_stdio_definitions.lib'] + compiler.defines += ['COMPILER_MSVC', 'COMPILER_MSVC32'] + else: + compiler.defines += ['COMPILER_GCC'] + + # For everything after Swarm, this needs to be defined for entity networking + # to work properly with sendprop value changes. + if sdk.name in ['blade', 'insurgency', 'doi', 'csgo']: + compiler.defines += ['NETWORK_VARS_ENABLED'] + + if sdk.name in ['css', 'hl2dm', 'dods', 'sdk2013', 'bms', 'tf2', 'l4d', 'nucleardawn', 'l4d2']: + if builder.target_platform in ['linux', 'mac']: + compiler.defines += ['NO_HOOK_MALLOC', 'NO_MALLOC_OVERRIDE'] + + if sdk.name == 'csgo' and builder.target_platform == 'linux': + compiler.linkflags += ['-lstdc++'] + + for path in paths: + compiler.cxxincludes += [os.path.join(sdk.path, *path)] + + if builder.target_platform == 'linux': + if sdk.name == 'episode1': + lib_folder = os.path.join(sdk.path, 'linux_sdk') + elif sdk.name in ['sdk2013', 'bms']: + lib_folder = os.path.join(sdk.path, 'lib', 'public', 'linux32') + else: + lib_folder = os.path.join(sdk.path, 'lib', 'linux') + elif builder.target_platform == 'mac': + if sdk.name in ['sdk2013', 'bms']: + lib_folder = os.path.join(sdk.path, 'lib', 'public', 'osx32') + else: + lib_folder = os.path.join(sdk.path, 'lib', 'mac') + + if builder.target_platform in ['linux', 'mac']: + if sdk.name in ['sdk2013', 'bms']: + compiler.postlink += [ + compiler.Dep(os.path.join(lib_folder, 'tier1.a')), + compiler.Dep(os.path.join(lib_folder, 'mathlib.a')) + ] + else: + compiler.postlink += [ + compiler.Dep(os.path.join(lib_folder, 'tier1_i486.a')), + compiler.Dep(os.path.join(lib_folder, 'mathlib_i486.a')) + ] + + if sdk.name in ['blade', 'insurgency', 'doi', 'csgo']: + compiler.postlink += [compiler.Dep(os.path.join(lib_folder, 'interfaces_i486.a'))] + + dynamic_libs = [] + if builder.target_platform == 'linux': + if sdk.name in ['css', 'hl2dm', 'dods', 'tf2', 'sdk2013', 'bms', 'nucleardawn', 'l4d2', 'insurgency', 'doi']: + dynamic_libs = ['libtier0_srv.so', 'libvstdlib_srv.so'] + elif sdk.name in ['l4d', 'blade', 'insurgency', 'doi', 'csgo']: + dynamic_libs = ['libtier0.so', 'libvstdlib.so'] + else: + dynamic_libs = ['tier0_i486.so', 'vstdlib_i486.so'] + elif builder.target_platform == 'mac': + compiler.linkflags.append('-liconv') + dynamic_libs = ['libtier0.dylib', 'libvstdlib.dylib'] + elif builder.target_platform == 'windows': + libs = ['tier0', 'tier1', 'vstdlib', 'mathlib'] + if sdk.name in ['swarm', 'blade', 'insurgency', 'doi', 'csgo']: + libs.append('interfaces') + for lib in libs: + lib_path = os.path.join(sdk.path, 'lib', 'public', lib) + '.lib' + compiler.linkflags.append(compiler.Dep(lib_path)) + + for library in dynamic_libs: + source_path = os.path.join(lib_folder, library) + output_path = os.path.join(binary.localFolder, library) + + def make_linker(source_path, output_path): + def link(context, binary): + cmd_node, (output,) = context.AddSymlink(source_path, output_path) + return output + return link + + linker = make_linker(source_path, output_path) + compiler.linkflags[0:0] = [compiler.Dep(library, linker)] + + return binary + + def HL2Library(self, context, name, sdk): + binary = context.compiler.Library(name) + self.ConfigureForExtension(context, binary.compiler) + return self.ConfigureForHL2(binary, sdk) + + def HL2Project(self, context, name): + project = context.compiler.LibraryProject(name) + self.ConfigureForExtension(context, project.compiler) + return project + + def HL2Config(self, project, name, sdk): + binary = project.Configure(name, '{0} - {1}'.format(self.tag, sdk.name)) + return self.ConfigureForHL2(binary, sdk) + +Extension = ExtensionConfig() +Extension.detectSDKs() +Extension.configure() + +# Add additional buildscripts here +BuildScripts = [ + 'extension/AMBuilder', + 'PackageScript', +] + +builder.RunBuildScripts(BuildScripts, { 'Extension': Extension}) diff --git a/extension/PackageScript b/PackageScript similarity index 83% rename from extension/PackageScript rename to PackageScript index 3b05fb0..29c0140 100644 --- a/extension/PackageScript +++ b/PackageScript @@ -1,49 +1,49 @@ -# vim: set ts=8 sts=2 sw=2 tw=99 et ft=python: -import os - -# This is where the files will be output to -# package is the default -builder.SetBuildFolder('package') - -# Add any folders you need to this list -folder_list = [ - 'addons/sourcemod/extensions', - 'addons/sourcemod/gamedata', - 'addons/sourcemod/scripting/include', -] - -# Create the distribution folder hierarchy. -folder_map = {} -for folder in folder_list: - norm_folder = os.path.normpath(folder) - folder_map[folder] = builder.AddFolder(norm_folder) - -# Do all straight-up file copies from the source tree. -def CopyFiles(src, dest, files): - if not dest: - dest = src - dest_entry = folder_map[dest] - for source_file in files: - source_path = os.path.join(builder.sourcePath, src, source_file) - builder.AddCopy(source_path, dest_entry) - -# GameData files -CopyFiles('gamedata', 'addons/sourcemod/gamedata', - [ '../../addons/sourcemod/gamedata/sendproxy.txt', - ] -) -CopyFiles('include', 'addons/sourcemod/scripting/include', - [ '../../addons/sourcemod/scripting/include/sendproxy.inc', - ] -) - -# Config Files -#CopyFiles('configs', 'addons/sourcemod/configs', -# [ 'configfile.cfg', -# 'otherconfig.cfg, -# ] -#) - -# Copy binaries. -for cxx_task in Extension.extensions: +# vim: set ts=8 sts=2 sw=2 tw=99 et ft=python: +import os + +# This is where the files will be output to +# package is the default +builder.SetBuildFolder('package') + +# Add any folders you need to this list +folder_list = [ + 'addons/sourcemod/extensions', + 'addons/sourcemod/gamedata', + 'addons/sourcemod/scripting/include', +] + +# Create the distribution folder hierarchy. +folder_map = {} +for folder in folder_list: + norm_folder = os.path.normpath(folder) + folder_map[folder] = builder.AddFolder(norm_folder) + +# Do all straight-up file copies from the source tree. +def CopyFiles(src, dest, files): + if not dest: + dest = src + dest_entry = folder_map[dest] + for source_file in files: + source_path = os.path.join(builder.sourcePath, src, source_file) + builder.AddCopy(source_path, dest_entry) + +# GameData files +CopyFiles('gamedata', 'addons/sourcemod/gamedata', + [ 'sendproxy.txt', + ] +) +CopyFiles('scripting', 'addons/sourcemod/scripting/include', + [ 'include/sendproxy.inc', + ] +) + +# Config Files +#CopyFiles('configs', 'addons/sourcemod/configs', +# [ 'configfile.cfg', +# 'otherconfig.cfg, +# ] +#) + +# Copy binaries. +for cxx_task in Extension.extensions: builder.AddCopy(cxx_task.binary, folder_map['addons/sourcemod/extensions']) \ No newline at end of file diff --git a/extension/configure.py b/configure.py similarity index 100% rename from extension/configure.py rename to configure.py diff --git a/addons/sourcemod/gamedata/sendproxy.txt b/gamedata/sendproxy.txt similarity index 96% rename from addons/sourcemod/gamedata/sendproxy.txt rename to gamedata/sendproxy.txt index 9f17b2c..26b2078 100644 --- a/addons/sourcemod/gamedata/sendproxy.txt +++ b/gamedata/sendproxy.txt @@ -1,95 +1,95 @@ -"Games" -{ - "left4dead2" - { - "Addresses" - { - "framesnapshotmanager" - { - "linux" - { - "signature" "framesnapshotmanager" - } - "windows" - { - "signature" "CFrameSnapshot::ReleaseReference" - "read" "7" - } - "read" "0" - } - } - - "Signatures" - { - "framesnapshotmanager" - { - "library" "engine" - "linux" "@framesnapshotmanager" - } - - "SV_ComputeClientPacks" - { - "library" "engine" - "linux" "@_Z21SV_ComputeClientPacksiPP11CGameClientP14CFrameSnapshot" - "windows" "\x55\x8B\xEC\x83\xEC\x44\xA1\x2A\x2A\x2A\x2A\x53" - // 55 8B EC 83 EC 44 A1 ? ? ? ? 53 - } - - "PackEntities_Normal" - { - "library" "engine" - "linux" "@_Z19PackEntities_NormaliPP11CGameClientP14CFrameSnapshot" - "windows" "\x55\x8B\xEC\xB8\x48\x60\x00\x00" - // 55 8B EC B8 48 60 00 00 - } - - "CFrameSnapshotManager::UsePreviouslySentPacket" - { - "library" "engine" - "linux" "@_ZN21CFrameSnapshotManager23UsePreviouslySentPacketEP14CFrameSnapshotii" - "windows" "\x55\x8B\xEC\x56\x8B\x75\x0C\x57\x8B\xBC\xB1\x9C\x00\x00\x00" - // 55 8B EC 56 8B 75 0C 57 8B BC B1 9C 00 00 00 - } - - "CFrameSnapshotManager::GetPreviouslySentPacket" - { - "library" "engine" - "linux" "@_ZN21CFrameSnapshotManager23GetPreviouslySentPacketEii" - "windows" "\x55\x8B\xEC\x8B\x55\x08\x8B\x84\x91\x9C\x00\x00\x00" - } - - "CFrameSnapshotManager::CreatePackedEntity" - { - "library" "engine" - "linux" "@_ZN21CFrameSnapshotManager18CreatePackedEntityEP14CFrameSnapshoti" - "windows" "\x55\x8B\xEC\x83\xEC\x0C\x53\x8B\xD9\x56" - // 55 8B EC 83 EC 0C 53 8B D9 56 - } - - "CFrameSnapshotManager::CreateEmptySnapshot" - { - "library" "engine" - "linux" "@_ZN21CFrameSnapshotManager19CreateEmptySnapshotEii" - "windows" "\x55\x8B\xEC\x83\xEC\x0C\x53\x56\x57\x89\x4D\xF8" - // 55 8B EC 83 EC 0C 53 56 57 89 4D F8 - // Search string "Sending full update to Client %s (%s)", the called function with arg2 is 2048 - } - - "CFrameSnapshotManager::RemoveEntityReference" - { - "library" "engine" - "linux" "@_ZN21CFrameSnapshotManager21RemoveEntityReferenceEi" - "windows" "\x55\x8B\xEC\x51\x8B\x45\x08\x53\x8B\x18" - // 55 8B EC 51 8B 45 08 53 8B 18 - } - - "CFrameSnapshot::ReleaseReference" - { - "library" "engine" - "linux" "@_ZN14CFrameSnapshot16ReleaseReferenceEv" - "windows" "\x55\x8B\xEC\x51\x56\x8B\x35\x2A\x2A\x2A\x2A\x57" - // 55 8B EC 51 56 8B 35 ? ? ? ? 57 - } - } - } -} +"Games" +{ + "left4dead2" + { + "Addresses" + { + "framesnapshotmanager" + { + "linux" + { + "signature" "framesnapshotmanager" + } + "windows" + { + "signature" "CFrameSnapshot::ReleaseReference" + "read" "7" + } + "read" "0" + } + } + + "Signatures" + { + "framesnapshotmanager" + { + "library" "engine" + "linux" "@framesnapshotmanager" + } + + "SV_ComputeClientPacks" + { + "library" "engine" + "linux" "@_Z21SV_ComputeClientPacksiPP11CGameClientP14CFrameSnapshot" + "windows" "\x55\x8B\xEC\x83\xEC\x44\xA1\x2A\x2A\x2A\x2A\x53" + // 55 8B EC 83 EC 44 A1 ? ? ? ? 53 + } + + "PackEntities_Normal" + { + "library" "engine" + "linux" "@_Z19PackEntities_NormaliPP11CGameClientP14CFrameSnapshot" + "windows" "\x55\x8B\xEC\xB8\x48\x60\x00\x00" + // 55 8B EC B8 48 60 00 00 + } + + "CFrameSnapshotManager::UsePreviouslySentPacket" + { + "library" "engine" + "linux" "@_ZN21CFrameSnapshotManager23UsePreviouslySentPacketEP14CFrameSnapshotii" + "windows" "\x55\x8B\xEC\x56\x8B\x75\x0C\x57\x8B\xBC\xB1\x9C\x00\x00\x00" + // 55 8B EC 56 8B 75 0C 57 8B BC B1 9C 00 00 00 + } + + "CFrameSnapshotManager::GetPreviouslySentPacket" + { + "library" "engine" + "linux" "@_ZN21CFrameSnapshotManager23GetPreviouslySentPacketEii" + "windows" "\x55\x8B\xEC\x8B\x55\x08\x8B\x84\x91\x9C\x00\x00\x00" + } + + "CFrameSnapshotManager::CreatePackedEntity" + { + "library" "engine" + "linux" "@_ZN21CFrameSnapshotManager18CreatePackedEntityEP14CFrameSnapshoti" + "windows" "\x55\x8B\xEC\x83\xEC\x0C\x53\x8B\xD9\x56" + // 55 8B EC 83 EC 0C 53 8B D9 56 + } + + "CFrameSnapshotManager::CreateEmptySnapshot" + { + "library" "engine" + "linux" "@_ZN21CFrameSnapshotManager19CreateEmptySnapshotEii" + "windows" "\x55\x8B\xEC\x83\xEC\x0C\x53\x56\x57\x89\x4D\xF8" + // 55 8B EC 83 EC 0C 53 56 57 89 4D F8 + // Search string "Sending full update to Client %s (%s)", the called function with arg2 is 2048 + } + + "CFrameSnapshotManager::RemoveEntityReference" + { + "library" "engine" + "linux" "@_ZN21CFrameSnapshotManager21RemoveEntityReferenceEi" + "windows" "\x55\x8B\xEC\x51\x8B\x45\x08\x53\x8B\x18" + // 55 8B EC 51 8B 45 08 53 8B 18 + } + + "CFrameSnapshot::ReleaseReference" + { + "library" "engine" + "linux" "@_ZN14CFrameSnapshot16ReleaseReferenceEv" + "windows" "\x55\x8B\xEC\x51\x56\x8B\x35\x2A\x2A\x2A\x2A\x57" + // 55 8B EC 51 56 8B 35 ? ? ? ? 57 + } + } + } +} diff --git a/addons/sourcemod/scripting/include/sendproxy.inc b/scripting/include/sendproxy.inc similarity index 97% rename from addons/sourcemod/scripting/include/sendproxy.inc rename to scripting/include/sendproxy.inc index 24947ba..3cfa126 100644 --- a/addons/sourcemod/scripting/include/sendproxy.inc +++ b/scripting/include/sendproxy.inc @@ -1,124 +1,124 @@ -#if defined _SENDPROXYMANAGER_INC_ - #endinput -#endif -#define _SENDPROXYMANAGER_INC_ - -#define SENDPROXY_LIB "sendproxy2" - -enum SendPropType -{ - Prop_Int, - Prop_Float, - Prop_String, - Prop_Vector, - Prop_EHandle, - Prop_Max -}; - -/** - * Callback for send proxy hooks. - * - * @param entity Index of the hooked entity. - * @param prop Name of the hooked send prop. - * @param value Prop value. - * @param element 0 if the hooked prop is not an array, - * otherwise an index into the array (starting from 0). - * @param client Index of the current processing client. - * - * @return Action Plugin_Changed to override value, otherwise ignored. - */ -typeset SendProxyCallback -{ - function Action (int entity, const char[] prop, int &value, int element, int client); //Prop_Int, Prop_EHandle - function Action (int entity, const char[] prop, float &value, int element, int client); //Prop_Float - function Action (int entity, const char[] prop, char[] value, int maxlength, int element, int client); //Prop_String - function Action (int entity, const char[] prop, float value[3], int element, int client); //Prop_Vector -}; - -/** - * Callback for gamerules send proxy hooks. - * - * @param prop Name of the hooked send prop. - * @param value Prop value. - * @param element 0 if the hooked prop is NOT an array (InsideArray), - * otherwise an index into the array (starting from 0). - * @param client Index of the current processing client. - * - * @return Action Plugin_Changed to override value, ignored otherwise. - */ -typeset SendProxyCallbackGamerules -{ - function Action (const char[] prop, int &value, int element, int client); //Prop_Int, Prop_EHandle - function Action (const char[] prop, float &value, int element, int client); //Prop_Float - function Action (const char[] prop, char[] value, int maxlength, int element, int client); //Prop_String - function Action (const char[] prop, float value[3], int element, int client); //Prop_Vector -}; - -/** - * Hook an entity's prop to override its value in callback without actually changing the prop. - * @note Callback function cannot be checked so make sure it matches the prop type. - * - * @param entity Entity index to hook. - * @param prop Send prop name. - * @param type Prop type. Reports an error if type is mismatched. - * @param callback Callback function. - * @param element Element of the prop. Has no effect if the prop is NOT an array or a table. - * - * @return bool True if success, false if already hooked. - */ -native bool SendProxy_HookEntity(int entity, const char[] prop, SendPropType type, SendProxyCallback callback, int element = 0); -native bool SendProxy_HookGameRules(const char[] prop, SendPropType type, SendProxyCallbackGamerules callback, int element = 0); - -/** - * Unhook an entity's prop. - * - * @param entity Entity index to unhook. - * @param prop Send prop name. - * @param callback Callback function. - * @param element Element of the prop. Has no effect if the prop is NOT an array or a table. - * - * @return bool True if found, false otherwise. - */ -native bool SendProxy_UnhookEntity(int entity, const char[] prop, SendProxyCallback callback, int element = 0); -native bool SendProxy_UnhookGameRules(const char[] prop, SendProxyCallbackGamerules callback, int element = 0); - -/** - * Test if an entity's prop is hooked. - * - * @param entity Entity index to hook. - * @param prop Send prop name. - * @param callback Callback function. - * @param element Element of the prop. Has no effect if the prop is NOT an array or a table. - * - * @return bool True if hooked, false otherwise. - */ -native bool SendProxy_IsHookedEntity(int entity, const char[] prop, SendProxyCallback callback, int element = 0); -native bool SendProxy_IsHookedGameRules(const char[] prop, SendProxyCallbackGamerules callback, int element = 0); - -public __ext_sendproxymanager_SetNTVOptional() -{ -#if !defined REQUIRE_EXTENSIONS - MarkNativeAsOptional("SendProxy_HookEntity"); - MarkNativeAsOptional("SendProxy_HookGameRules"); - MarkNativeAsOptional("SendProxy_UnhookEntity"); - MarkNativeAsOptional("SendProxy_UnhookGameRules"); - MarkNativeAsOptional("SendProxy_IsHookedEntity"); - MarkNativeAsOptional("SendProxy_IsHookedGameRules"); -#endif -} - -public Extension __ext_sendproxymanager = -{ - name = "SendProxy Manager", - file = "sendproxy.ext", -#if defined AUTOLOAD_EXTENSIONS - autoload = 1, -#else - autoload = 0, -#endif -#if defined REQUIRE_EXTENSIONS - required = 1, -#else - required = 0, -#endif +#if defined _SENDPROXYMANAGER_INC_ + #endinput +#endif +#define _SENDPROXYMANAGER_INC_ + +#define SENDPROXY_LIB "sendproxy2" + +enum SendPropType +{ + Prop_Int, + Prop_Float, + Prop_String, + Prop_Vector, + Prop_EHandle, + Prop_Max +}; + +/** + * Callback for send proxy hooks. + * + * @param entity Index of the hooked entity. + * @param prop Name of the hooked send prop. + * @param value Prop value. + * @param element 0 if the hooked prop is not an array, + * otherwise an index into the array (starting from 0). + * @param client Index of the current processing client. + * + * @return Action Plugin_Changed to override value, otherwise ignored. + */ +typeset SendProxyCallback +{ + function Action (int entity, const char[] prop, int &value, int element, int client); //Prop_Int, Prop_EHandle + function Action (int entity, const char[] prop, float &value, int element, int client); //Prop_Float + function Action (int entity, const char[] prop, char[] value, int maxlength, int element, int client); //Prop_String + function Action (int entity, const char[] prop, float value[3], int element, int client); //Prop_Vector +}; + +/** + * Callback for gamerules send proxy hooks. + * + * @param prop Name of the hooked send prop. + * @param value Prop value. + * @param element 0 if the hooked prop is NOT an array (InsideArray), + * otherwise an index into the array (starting from 0). + * @param client Index of the current processing client. + * + * @return Action Plugin_Changed to override value, ignored otherwise. + */ +typeset SendProxyCallbackGamerules +{ + function Action (const char[] prop, int &value, int element, int client); //Prop_Int, Prop_EHandle + function Action (const char[] prop, float &value, int element, int client); //Prop_Float + function Action (const char[] prop, char[] value, int maxlength, int element, int client); //Prop_String + function Action (const char[] prop, float value[3], int element, int client); //Prop_Vector +}; + +/** + * Hook an entity's prop to override its value in callback without actually changing the prop. + * @note Callback function cannot be checked so make sure it matches the prop type. + * + * @param entity Entity index to hook. + * @param prop Send prop name. + * @param type Prop type. Reports an error if type is mismatched. + * @param callback Callback function. + * @param element Element of the prop. Has no effect if the prop is NOT an array or a table. + * + * @return bool True if success, false if already hooked. + */ +native bool SendProxy_HookEntity(int entity, const char[] prop, SendPropType type, SendProxyCallback callback, int element = 0); +native bool SendProxy_HookGameRules(const char[] prop, SendPropType type, SendProxyCallbackGamerules callback, int element = 0); + +/** + * Unhook an entity's prop. + * + * @param entity Entity index to unhook. + * @param prop Send prop name. + * @param callback Callback function. + * @param element Element of the prop. Has no effect if the prop is NOT an array or a table. + * + * @return bool True if found, false otherwise. + */ +native bool SendProxy_UnhookEntity(int entity, const char[] prop, SendProxyCallback callback, int element = 0); +native bool SendProxy_UnhookGameRules(const char[] prop, SendProxyCallbackGamerules callback, int element = 0); + +/** + * Test if an entity's prop is hooked. + * + * @param entity Entity index to hook. + * @param prop Send prop name. + * @param callback Callback function. + * @param element Element of the prop. Has no effect if the prop is NOT an array or a table. + * + * @return bool True if hooked, false otherwise. + */ +native bool SendProxy_IsHookedEntity(int entity, const char[] prop, SendProxyCallback callback, int element = 0); +native bool SendProxy_IsHookedGameRules(const char[] prop, SendProxyCallbackGamerules callback, int element = 0); + +public __ext_sendproxymanager_SetNTVOptional() +{ +#if !defined REQUIRE_EXTENSIONS + MarkNativeAsOptional("SendProxy_HookEntity"); + MarkNativeAsOptional("SendProxy_HookGameRules"); + MarkNativeAsOptional("SendProxy_UnhookEntity"); + MarkNativeAsOptional("SendProxy_UnhookGameRules"); + MarkNativeAsOptional("SendProxy_IsHookedEntity"); + MarkNativeAsOptional("SendProxy_IsHookedGameRules"); +#endif +} + +public Extension __ext_sendproxymanager = +{ + name = "SendProxy Manager", + file = "sendproxy.ext", +#if defined AUTOLOAD_EXTENSIONS + autoload = 1, +#else + autoload = 0, +#endif +#if defined REQUIRE_EXTENSIONS + required = 1, +#else + required = 0, +#endif }; \ No newline at end of file From 98ebc2e04a32a0e61a234d4c1ffb49a2cee98747 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Thu, 11 Dec 2025 20:16:17 +0800 Subject: [PATCH 60/73] Fix source codes being included to package --- .github/workflows/build.yml | 33 +++++++++++++-------------------- .gitignore | 3 ++- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c31a650..fcafaa3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -82,11 +82,12 @@ jobs: run: | python -m pip install --upgrade pip setuptools wheel - - name: Checkout Repository - uses: actions/checkout@v4 - with: - path: src - + - name: Install AMBuild + working-directory: alliedmodders + run: | + git clone https://github.com/alliedmodders/ambuild + pip install ./ambuild + - name: Prepare Alliedmodders Directory shell: bash run: | @@ -109,17 +110,15 @@ jobs: run: | git clone https://github.com/alliedmodders/metamod-source mmsource-1.10 -b 1.10-dev - - name: Install AMBuild - working-directory: alliedmodders - run: | - git clone https://github.com/alliedmodders/ambuild - pip install ./ambuild - + - name: Checkout Repository + uses: actions/checkout@v4 + with: + path: src + - name: Run AMBuild working-directory: src shell: bash run: | - cd extension mkdir build cd build python ../configure.py \ @@ -131,14 +130,8 @@ jobs: ${{ matrix.dbgopt }} ambuild - - name: Copy to addons directory - working-directory: src - shell: bash - run: | - cp -r extension/build/package/addons/sourcemod/extensions addons/sourcemod - - name: Upload Binary (Package) uses: actions/upload-artifact@v4 with: - name: sendproxy-${{ matrix.os_short }} - path: src/addons \ No newline at end of file + name: sendproxy-${{ matrix.os_short }}-${{ env.GITHUB_SHA_SHORT }} + path: src/build/package \ No newline at end of file diff --git a/.gitignore b/.gitignore index 25e7109..6a4ed91 100644 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,5 @@ .vscode/*.* -extension/build/* \ No newline at end of file +extension/build/* +build/* \ No newline at end of file From 06a2ef0607a5b04e560c1c3b8c3ac77b021c40fc Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Fri, 12 Dec 2025 05:10:20 +0800 Subject: [PATCH 61/73] Update AMBuild to 2.2 --- AMBuildScript | 460 ++++++++++++++++++++------------------------ configure.py | 30 +-- extension/AMBuilder | 20 +- 3 files changed, 232 insertions(+), 278 deletions(-) diff --git a/AMBuildScript b/AMBuildScript index 5da59cc..d1f9e15 100644 --- a/AMBuildScript +++ b/AMBuildScript @@ -1,46 +1,5 @@ # vim: set sts=2 ts=8 sw=2 tw=99 et ft=python: -import os, sys - -# Simple extensions do not need to modify this file. - -class SDK(object): - def __init__(self, sdk, ext, aDef, name, platform, dir): - self.folder = 'hl2sdk-' + dir - self.envvar = sdk - self.ext = ext - self.code = aDef - self.define = name - self.platform = platform - self.name = dir - self.path = None # Actual path - -WinOnly = ['windows'] -WinLinux = ['windows', 'linux'] -WinLinuxMac = ['windows', 'linux', 'mac'] - -PossibleSDKs = { - 'episode1': SDK('HL2SDK', '1.ep1', '1', 'EPISODEONE', WinLinux, 'episode1'), - 'ep2': SDK('HL2SDKOB', '2.ep2', '3', 'ORANGEBOX', WinLinux, 'orangebox'), - 'css': SDK('HL2SDKCSS', '2.css', '6', 'CSS', WinLinuxMac, 'css'), - 'hl2dm': SDK('HL2SDKHL2DM', '2.hl2dm', '7', 'HL2DM', WinLinuxMac, 'hl2dm'), - 'dods': SDK('HL2SDKDODS', '2.dods', '8', 'DODS', WinLinuxMac, 'dods'), - 'sdk2013': SDK('HL2SDK2013', '2.sdk2013', '9', 'SDK2013', WinLinuxMac, 'sdk2013'), - 'tf2': SDK('HL2SDKTF2', '2.tf2', '11', 'TF2', WinLinuxMac, 'tf2'), - 'l4d': SDK('HL2SDKL4D', '2.l4d', '12', 'LEFT4DEAD', WinLinuxMac, 'l4d'), - 'nucleardawn': SDK('HL2SDKND', '2.nd', '13', 'NUCLEARDAWN', WinLinuxMac, 'nucleardawn'), - 'l4d2': SDK('HL2SDKL4D2', '2.l4d2', '15', 'LEFT4DEAD2', WinLinuxMac, 'l4d2'), - 'darkm': SDK('HL2SDK-DARKM', '2.darkm', '2', 'DARKMESSIAH', WinOnly, 'darkm'), - 'swarm': SDK('HL2SDK-SWARM', '2.swarm', '16', 'ALIENSWARM', WinOnly, 'swarm'), - 'bgt': SDK('HL2SDK-BGT', '2.bgt', '4', 'BLOODYGOODTIME', WinOnly, 'bgt'), - 'eye': SDK('HL2SDK-EYE', '2.eye', '5', 'EYE', WinOnly, 'eye'), - 'csgo': SDK('HL2SDKCSGO', '2.csgo', '21', 'CSGO', WinLinuxMac, 'csgo'), - 'portal2': SDK('HL2SDKPORTAL2', '2.portal2', '17', 'PORTAL2', [], 'portal2'), - 'blade': SDK('HL2SDKBLADE', '2.blade', '18', 'BLADE', WinLinux, 'blade'), - 'insurgency': SDK('HL2SDKINSURGENCY', '2.insurgency', '19', 'INSURGENCY', WinLinuxMac, 'insurgency'), - 'contagion': SDK('HL2SDKCONTAGION', '2.contagion', '14', 'CONTAGION', WinOnly, 'contagion'), - 'bms': SDK('HL2SDKBMS', '2.bms', '10', 'BMS', WinLinux, 'bms'), - 'doi': SDK('HL2SDKDOI', '2.doi', '20', 'DOI', WinLinuxMac, 'doi'), -} +import os, shutil def ResolveEnvPath(env, folder): if env in os.environ: @@ -62,15 +21,71 @@ def ResolveEnvPath(env, folder): def Normalize(path): return os.path.abspath(os.path.normpath(path)) + +def SetArchFlags(compiler): + if compiler.behavior == 'gcc': + if compiler.target.arch == 'x86_64': + compiler.cflags += ['-fPIC'] + elif compiler.like('msvc'): + if compiler.target.arch == 'x86_64': + compiler.defines += ['WIN64'] + +hl2sdk_manifests_path = None + +# First we check if the manifest exists in the current path +hl2sdk_manifests_path = os.path.join(builder.sourcePath, 'hl2sdk-manifests/SdkHelpers.ambuild') + +if not os.path.exists(hl2sdk_manifests_path): + # manifests does not exists in the project file, use the path from --hl2sdk-manifest-path + if not builder.options.hl2sdk_manifest: + raise Exception('HL2SDK Manifest root path not set! (--hl2sdk-manifest-path)') + else: + hl2sdk_manifests_path = os.path.join(builder.options.hl2sdk_manifest, 'SdkHelpers.ambuild') + + if not os.path.exists(hl2sdk_manifests_path): + raise Exception('Could not find SdkHelpers.ambuild in the given HL2SDK Manifest path!') + + +SdkHelpers = builder.Eval(hl2sdk_manifests_path, { + 'Project': 'sm-extension' +}) class ExtensionConfig(object): def __init__(self): + self.sdk_manifests = [] self.sdks = {} - self.binaries = [] + self.sdk_targets = [] self.extensions = [] self.generated_headers = None self.mms_root = None self.sm_root = None + self.all_targets = [] + self.target_archs = set() + + if builder.options.targets: + target_archs = builder.options.targets.split(',') + else: + target_archs = ['x86', 'x86_64'] + + for arch in target_archs: + try: + cxx = builder.DetectCxx(target_arch = arch) + self.target_archs.add(cxx.target.arch) + except Exception as e: + # Error if archs were manually overridden. + if builder.options.targets: + raise + print('Skipping target {}: {}'.format(arch, e)) + continue + self.all_targets.append(cxx) + + if not self.all_targets: + raise Exception('No suitable C/C++ compiler was found.') + + def use_auto_versioning(self): + if builder.backend != 'amb2': + return False + return not getattr(builder.options, 'disable_auto_versioning', False) @property def tag(self): @@ -78,48 +93,35 @@ class ExtensionConfig(object): return 'Debug' return 'Release' - def detectSDKs(self): - sdk_list = builder.options.sdks.split(',') - use_all = sdk_list[0] == 'all' - use_present = sdk_list[0] == 'present' - - for sdk_name in PossibleSDKs: - sdk = PossibleSDKs[sdk_name] - if builder.target_platform in sdk.platform: - if builder.options.hl2sdk_root: - sdk_path = os.path.join(builder.options.hl2sdk_root, sdk.folder) - else: - sdk_path = ResolveEnvPath(sdk.envvar, sdk.folder) - if sdk_path is None or not os.path.isdir(sdk_path): - if use_all or sdk_name in sdk_list: - raise Exception('Could not find a valid path for {0}'.format(sdk.envvar)) - continue - if use_all or use_present or sdk_name in sdk_list: - sdk.path = Normalize(sdk_path) - self.sdks[sdk_name] = sdk - - if len(self.sdks) < 1: - raise Exception('At least one SDK must be available.') + def findSdkPath(self, sdk_name): + dir_name = 'hl2sdk-{}'.format(sdk_name) + if builder.options.hl2sdk_root: + sdk_path = os.path.join(builder.options.hl2sdk_root, dir_name) + if os.path.exists(sdk_path): + return sdk_path + return ResolveEnvPath('HL2SDK{}'.format(sdk_name.upper()), dir_name) - if builder.options.sm_path: - self.sm_root = builder.options.sm_path - else: - self.sm_root = ResolveEnvPath('SOURCEMOD18', 'sourcemod-1.8') - if not self.sm_root: - self.sm_root = ResolveEnvPath('SOURCEMOD', 'sourcemod') - if not self.sm_root: - self.sm_root = ResolveEnvPath('SOURCEMOD_DEV', 'sourcemod-central') + def shouldIncludeSdk(self, sdk): + return not sdk.get('source2', False) - if not self.sm_root or not os.path.isdir(self.sm_root): - raise Exception('Could not find a source copy of SourceMod') - self.sm_root = Normalize(self.sm_root) + def detectSDKs(self): + sdk_list = [s for s in builder.options.sdks.split(',') if s] + SdkHelpers.sdk_filter = self.shouldIncludeSdk + SdkHelpers.find_sdk_path = self.findSdkPath + SdkHelpers.findSdks(builder, self.all_targets, sdk_list) + + self.sdks = SdkHelpers.sdks + self.sdk_manifests = SdkHelpers.sdk_manifests + self.sdk_targets = SdkHelpers.sdk_targets if builder.options.mms_path: self.mms_root = builder.options.mms_path else: - self.mms_root = ResolveEnvPath('MMSOURCE110', 'mmsource-1.10') + self.mms_root = ResolveEnvPath('MMSOURCE112', 'mmsource-1.12') if not self.mms_root: self.mms_root = ResolveEnvPath('MMSOURCE', 'metamod-source') + if not self.mms_root: + self.mms_root = ResolveEnvPath('MMSOURCE_DEV', 'metamod-source') if not self.mms_root: self.mms_root = ResolveEnvPath('MMSOURCE_DEV', 'mmsource-central') @@ -127,12 +129,45 @@ class ExtensionConfig(object): raise Exception('Could not find a source copy of Metamod:Source') self.mms_root = Normalize(self.mms_root) + if builder.options.sm_path: + self.sm_root = builder.options.sm_path + else: + self.sm_root = ResolveEnvPath('SOURCEMOD112', 'sourcemod-1.12') + if not self.sm_root: + self.sm_root = ResolveEnvPath('SOURCEMOD', 'sourcemod') + if not self.sm_root: + self.sm_root = ResolveEnvPath('SOURCEMOD_DEV', 'sourcemod') + if not self.sm_root: + self.sm_root = ResolveEnvPath('SOURCEMOD_DEV', 'sourcemod-central') + + if not self.sm_root or not os.path.isdir(self.sm_root): + raise Exception('Could not find a source copy of SourceMod') + self.sm_root = Normalize(self.sm_root) + def configure(self): - cxx = builder.DetectCompilers() + + allowed_archs = ['x86','x86_64'] + + if not set(self.target_archs).issubset(allowed_archs): + raise Exception('Unknown target architecture: {0}'.format(self.target_archs)) + + for cxx in self.all_targets: + self.configure_cxx(cxx) + + def configure_cxx(self, cxx): + if cxx.family == 'msvc': + if cxx.version < 1914 and builder.options.generator != 'vs': + raise Exception(f'Only MSVC 2017 15.7 and later are supported, full C++17 support is required. ({str(cxx.version)} < 1914)') + elif cxx.family == 'gcc': + if cxx.version < 'gcc-8': + raise Exception('Only GCC versions 8 or later are supported, full C++17 support is required.') + elif cxx.family == 'clang': + if cxx.version < 'clang-5': + raise Exception('Only clang versions 5 or later are supported, full C++17 support is required.') if cxx.like('gcc'): self.configure_gcc(cxx) - elif cxx.vendor == 'msvc': + elif cxx.family == 'msvc': self.configure_msvc(cxx) # Optimization @@ -144,18 +179,13 @@ class ExtensionConfig(object): cxx.defines += ['DEBUG', '_DEBUG'] # Platform-specifics - if builder.target_platform == 'linux': + if cxx.target.platform == 'linux': self.configure_linux(cxx) - elif builder.target_platform == 'mac': + elif cxx.target.platform == 'mac': self.configure_mac(cxx) - elif builder.target_platform == 'windows': + elif cxx.target.platform == 'windows': self.configure_windows(cxx) - # Finish up. - cxx.includes += [ - os.path.join(self.sm_root, 'public'), - ] - def configure_gcc(self, cxx): cxx.defines += [ 'stricmp=strcasecmp', @@ -169,56 +199,52 @@ class ExtensionConfig(object): '-pipe', '-fno-strict-aliasing', '-Wall', - #'-Werror', + # '-Werror', '-Wno-unused', '-Wno-switch', '-Wno-array-bounds', - '-msse', - '-m32', '-fvisibility=hidden', - '-Wno-implicit-int-float-conversion', - '-fno-omit-frame-pointer', ] + + if cxx.target.arch in ['x86', 'x86_64']: + cxx.cflags += ['-msse'] + cxx.cxxflags += [ - '-fno-exceptions', '-fno-threadsafe-statics', '-Wno-non-virtual-dtor', '-Wno-overloaded-virtual', + '-Wno-register', '-fvisibility-inlines-hidden', + '-std=c++17', ] - cxx.linkflags += ['-m32'] - if cxx.version >= 'clang-5': - cxx.cxxflags += ['-Wno-register','-std=c++17'] - else: - cxx.cxxflags += ['-std=c++17'] - - have_gcc = cxx.vendor == 'gcc' - have_clang = cxx.vendor == 'clang' - if cxx.version >= 'clang-3.6': - cxx.cxxflags += ['-Wno-inconsistent-missing-override'] - if have_clang or (cxx.version >= 'gcc-4.6'): - cxx.cflags += ['-Wno-narrowing'] - if have_clang or (cxx.version >= 'gcc-4.7'): - cxx.cxxflags += ['-Wno-delete-non-virtual-dtor'] - if cxx.version >= 'gcc-4.8': - cxx.cflags += ['-Wno-unused-result'] + + have_gcc = cxx.family == 'gcc' + have_clang = cxx.family == 'clang' + # Work around errors from smsdk_ext.cpp if have_clang: cxx.cxxflags += ['-Wno-implicit-exception-spec-mismatch'] - if cxx.version >= 'apple-clang-5.1' or cxx.version >= 'clang-3.4': - cxx.cxxflags += ['-Wno-deprecated-register'] - else: - cxx.cxxflags += ['-Wno-deprecated'] - cxx.cflags += ['-Wno-sometimes-uninitialized'] + + # Work around SDK warnings. + if cxx.version >= 'clang-10.0' or cxx.version >= 'apple-clang-12.0': + cxx.cflags += [ + '-Wno-implicit-int-float-conversion', + '-Wno-tautological-overlap-compare', + ] if have_gcc: cxx.cflags += ['-mfpmath=sse'] + cxx.cflags += ['-Wno-maybe-uninitialized'] if builder.options.opt == '1': - cxx.cflags += ['-O3'] + cxx.cflags += ['-O3'] + + # Don't omit the frame pointer. + cxx.cflags += ['-fno-omit-frame-pointer'] def configure_msvc(self, cxx): + if builder.options.debug == '1': cxx.cflags += ['/MTd'] cxx.linkflags += ['/NODEFAULTLIB:libcmt'] @@ -268,26 +294,38 @@ class ExtensionConfig(object): def configure_linux(self, cxx): cxx.defines += ['LINUX', '_LINUX', 'POSIX', '_FILE_OFFSET_BITS=64'] cxx.linkflags += ['-lm'] - if cxx.vendor == 'gcc': + if cxx.family == 'gcc': cxx.linkflags += ['-static-libgcc'] - elif cxx.vendor == 'clang': + elif cxx.family == 'clang': cxx.linkflags += ['-lgcc_eh'] cxx.linkflags += ['-static-libstdc++'] def configure_mac(self, cxx): - cxx.defines += ['OSX', '_OSX', 'POSIX'] - cxx.cflags += ['-mmacosx-version-min=10.5'] + cxx.defines += ['OSX', '_OSX', 'POSIX', 'KE_ABSOLUTELY_NO_STL'] + cxx.cflags += ['-mmacosx-version-min=10.15'] cxx.linkflags += [ - '-mmacosx-version-min=10.5', - '-arch', 'i386', - '-lstdc++', - '-stdlib=libstdc++', + '-mmacosx-version-min=10.15', + '-stdlib=libc++', + '-lc++', ] - cxx.cxxflags += ['-stdlib=libstdc++'] + cxx.cxxflags += ['-stdlib=libc++'] def configure_windows(self, cxx): cxx.defines += ['WIN32', '_WINDOWS'] - + + def LibraryBuilder(self, compiler, name): + binary = compiler.Library(name) + self.AddVersioning(binary) + if binary.compiler.like('msvc'): + binary.compiler.linkflags += ['/SUBSYSTEM:WINDOWS'] + self.AddCxxCompat(binary) + return binary + + def Library(self, context, compiler, name): + compiler = compiler.clone() + SetArchFlags(compiler) + return self.LibraryBuilder(compiler, name) + def ConfigureForExtension(self, context, compiler): compiler.cxxincludes += [ os.path.join(context.currentSourcePath), @@ -300,154 +338,64 @@ class ExtensionConfig(object): ] return compiler - def ConfigureForHL2(self, binary, sdk): - compiler = binary.compiler + def ExtLibrary(self, context, compiler, name): + binary = self.Library(context, compiler, name) + SetArchFlags(compiler) + self.ConfigureForExtension(context, binary.compiler) + return binary - if sdk.name == 'episode1': - mms_path = os.path.join(self.mms_root, 'core-legacy') - else: - mms_path = os.path.join(self.mms_root, 'core') + def AddCxxCompat(self, binary): + if binary.compiler.target.platform == 'linux': + binary.sources += [ + os.path.join(self.sm_root, 'public', 'amtl', 'compat', 'stdcxx.cpp'), + ] + + def ConfigureForHL2(self, context, binary, sdk): + compiler = binary.compiler + SetArchFlags(compiler) compiler.cxxincludes += [ - os.path.join(mms_path), - os.path.join(mms_path, 'sourcehook'), + os.path.join(self.mms_root, 'core'), + os.path.join(self.mms_root, 'core', 'sourcehook'), ] - defines = ['SE_' + PossibleSDKs[i].define + '=' + PossibleSDKs[i].code for i in PossibleSDKs] - compiler.defines += defines - - paths = [ - ['public'], - ['public', 'engine'], - ['public', 'mathlib'], - ['public', 'vstdlib'], - ['public', 'tier0'], - ['public', 'tier1'] - ] - if sdk.name == 'episode1' or sdk.name == 'darkm': - paths.append(['public', 'dlls']) - paths.append(['game_shared']) - else: - paths.append(['public', 'game', 'server']) - paths.append(['public', 'toolframework']) - paths.append(['game', 'shared']) - paths.append(['common']) - - compiler.defines += ['SOURCE_ENGINE=' + sdk.code] - - if sdk.name in ['sdk2013', 'bms'] and compiler.like('gcc'): - # The 2013 SDK already has these in public/tier0/basetypes.h - compiler.defines.remove('stricmp=strcasecmp') - compiler.defines.remove('_stricmp=strcasecmp') - compiler.defines.remove('_snprintf=snprintf') - compiler.defines.remove('_vsnprintf=vsnprintf') - - if compiler.like('msvc'): - if compiler.version >= 1900: - compiler.linkflags += ['legacy_stdio_definitions.lib'] - compiler.defines += ['COMPILER_MSVC', 'COMPILER_MSVC32'] - else: - compiler.defines += ['COMPILER_GCC'] - - # For everything after Swarm, this needs to be defined for entity networking - # to work properly with sendprop value changes. - if sdk.name in ['blade', 'insurgency', 'doi', 'csgo']: - compiler.defines += ['NETWORK_VARS_ENABLED'] - - if sdk.name in ['css', 'hl2dm', 'dods', 'sdk2013', 'bms', 'tf2', 'l4d', 'nucleardawn', 'l4d2']: - if builder.target_platform in ['linux', 'mac']: - compiler.defines += ['NO_HOOK_MALLOC', 'NO_MALLOC_OVERRIDE'] - - if sdk.name == 'csgo' and builder.target_platform == 'linux': - compiler.linkflags += ['-lstdc++'] - - for path in paths: - compiler.cxxincludes += [os.path.join(sdk.path, *path)] - - if builder.target_platform == 'linux': - if sdk.name == 'episode1': - lib_folder = os.path.join(sdk.path, 'linux_sdk') - elif sdk.name in ['sdk2013', 'bms']: - lib_folder = os.path.join(sdk.path, 'lib', 'public', 'linux32') - else: - lib_folder = os.path.join(sdk.path, 'lib', 'linux') - elif builder.target_platform == 'mac': - if sdk.name in ['sdk2013', 'bms']: - lib_folder = os.path.join(sdk.path, 'lib', 'public', 'osx32') - else: - lib_folder = os.path.join(sdk.path, 'lib', 'mac') - - if builder.target_platform in ['linux', 'mac']: - if sdk.name in ['sdk2013', 'bms']: - compiler.postlink += [ - compiler.Dep(os.path.join(lib_folder, 'tier1.a')), - compiler.Dep(os.path.join(lib_folder, 'mathlib.a')) - ] - else: - compiler.postlink += [ - compiler.Dep(os.path.join(lib_folder, 'tier1_i486.a')), - compiler.Dep(os.path.join(lib_folder, 'mathlib_i486.a')) - ] + for other_sdk in self.sdk_manifests: + compiler.defines += ['SE_{}={}'.format(other_sdk['define'], other_sdk['code'])] - if sdk.name in ['blade', 'insurgency', 'doi', 'csgo']: - compiler.postlink += [compiler.Dep(os.path.join(lib_folder, 'interfaces_i486.a'))] - - dynamic_libs = [] - if builder.target_platform == 'linux': - if sdk.name in ['css', 'hl2dm', 'dods', 'tf2', 'sdk2013', 'bms', 'nucleardawn', 'l4d2', 'insurgency', 'doi']: - dynamic_libs = ['libtier0_srv.so', 'libvstdlib_srv.so'] - elif sdk.name in ['l4d', 'blade', 'insurgency', 'doi', 'csgo']: - dynamic_libs = ['libtier0.so', 'libvstdlib.so'] - else: - dynamic_libs = ['tier0_i486.so', 'vstdlib_i486.so'] - elif builder.target_platform == 'mac': - compiler.linkflags.append('-liconv') - dynamic_libs = ['libtier0.dylib', 'libvstdlib.dylib'] - elif builder.target_platform == 'windows': - libs = ['tier0', 'tier1', 'vstdlib', 'mathlib'] - if sdk.name in ['swarm', 'blade', 'insurgency', 'doi', 'csgo']: - libs.append('interfaces') - for lib in libs: - lib_path = os.path.join(sdk.path, 'lib', 'public', lib) + '.lib' - compiler.linkflags.append(compiler.Dep(lib_path)) - - for library in dynamic_libs: - source_path = os.path.join(lib_folder, library) - output_path = os.path.join(binary.localFolder, library) - - def make_linker(source_path, output_path): - def link(context, binary): - cmd_node, (output,) = context.AddSymlink(source_path, output_path) - return output - return link - - linker = make_linker(source_path, output_path) - compiler.linkflags[0:0] = [compiler.Dep(library, linker)] + SdkHelpers.configureCxx(context, binary, sdk) return binary - def HL2Library(self, context, name, sdk): - binary = context.compiler.Library(name) + def HL2Library(self, context, compiler, name, sdk): + binary = self.Library(context, compiler, name) self.ConfigureForExtension(context, binary.compiler) - return self.ConfigureForHL2(binary, sdk) - - def HL2Project(self, context, name): - project = context.compiler.LibraryProject(name) - self.ConfigureForExtension(context, project.compiler) - return project - - def HL2Config(self, project, name, sdk): - binary = project.Configure(name, '{0} - {1}'.format(self.tag, sdk.name)) - return self.ConfigureForHL2(binary, sdk) + return self.ConfigureForHL2(context, binary, sdk) + + def HL2Config(self, project, context, compiler, name, sdk): + binary = project.Configure(compiler, name, + '{0} - {1} {2}'.format(self.tag, sdk['name'], compiler.target.arch)) + self.AddCxxCompat(binary) + return self.ConfigureForHL2(context, binary, sdk) + + def HL2ExtConfig(self, project, context, compiler, name, sdk): + binary = project.Configure(compiler, name, + '{0} - {1} {2}'.format(self.tag, sdk['name'], compiler.target.arch)) + self.AddCxxCompat(binary) + self.ConfigureForHL2(context, binary, sdk) + self.ConfigureForExtension(context, binary.compiler) + return binary Extension = ExtensionConfig() Extension.detectSDKs() Extension.configure() -# Add additional buildscripts here +# This will clone the list and each cxx object as we recurse, preventing child +# scripts from messing up global state. +builder.targets = builder.CloneableList(Extension.all_targets) + BuildScripts = [ 'extension/AMBuilder', 'PackageScript', ] -builder.RunBuildScripts(BuildScripts, { 'Extension': Extension}) +builder.Build(BuildScripts, { 'Extension': Extension }) diff --git a/configure.py b/configure.py index 57910e8..88f1cad 100644 --- a/configure.py +++ b/configure.py @@ -4,20 +4,24 @@ # Simple extensions do not need to modify this file. -builder = run.PrepareBuild(sourcePath = sys.path[0]) - -builder.options.add_option('--hl2sdk-root', type=str, dest='hl2sdk_root', default=None, - help='Root search folder for HL2SDKs') -builder.options.add_option('--mms-path', type=str, dest='mms_path', default=None, - help='Path to Metamod:Source') -builder.options.add_option('--sm-path', type=str, dest='sm_path', default=None, +parser = run.BuildParser(sourcePath=sys.path[0], api='2.2') +parser.options.add_argument('--hl2sdk-root', type=str, dest='hl2sdk_root', default=None, + help='Root search folder for HL2SDKs') +parser.options.add_argument('--hl2sdk-manifest-path', type=str, dest='hl2sdk_manifest', default=None, + help='Path to HL2SDK Manifests') +parser.options.add_argument('--sm-path', type=str, dest='sm_path', default=None, help='Path to SourceMod') -builder.options.add_option('--enable-debug', action='store_const', const='1', dest='debug', +parser.options.add_argument('--mms-path', type=str, dest='mms_path', default=None, + help='Path to Metamod:Source') + +parser.options.add_argument('--enable-debug', action='store_const', const='1', dest='debug', help='Enable debugging symbols') -builder.options.add_option('--enable-optimize', action='store_const', const='1', dest='opt', +parser.options.add_argument('--enable-optimize', action='store_const', const='1', dest='opt', help='Enable optimization') -builder.options.add_option('-s', '--sdks', default='all', dest='sdks', - help='Build against specified SDKs; valid args are "all", "present", or ' - 'comma-delimited list of engine names (default: %default)') +parser.options.add_argument('-s', '--sdks', default='present', dest='sdks', + help='Build against specified SDKs; valid args are "none", "all", "present",' + ' or comma-delimited list of engine names') +parser.options.add_argument('--targets', type=str, dest='targets', default=None, + help="Override the target architecture (use commas to separate multiple targets).") +parser.Configure() -builder.Configure() diff --git a/extension/AMBuilder b/extension/AMBuilder index d2a3e24..22f9955 100644 --- a/extension/AMBuilder +++ b/extension/AMBuilder @@ -1,5 +1,5 @@ # vim: set sts=2 ts=8 sw=2 tw=99 et ft=python: -import os, sys +import os projectName = 'sendproxy' @@ -15,11 +15,7 @@ sourceFiles = [ 'sendprop_hookmanager.cpp', ] -############### -# Make sure to edit PackageScript, which copies your files to their appropriate locations -# Simple extensions do not need to modify past this point. - -project = Extension.HL2Project(builder, projectName + '.ext') +project = builder.LibraryProject(projectName) if os.path.isfile(os.path.join(builder.currentSourcePath, 'sdk', 'smsdk_ext.cpp')): # Use the copy included in the project @@ -32,7 +28,13 @@ project.sources += sourceFiles for sdk_name in Extension.sdks: sdk = Extension.sdks[sdk_name] - - binary = Extension.HL2Config(project, projectName + '.ext.' + sdk.ext, sdk) + if sdk['name'] in ['mock']: + continue + + for cxx in builder.targets: + if not cxx.target.arch in sdk['platforms'][cxx.target.platform]: + continue + + binary = Extension.HL2ExtConfig(project, builder, cxx, projectName + '.ext.' + sdk['extension'], sdk) -Extension.extensions = builder.Add(project) +Extension.extensions += builder.Add(project) From f4d82c4d2e4891d350a0a6a1398566721a201c81 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Fri, 12 Dec 2025 16:22:36 +0800 Subject: [PATCH 62/73] Attempt to add safetyhook support --- AMBuildScript | 47 ++++ extension/AMBuilder | 3 +- extension/CDetour/detourhelpers.h | 98 ------- extension/CDetour/detours.cpp | 192 ------------- extension/CDetour/detours.h | 435 ------------------------------ extension/asm/asm.c | 421 ----------------------------- extension/asm/asm.h | 40 --- 7 files changed, 48 insertions(+), 1188 deletions(-) delete mode 100644 extension/CDetour/detourhelpers.h delete mode 100644 extension/CDetour/detours.cpp delete mode 100644 extension/CDetour/detours.h delete mode 100644 extension/asm/asm.c delete mode 100644 extension/asm/asm.h diff --git a/AMBuildScript b/AMBuildScript index d1f9e15..47371b1 100644 --- a/AMBuildScript +++ b/AMBuildScript @@ -350,6 +350,36 @@ class ExtensionConfig(object): os.path.join(self.sm_root, 'public', 'amtl', 'compat', 'stdcxx.cpp'), ] + def AddCDetour(self, binary): + sm_public_path = os.path.join(self.sm_root, 'public') + binary.sources += [ os.path.join(sm_public_path, 'CDetour', 'detours.cpp') ] + + if SafetyHook.libsafetyhook is not None: + # sm1.12+ + binary.compiler.cxxincludes += [ os.path.join(sm_public_path, 'safetyhook', 'include') ] + + for task in SafetyHook.libsafetyhook: + if task.target.arch == binary.compiler.target.arch: + binary.compiler.linkflags += [task.binary] + return + raise Exception('No suitable build of safetyhook was found.') + elif os.path.exists( os.path.join(sm_public_path, 'asm') ) and os.path.exists( os.path.join(sm_public_path, 'libudis86') ): + # sm1.10+ + binary.sources += [ os.path.join(sm_public_path, 'asm', 'asm.c') ] + libudis_folder = os.path.join(sm_public_path, 'libudis86') + if os.path.isdir(libudis_folder): + binary.compiler.defines += ['HAVE_STRING_H'] + binary.sources += [ + os.path.join(libudis_folder, 'decode.c'), + os.path.join(libudis_folder, 'itab.c'), + os.path.join(libudis_folder, 'syn-att.c'), + os.path.join(libudis_folder, 'syn-intel.c'), + os.path.join(libudis_folder, 'syn.c'), + os.path.join(libudis_folder, 'udis86.c'), + ] + else: + raise Exception('Failed to add CDetour.') + def ConfigureForHL2(self, context, binary, sdk): compiler = binary.compiler SetArchFlags(compiler) @@ -389,6 +419,23 @@ Extension = ExtensionConfig() Extension.detectSDKs() Extension.configure() +class SafetyHookShim(object): + def __init__(self): + self.all_targets = {} + self.libsafetyhook = None + +SafetyHook = SafetyHookShim() +SafetyHookPath = os.path.join(Extension.sm_root, 'public', 'safetyhook') + +if os.path.exists(SafetyHookPath): + if os.path.exists( os.path.join('safetyhook', 'AMBuilder') ): + os.unlink(os.path.join('safetyhook', 'AMBuilder')) + shutil.copytree(SafetyHookPath, os.path.join(builder.sourcePath, 'safetyhook'), dirs_exist_ok = True) + + SafetyHook.all_targets = Extension.all_targets + SafetyHook.libsafetyhook = {} + builder.Build( 'safetyhook/AMBuilder', {'SafetyHook': SafetyHook } ) + # This will clone the list and each cxx object as we recurse, preventing child # scripts from messing up global state. builder.targets = builder.CloneableList(Extension.all_targets) diff --git a/extension/AMBuilder b/extension/AMBuilder index 22f9955..6d07110 100644 --- a/extension/AMBuilder +++ b/extension/AMBuilder @@ -7,8 +7,6 @@ projectName = 'sendproxy' sourceFiles = [ 'sdk/memoverride.cpp', 'extension.cpp', - 'CDetour/detours.cpp', - 'asm/asm.c', 'natives.cpp', 'clientpacks_detours.cpp', 'sendproxy_callback.cpp', @@ -36,5 +34,6 @@ for sdk_name in Extension.sdks: continue binary = Extension.HL2ExtConfig(project, builder, cxx, projectName + '.ext.' + sdk['extension'], sdk) + Extension.AddCDetour(binary) Extension.extensions += builder.Add(project) diff --git a/extension/CDetour/detourhelpers.h b/extension/CDetour/detourhelpers.h deleted file mode 100644 index 85cda0e..0000000 --- a/extension/CDetour/detourhelpers.h +++ /dev/null @@ -1,98 +0,0 @@ -/** - * vim: set ts=4 : - * ============================================================================= - * SourceMod - * Copyright (C) 2004-2007 AlliedModders LLC. All rights reserved. - * ============================================================================= - * - * 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 . - * - * As a special exception, AlliedModders LLC gives you permission to link the - * code of this program (as well as its derivative works) to "Half-Life 2," the - * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software - * by the Valve Corporation. You must obey the GNU General Public License in - * all respects for all other code used. Additionally, AlliedModders LLC grants - * this exception to all derivative works. AlliedModders LLC defines further - * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), - * or . - * - * Version: $Id: detourhelpers.h 248 2008-08-27 00:56:22Z pred $ - */ - -#ifndef _INCLUDE_SOURCEMOD_DETOURHELPERS_H_ -#define _INCLUDE_SOURCEMOD_DETOURHELPERS_H_ - -#if defined PLATFORM_POSIX -#include -#define PAGE_SIZE 4096 -#define ALIGN(ar) ((long)ar & ~(PAGE_SIZE-1)) -#define PAGE_EXECUTE_READWRITE PROT_READ|PROT_WRITE|PROT_EXEC -#endif - -struct patch_t -{ - patch_t() - { - patch[0] = 0; - bytes = 0; - } - unsigned char patch[20]; - size_t bytes; -}; - -inline void ProtectMemory(void *addr, int length, int prot) -{ -#if defined PLATFORM_POSIX - void *addr2 = (void *)ALIGN(addr); - mprotect(addr2, sysconf(_SC_PAGESIZE), prot); -#elif defined PLATFORM_WINDOWS - DWORD old_prot; - VirtualProtect(addr, length, prot, &old_prot); -#endif -} - -inline void SetMemPatchable(void *address, size_t size) -{ - ProtectMemory(address, (int)size, PAGE_EXECUTE_READWRITE); -} - -inline void DoGatePatch(unsigned char *target, void *callback) -{ - SetMemPatchable(target, 20); - - target[0] = 0xFF; /* JMP */ - target[1] = 0x25; /* MEM32 */ - *(void **)(&target[2]) = callback; -} - -inline void ApplyPatch(void *address, int offset, const patch_t *patch, patch_t *restore) -{ - ProtectMemory(address, 20, PAGE_EXECUTE_READWRITE); - - unsigned char *addr = (unsigned char *)address + offset; - if (restore) - { - for (size_t i=0; ibytes; i++) - { - restore->patch[i] = addr[i]; - } - restore->bytes = patch->bytes; - } - - for (size_t i=0; ibytes; i++) - { - addr[i] = patch->patch[i]; - } -} - -#endif //_INCLUDE_SOURCEMOD_DETOURHELPERS_H_ diff --git a/extension/CDetour/detours.cpp b/extension/CDetour/detours.cpp deleted file mode 100644 index 71aba44..0000000 --- a/extension/CDetour/detours.cpp +++ /dev/null @@ -1,192 +0,0 @@ -/** -* vim: set ts=4 : -* ============================================================================= -* SourceMod -* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. -* ============================================================================= -* -* 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 . -* -* As a special exception, AlliedModders LLC gives you permission to link the -* code of this program (as well as its derivative works) to "Half-Life 2," the -* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software -* by the Valve Corporation. You must obey the GNU General Public License in -* all respects for all other code used. Additionally, AlliedModders LLC grants -* this exception to all derivative works. AlliedModders LLC defines further -* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), -* or . -* -* Version: $Id: detours.cpp 248 2008-08-27 00:56:22Z pred $ -*/ - -#include "detours.h" -#include - -ISourcePawnEngine *CDetourManager::spengine = NULL; -IGameConfig *CDetourManager::gameconf = NULL; - -void CDetourManager::Init(ISourcePawnEngine *spengine, IGameConfig *gameconf) -{ - CDetourManager::spengine = spengine; - CDetourManager::gameconf = gameconf; -} - -CDetour *CDetourManager::CreateDetour(void *callbackfunction, void **trampoline, const char *signame) -{ - CDetour *detour = new CDetour(callbackfunction, trampoline, signame); - if (detour) - { - if (!detour->Init(spengine, gameconf)) - { - delete detour; - return NULL; - } - - return detour; - } - - return NULL; -} - -CDetour::CDetour(void *callbackfunction, void **trampoline, const char *signame) -{ - enabled = false; - detoured = false; - detour_address = NULL; - detour_trampoline = NULL; - this->signame = signame; - this->detour_callback = callbackfunction; - spengine = NULL; - gameconf = NULL; - this->trampoline = trampoline; -} - -bool CDetour::Init(ISourcePawnEngine *spengine, IGameConfig *gameconf) -{ - this->spengine = spengine; - this->gameconf = gameconf; - - if (!CreateDetour()) - { - enabled = false; - return enabled; - } - - enabled = true; - - return enabled; -} - -void CDetour::Destroy() -{ - DeleteDetour(); - delete this; -} - -bool CDetour::IsEnabled() -{ - return enabled; -} - -bool CDetour::CreateDetour() -{ - if (!gameconf->GetMemSig(signame, &detour_address)) - { - g_pSM->LogError(myself, "Could not locate %s - Disabling detour", signame); - return false; - } - - if (!detour_address) - { - g_pSM->LogError(myself, "Sigscan for %s failed - Disabling detour to prevent crashes", signame); - return false; - } - - detour_restore.bytes = copy_bytes((unsigned char *)detour_address, NULL, OP_JMP_SIZE+1); - - /* First, save restore bits */ - for (size_t i=0; iAllocatePageMemory(CodeSize); - spengine->SetReadWrite(wr.outbase); - wr.outptr = wr.outbase; - detour_trampoline = wr.outbase; - goto jit_rewind; - } - - spengine->SetReadExecute(wr.outbase); - - *trampoline = detour_trampoline; - - return true; -} - -void CDetour::DeleteDetour() -{ - if (detoured) - { - DisableDetour(); - } - - if (detour_trampoline) - { - /* Free the allocated trampoline memory */ - spengine->FreePageMemory(detour_trampoline); - detour_trampoline = NULL; - } -} - -void CDetour::EnableDetour() -{ - if (!detoured) - { - DoGatePatch((unsigned char *)detour_address, &detour_callback); - detoured = true; - } -} - -void CDetour::DisableDetour() -{ - if (detoured) - { - /* Remove the patch */ - ApplyPatch(detour_address, 0, &detour_restore, NULL); - detoured = false; - } -} diff --git a/extension/CDetour/detours.h b/extension/CDetour/detours.h deleted file mode 100644 index c7c371b..0000000 --- a/extension/CDetour/detours.h +++ /dev/null @@ -1,435 +0,0 @@ -/** -* vim: set ts=4 : -* ============================================================================= -* SourceMod -* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. -* ============================================================================= -* -* 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 . -* -* As a special exception, AlliedModders LLC gives you permission to link the -* code of this program (as well as its derivative works) to "Half-Life 2," the -* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software -* by the Valve Corporation. You must obey the GNU General Public License in -* all respects for all other code used. Additionally, AlliedModders LLC grants -* this exception to all derivative works. AlliedModders LLC defines further -* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), -* or . -* -* Version: $Id: detours.h 257 2008-09-23 03:12:13Z pred $ -*/ - -#ifndef _INCLUDE_SOURCEMOD_DETOURS_H_ -#define _INCLUDE_SOURCEMOD_DETOURS_H_ - -#include "../extension.h" -#include -#include -#include "detourhelpers.h" - -/** - * CDetours class for SourceMod Extensions by pRED* - * detourhelpers.h entirely stolen from CSS:DM and were written by BAILOPAN (I assume). - * asm.h/c from devmaster.net (thanks cybermind) edited by pRED* to handle gcc -fPIC thunks correctly - * Concept by Nephyrin Zey (http://www.doublezen.net/) and Windows Detour Library (http://research.microsoft.com/sn/detours/) - * Member function pointer ideas by Don Clugston (http://www.codeproject.com/cpp/FastDelegate.asp) - */ - -#define DETOUR_MEMBER_CALL(name) (this->*name##_Actual) -#define DETOUR_STATIC_CALL(name) (name##_Actual) - -#define DETOUR_DECL_STATIC0(name, ret) \ -ret (*name##_Actual)(void) = NULL; \ -ret name(void) - -#define DETOUR_DECL_STATIC1(name, ret, p1type, p1name) \ -ret (*name##_Actual)(p1type) = NULL; \ -ret name(p1type p1name) - -#define DETOUR_DECL_STATIC2(name, ret, p1type, p1name, p2type, p2name) \ - ret (*name##_Actual)(p1type, p2type) = NULL; \ - ret name(p1type p1name, p2type p2name) - - -#define DETOUR_DECL_STATIC3(name, ret, p1type, p1name, p2type, p2name, p3type, p3name) \ - ret (*name##_Actual)(p1type, p2type, p3type) = NULL; \ - ret name(p1type p1name, p2type p2name, p3type p3name) - -#define DETOUR_DECL_STATIC4(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name) \ - ret (*name##_Actual)(p1type, p2type, p3type, p4type) = NULL; \ - ret name(p1type p1name, p2type p2name, p3type p3name, p4type p4name) - -#define DETOUR_DECL_STATIC5(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name, p5type, p5name) \ - ret (*name##_Actual)(p1type, p2type, p3type, p4type, p5type) = NULL; \ - ret name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name) - -#define DETOUR_DECL_STATIC6(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name, p5type, p5name, p6type, p6name) \ - ret (*name##_Actual)(p1type, p2type, p3type, p4type, p5type, p6type) = NULL; \ - ret name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name, p6type p6name) - -#define DETOUR_DECL_STATIC7(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name, p5type, p5name, p6type, p6name, p7type, p7name) \ - ret (*name##_Actual)(p1type, p2type, p3type, p4type, p5type, p6type, p7type) = NULL; \ - ret name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name, p6type p6name, p7type p7name) - -#define DETOUR_DECL_STATIC0_fastcall(name, ret) \ - ret (__fastcall *name##_Actual)(void) = NULL; \ - ret __fastcall name(void) - -#define DETOUR_DECL_STATIC1_fastcall(name, ret, p1type, p1name) \ - ret (__fastcall *name##_Actual)(p1type) = NULL; \ - ret __fastcall name(p1type p1name) - -#define DETOUR_DECL_STATIC2_fastcall(name, ret, p1type, p1name, p2type, p2name) \ - ret (__fastcall *name##_Actual)(p1type, p2type) = NULL; \ - ret __fastcall name(p1type p1name, p2type p2name) - -#define DETOUR_DECL_STATIC3_fastcall(name, ret, p1type, p1name, p2type, p2name, p3type, p3name) \ - ret (__fastcall *name##_Actual)(p1type, p2type, p3type) = NULL; \ - ret __fastcall name(p1type p1name, p2type p2name, p3type p3name) - -#define DETOUR_DECL_STATIC4_fastcall(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name) \ - ret (__fastcall *name##_Actual)(p1type, p2type, p3type, p4type) = NULL; \ - ret __fastcall name(p1type p1name, p2type p2name, p3type p3name, p4type p4name) - -#define DETOUR_DECL_STATIC5_fastcall(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name, p5type, p5name) \ - ret (__fastcall *name##_Actual)(p1type, p2type, p3type, p4type, p5type) = NULL; \ - ret __fastcall name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name) - -#define DETOUR_DECL_STATIC6_fastcall(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name, p5type, p5name, p6type, p6name) \ - ret (__fastcall *name##_Actual)(p1type, p2type, p3type, p4type, p5type, p6type) = NULL; \ - ret __fastcall name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name, p6type p6name) - -#define DETOUR_DECL_STATIC7_fastcall(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name, p5type, p5name, p6type, p6name, p7type, p7name) \ - ret (__fastcall *name##_Actual)(p1type, p2type, p3type, p4type, p5type, p6type, p7type) = NULL; \ - ret __fastcall name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name, p6type p6name, p7type p7name) - -#define DETOUR_DECL_MEMBER0(name, ret) \ -class name##Class \ -{ \ -public: \ - ret name(); \ - static ret (name##Class::* name##_Actual)(void); \ -}; \ -ret (name##Class::* name##Class::name##_Actual)(void) = NULL; \ -ret name##Class::name() - -#define DETOUR_DECL_MEMBER1(name, ret, p1type, p1name) \ -class name##Class \ -{ \ -public: \ - ret name(p1type p1name); \ - static ret (name##Class::* name##_Actual)(p1type); \ -}; \ -ret (name##Class::* name##Class::name##_Actual)(p1type) = NULL; \ -ret name##Class::name(p1type p1name) - -#define DETOUR_DECL_MEMBER2(name, ret, p1type, p1name, p2type, p2name) \ -class name##Class \ -{ \ -public: \ - ret name(p1type p1name, p2type p2name); \ - static ret (name##Class::* name##_Actual)(p1type, p2type); \ -}; \ -ret (name##Class::* name##Class::name##_Actual)(p1type, p2type) = NULL; \ -ret name##Class::name(p1type p1name, p2type p2name) - -#define DETOUR_DECL_MEMBER3(name, ret, p1type, p1name, p2type, p2name, p3type, p3name) \ -class name##Class \ -{ \ -public: \ - ret name(p1type p1name, p2type p2name, p3type p3name); \ - static ret (name##Class::* name##_Actual)(p1type, p2type, p3type); \ -}; \ -ret (name##Class::* name##Class::name##_Actual)(p1type, p2type, p3type) = NULL; \ -ret name##Class::name(p1type p1name, p2type p2name, p3type p3name) - -#define DETOUR_DECL_MEMBER4(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name) \ -class name##Class \ -{ \ -public: \ - ret name(p1type p1name, p2type p2name, p3type p3name, p4type p4name); \ - static ret (name##Class::* name##_Actual)(p1type, p2type, p3type, p4type); \ -}; \ -ret (name##Class::* name##Class::name##_Actual)(p1type, p2type, p3type, p4type) = NULL; \ -ret name##Class::name(p1type p1name, p2type p2name, p3type p3name, p4type p4name) - -#define DETOUR_DECL_MEMBER5(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name, p5type, p5name) \ -class name##Class \ -{ \ -public: \ - ret name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name); \ - static ret (name##Class::* name##_Actual)(p1type, p2type, p3type, p4type, p5type); \ -}; \ -ret (name##Class::* name##Class::name##_Actual)(p1type, p2type, p3type, p4type, p5type) = NULL; \ -ret name##Class::name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name) - -#define DETOUR_DECL_MEMBER7(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name, p5type, p5name, p6type, p6name, p7type, p7name) \ -class name##Class \ -{ \ -public: \ - ret name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name, p6type p6name, p7type p7name); \ - static ret (name##Class::* name##_Actual)(p1type, p2type, p3type, p4type, p5type, p6type, p7type); \ -}; \ -ret (name##Class::* name##Class::name##_Actual)(p1type, p2type, p3type, p4type, p5type, p6type, p7type) = NULL; \ -ret name##Class::name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name, p6type p6name, p7type p7name) - -#define DETOUR_DECL_MEMBER8(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name, p5type, p5name, p6type, p6name, p7type, p7name, p8type, p8name) \ -class name##Class \ -{ \ -public: \ - ret name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name, p6type p6name, p7type p7name, p8type p8name); \ - static ret (name##Class::* name##_Actual)(p1type, p2type, p3type, p4type, p5type, p6type, p7type, p8type); \ -}; \ -ret (name##Class::* name##Class::name##_Actual)(p1type, p2type, p3type, p4type, p5type, p6type, p7type, p8type) = NULL; \ -ret name##Class::name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name, p6type p6name, p7type p7name, p8type p8name) - -#define DETOUR_DECL_MEMBER9(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name, p5type, p5name, p6type, p6name, p7type, p7name, p8type, p8name, p9type, p9name) \ -class name##Class \ -{ \ -public: \ - ret name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name, p6type p6name, p7type p7name, p8type p8name, p9type p9name); \ - static ret (name##Class::* name##_Actual)(p1type, p2type, p3type, p4type, p5type, p6type, p7type, p8type, p9type); \ -}; \ -ret (name##Class::* name##Class::name##_Actual)(p1type, p2type, p3type, p4type, p5type, p6type, p7type, p8type, p9type) = NULL; \ -ret name##Class::name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name, p6type p6name, p7type p7name, p8type p8name, p9type p9name) - -#define DETOUR_DECL_MEMBER10(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name, p5type, p5name, p6type, p6name, p7type, p7name, p8type, p8name, p9type, p9name, p10type, p10name) \ -class name##Class \ -{ \ -public: \ - ret name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name, p6type p6name, p7type p7name, p8type p8name, p9type p9name, p10type p10name); \ - static ret (name##Class::* name##_Actual)(p1type, p2type, p3type, p4type, p5type, p6type, p7type, p8type, p9type, p10type); \ -}; \ -ret (name##Class::* name##Class::name##_Actual)(p1type, p2type, p3type, p4type, p5type, p6type, p7type, p8type, p9type, p10type) = NULL; \ -ret name##Class::name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name, p6type p6name, p7type p7name, p8type p8name, p9type p9name, p10type p10name) - -#define DETOUR_DECL_MEMBER0_fastcall(name, ret) \ -class name##Class \ -{ \ -public: \ - ret __fastcall name(); \ - static ret (__fastcall name##Class::* name##_Actual)(void); \ -}; \ -ret (__fastcall name##Class::* name##Class::name##_Actual)(void) = NULL; \ -ret __fastcall name##Class::name() - -#define DETOUR_DECL_MEMBER1_fastcall(name, ret, p1type, p1name) \ -class name##Class \ -{ \ -public: \ - ret __fastcall name(p1type p1name); \ - static ret (__fastcall name##Class::* name##_Actual)(p1type); \ -}; \ -ret (__fastcall name##Class::* name##Class::name##_Actual)(p1type) = NULL; \ -ret __fastcall name##Class::name(p1type p1name) - -#define DETOUR_DECL_MEMBER2_fastcall(name, ret, p1type, p1name, p2type, p2name) \ -class name##Class \ -{ \ -public: \ - ret __fastcall name(p1type p1name, p2type p2name); \ - static ret (__fastcall name##Class::* name##_Actual)(p1type, p2type); \ -}; \ -ret (__fastcall name##Class::* name##Class::name##_Actual)(p1type, p2type) = NULL; \ -ret __fastcall name##Class::name(p1type p1name, p2type p2name) - -#define DETOUR_DECL_MEMBER3_fastcall(name, ret, p1type, p1name, p2type, p2name, p3type, p3name) \ -class name##Class \ -{ \ -public: \ - ret __fastcall name(p1type p1name, p2type p2name, p3type p3name); \ - static ret (__fastcall name##Class::* name##_Actual)(p1type, p2type, p3type); \ -}; \ -ret (__fastcall name##Class::* name##Class::name##_Actual)(p1type, p2type, p3type) = NULL; \ -ret __fastcall name##Class::name(p1type p1name, p2type p2name, p3type p3name) - -#define DETOUR_DECL_MEMBER4_fastcall(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name) \ -class name##Class \ -{ \ -public: \ - ret __fastcall name(p1type p1name, p2type p2name, p3type p3name, p4type p4name); \ - static ret (__fastcall name##Class::* name##_Actual)(p1type, p2type, p3type, p4type); \ -}; \ -ret (__fastcall name##Class::* name##Class::name##_Actual)(p1type, p2type, p3type, p4type) = NULL; \ -ret __fastcall name##Class::name(p1type p1name, p2type p2name, p3type p3name, p4type p4name) - -#define DETOUR_DECL_MEMBER5_fastcall(name, ret, p1type, p1name, p2type, p2name, p3type, p3name, p4type, p4name, p5type, p5name) \ -class name##Class \ -{ \ -public: \ - ret __fastcall name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name); \ - static ret (__fastcall name##Class::* name##_Actual)(p1type, p2type, p3type, p4type, p5type); \ -}; \ -ret (__fastcall name##Class::* name##Class::name##_Actual)(p1type, p2type, p3type, p4type, p5type) = NULL; \ -ret __fastcall name##Class::name(p1type p1name, p2type p2name, p3type p3name, p4type p4name, p5type p5name) - -#define GET_MEMBER_CALLBACK(name) (void *)GetCodeAddress(&name##Class::name) -#define GET_MEMBER_TRAMPOLINE(name) (void **)(&name##Class::name##_Actual) - -#define GET_STATIC_CALLBACK(name) (void *)&name -#define GET_STATIC_TRAMPOLINE(name) (void **)&name##_Actual - -#define DETOUR_CREATE_MEMBER(name, gamedata) CDetourManager::CreateDetour(GET_MEMBER_CALLBACK(name), GET_MEMBER_TRAMPOLINE(name), gamedata); -#define DETOUR_CREATE_STATIC(name, gamedata) CDetourManager::CreateDetour(GET_STATIC_CALLBACK(name), GET_STATIC_TRAMPOLINE(name), gamedata); - - -class GenericClass {}; -typedef void (GenericClass::*VoidFunc)(); - -inline void *GetCodeAddr(VoidFunc mfp) -{ - return *(void **)&mfp; -} - -/** - * Converts a member function pointer to a void pointer. - * This relies on the assumption that the code address lies at mfp+0 - * This is the case for both g++ and later MSVC versions on non virtual functions but may be different for other compilers - * Based on research by Don Clugston : http://www.codeproject.com/cpp/FastDelegate.asp - */ -#define GetCodeAddress(mfp) GetCodeAddr(reinterpret_cast(mfp)) - -class CDetourManager; - -class CDetour -{ -public: - - bool IsEnabled(); - - /** - * These would be somewhat self-explanatory I hope - */ - void EnableDetour(); - void DisableDetour(); - - void Destroy(); - - friend class CDetourManager; - -protected: - CDetour(void *callbackfunction, void **trampoline, const char *signame); - - bool Init(ISourcePawnEngine *spengine, IGameConfig *gameconf); -private: - - /* These create/delete the allocated memory */ - bool CreateDetour(); - void DeleteDetour(); - - bool enabled; - bool detoured; - - patch_t detour_restore; - /* Address of the detoured function */ - void *detour_address; - /* Address of the allocated trampoline function */ - void *detour_trampoline; - /* Address of the callback handler */ - void *detour_callback; - /* The function pointer used to call our trampoline */ - void **trampoline; - - const char *signame; - ISourcePawnEngine *spengine; - IGameConfig *gameconf; -}; - -class CDetourManager -{ -public: - - static void Init(ISourcePawnEngine *spengine, IGameConfig *gameconf); - - /** - * Creates a new detour - * - * @param callbackfunction Void pointer to your detour callback function. - * @param trampoline Address of the trampoline pointer - * @param signame Section name containing a signature to fetch from the gamedata file. - * @return A new CDetour pointer to control your detour. - * - * Example: - * - * CBaseServer::ConnectClient(netadr_s &, int, int, int, char const*, char const*, char const*, int) - * - * Define a new class with the required function and a member function pointer to the same type: - * - * class CBaseServerDetour - * { - * public: - * bool ConnectClient(void *netaddr_s, int, int, int, char const*, char const*, char const*, int); - * static bool (CBaseServerDetour::* ConnectClient_Actual)(void *netaddr_s, int, int, int, char const*, char const*, char const*, int); - * } - * - * void *callbackfunc = GetCodeAddress(&CBaseServerDetour::ConnectClient); - * void **trampoline = (void **)(&CBaseServerDetour::ConnectClient_Actual); - * - * Creation: - * CDetourManager::CreateDetour(callbackfunc, trampoline, "ConnectClient"); - * - * Usage: - * - * CBaseServerDetour::ConnectClient(void *netaddr_s, int, int, int, char const*, char const*, char const*, int) - * { - * //pre hook code - * bool result = (this->*ConnectClient_Actual)(netaddr_s, rest of params); - * //post hook code - * return result; - * } - * - * Note we changed the netadr_s reference into a void* to avoid needing to define the type - */ - static CDetour *CreateDetour(void *callbackfunction, void **trampoline, const char *signame); - - friend class CBlocker; - friend class CDetour; - -private: - static ISourcePawnEngine *spengine; - static IGameConfig *gameconf; -}; - -#define DECL_DETOUR(name) \ - CDetour *name##_Detour = nullptr; - -#define CREATE_DETOUR(name, signname, var) \ - name##_Detour = DETOUR_CREATE_MEMBER(name, signname); \ - if (name##_Detour != NULL) \ - { \ - name##_Detour->EnableDetour(); \ - var &= true; \ - } else { \ - g_pSM->LogError(myself, "Failed to create " signname " detour, check error log.\n"); \ - var = false; \ - } - -#define CREATE_DETOUR_STATIC(name, signname, var) \ - name##_Detour = DETOUR_CREATE_STATIC(name, signname); \ - if (name##_Detour != NULL) \ - { \ - name##_Detour->EnableDetour(); \ - var &= true; \ - } else { \ - g_pSM->LogError(myself, "Failed to create " signname " detour, check error log.\n"); \ - var = false; \ - } - -#define DESTROY_DETOUR(name) \ - if (name##_Detour != nullptr) \ -{ \ - name##_Detour->Destroy(); \ - name##_Detour = nullptr; \ -} - -#endif // _INCLUDE_SOURCEMOD_DETOURS_H_ diff --git a/extension/asm/asm.c b/extension/asm/asm.c deleted file mode 100644 index 2facf8d..0000000 --- a/extension/asm/asm.c +++ /dev/null @@ -1,421 +0,0 @@ -#include "asm.h" - -#ifndef WIN32 -#define _GNU_SOURCE -#include -#include - -#define REG_EAX 0 -#define REG_ECX 1 -#define REG_EDX 2 -#define REG_EBX 3 - -#define IA32_MOV_REG_IMM 0xB8 // encoding is +r -#endif - -extern void Msg( const char *, ... ); - -/** -* Checks if a call to a fpic thunk has just been written into dest. -* If found replaces it with a direct mov that sets the required register to the value of pc. -* -* @param dest Destination buffer where a call opcode + addr (5 bytes) has just been written. -* @param pc The program counter value that needs to be set (usually the next address from the source). -* @noreturn -*/ -void check_thunks(unsigned char *dest, unsigned char *pc) -{ -#if defined WIN32 - return; -#else - /* Step write address back 4 to the start of the function address */ - unsigned char *writeaddr = dest - 4; - unsigned char *calloffset = *(unsigned char **)writeaddr; - unsigned char *calladdr = (unsigned char *)(dest + (unsigned int)calloffset); - - /* Lookup name of function being called */ - if ((*calladdr == 0x8B) && (*(calladdr+2) == 0x24) && (*(calladdr+3) == 0xC3)) - { - //a thunk maybe? - char movByte = IA32_MOV_REG_IMM; - - /* Calculate the correct mov opcode */ - switch (*(calladdr+1)) - { - case 0x04: - { - movByte += REG_EAX; - break; - } - case 0x1C: - { - movByte += REG_EBX; - break; - } - case 0x0C: - { - movByte += REG_ECX; - break; - } - case 0x14: - { - movByte += REG_EDX; - break; - } - default: - { - Msg("Unknown thunk: %c\n", *(calladdr+1)); - break; - } - } - - /* Move our write address back one to where the call opcode was */ - writeaddr--; - - - /* Write our mov */ - *writeaddr = movByte; - writeaddr++; - - /* Write the value - The provided program counter value */ - *(void **)writeaddr = (void *)pc; - writeaddr += 4; - } - - return; -#endif -} - -//if dest is NULL, returns minimum number of bytes needed to be copied -//if dest is not NULL, it will copy the bytes to dest as well as fix CALLs and JMPs -//http://www.devmaster.net/forums/showthread.php?t=2311 -int copy_bytes(unsigned char *func, unsigned char* dest, int required_len) { - int bytecount = 0; - - while(bytecount < required_len && *func != 0xCC) - { - // prefixes F0h, F2h, F3h, 66h, 67h, D8h-DFh, 2Eh, 36h, 3Eh, 26h, 64h and 65h - int operandSize = 4; - int FPU = 0; - int twoByte = 0; - unsigned char opcode = 0x90; - unsigned char modRM = 0xFF; - while(*func == 0xF0 || - *func == 0xF2 || - *func == 0xF3 || - (*func & 0xFC) == 0x64 || - (*func & 0xF8) == 0xD8 || - (*func & 0x7E) == 0x62) - { - if(*func == 0x66) - { - operandSize = 2; - } - else if((*func & 0xF8) == 0xD8) - { - FPU = *func; - if (dest) - *dest++ = *func++; - else - func++; - bytecount++; - break; - } - - if (dest) - *dest++ = *func++; - else - func++; - bytecount++; - } - - // two-byte opcode byte - if(*func == 0x0F) - { - twoByte = 1; - if (dest) - *dest++ = *func++; - else - func++; - bytecount++; - } - - // opcode byte - opcode = *func++; - if (dest) *dest++ = opcode; - bytecount++; - - // mod R/M byte - modRM = 0xFF; - if(FPU) - { - if((opcode & 0xC0) != 0xC0) - { - modRM = opcode; - } - } - else if(!twoByte) - { - if((opcode & 0xC4) == 0x00 || - ((opcode & 0xF4) == 0x60 && ((opcode & 0x0A) == 0x02 || (opcode & 0x09) == 0x09)) || - (opcode & 0xF0) == 0x80 || - ((opcode & 0xF8) == 0xC0 && (opcode & 0x0E) != 0x02) || - (opcode & 0xFC) == 0xD0 || - (opcode & 0xF6) == 0xF6) - { - modRM = *func++; - if (dest) *dest++ = modRM; - bytecount++; - } - } - else - { - if(((opcode & 0xF0) == 0x00 && (opcode & 0x0F) >= 0x04 && (opcode & 0x0D) != 0x0D) || - (opcode & 0xF0) == 0x30 || - opcode == 0x77 || - (opcode & 0xF0) == 0x80 || - ((opcode & 0xF0) == 0xA0 && (opcode & 0x07) <= 0x02) || - (opcode & 0xF8) == 0xC8) - { - // No mod R/M byte - } - else - { - modRM = *func++; - if (dest) *dest++ = modRM; - bytecount++; - } - } - - // SIB - if((modRM & 0x07) == 0x04 && - (modRM & 0xC0) != 0xC0) - { - if (dest) - *dest++ = *func++; //SIB - else - func++; - bytecount++; - } - - // mod R/M displacement - - // Dword displacement, no base - if((modRM & 0xC5) == 0x05) { - if (dest) { - *(unsigned int*)dest = *(unsigned int*)func; - dest += 4; - } - func += 4; - bytecount += 4; - } - - // Byte displacement - if((modRM & 0xC0) == 0x40) { - if (dest) - *dest++ = *func++; - else - func++; - bytecount++; - } - - // Dword displacement - if((modRM & 0xC0) == 0x80) { - if (dest) { - *(unsigned int*)dest = *(unsigned int*)func; - dest += 4; - } - func += 4; - bytecount += 4; - } - - // immediate - if(FPU) - { - // Can't have immediate operand - } - else if(!twoByte) - { - if((opcode & 0xC7) == 0x04 || - (opcode & 0xFE) == 0x6A || // PUSH/POP/IMUL - (opcode & 0xF0) == 0x70 || // Jcc - opcode == 0x80 || - opcode == 0x83 || - (opcode & 0xFD) == 0xA0 || // MOV - opcode == 0xA8 || // TEST - (opcode & 0xF8) == 0xB0 || // MOV - (opcode & 0xFE) == 0xC0 || // RCL - opcode == 0xC6 || // MOV - opcode == 0xCD || // INT - (opcode & 0xFE) == 0xD4 || // AAD/AAM - (opcode & 0xF8) == 0xE0 || // LOOP/JCXZ - opcode == 0xEB || - (opcode == 0xF6 && (modRM & 0x30) == 0x00)) // TEST - { - if (dest) - *dest++ = *func++; - else - func++; - bytecount++; - } - else if((opcode & 0xF7) == 0xC2) // RET - { - if (dest) { - *(unsigned short*)dest = *(unsigned short*)func; - dest += 2; - } - func += 2; - bytecount += 2; - } - else if((opcode & 0xFC) == 0x80 || - (opcode & 0xC7) == 0x05 || - (opcode & 0xF8) == 0xB8 || - (opcode & 0xFE) == 0xE8 || // CALL/Jcc - (opcode & 0xFE) == 0x68 || - (opcode & 0xFC) == 0xA0 || - (opcode & 0xEE) == 0xA8 || - opcode == 0xC7 || - (opcode == 0xF7 && (modRM & 0x30) == 0x00)) - { - if (dest) { - //Fix CALL/JMP offset - if ((opcode & 0xFE) == 0xE8) { - if (operandSize == 4) - { - *(long*)dest = ((func + *(long*)func) - dest); - - //pRED* edit. func is the current address of the call address, +4 is the next instruction, so the value of $pc - check_thunks(dest+4, func+4); - } - else - *(short*)dest = ((func + *(short*)func) - dest); - - } else { - if (operandSize == 4) - *(unsigned long*)dest = *(unsigned long*)func; - else - *(unsigned short*)dest = *(unsigned short*)func; - } - dest += operandSize; - } - func += operandSize; - bytecount += operandSize; - - } - } - else - { - if(opcode == 0xBA || // BT - opcode == 0x0F || // 3DNow! - (opcode & 0xFC) == 0x70 || // PSLLW - (opcode & 0xF7) == 0xA4 || // SHLD - opcode == 0xC2 || - opcode == 0xC4 || - opcode == 0xC5 || - opcode == 0xC6) - { - if (dest) - *dest++ = *func++; - else - func++; - } - else if((opcode & 0xF0) == 0x80) // Jcc -i - { - if (dest) { - if (operandSize == 4) - *(unsigned long*)dest = *(unsigned long*)func; - else - *(unsigned short*)dest = *(unsigned short*)func; - - dest += operandSize; - } - func += operandSize; - bytecount += operandSize; - } - } - } - - return bytecount; -} - -//insert a specific JMP instruction at the given location -void inject_jmp(void* src, void* dest) { - *(unsigned char*)src = OP_JMP; - *(long*)((unsigned char*)src+1) = (long)((unsigned char*)dest - ((unsigned char*)src + OP_JMP_SIZE)); -} - -//fill a given block with NOPs -void fill_nop(void* src, unsigned int len) { - unsigned char* src2 = (unsigned char*)src; - while (len) { - *src2++ = OP_NOP; - --len; - } -} - -void* eval_jump(void* src) { - unsigned char* addr = (unsigned char*)src; - - if (!addr) return 0; - - //import table jump - if (addr[0] == OP_PREFIX && addr[1] == OP_JMP_SEG) { - addr += 2; - addr = *(unsigned char**)addr; - //TODO: if addr points into the IAT - return *(void**)addr; - } - - //8bit offset - else if (addr[0] == OP_JMP_BYTE) { - addr = &addr[OP_JMP_BYTE_SIZE] + *(char*)&addr[1]; - //mangled 32bit jump? - if (addr[0] == OP_JMP) { - addr = addr + *(int*)&addr[1]; - } - return addr; - } - /* - //32bit offset - else if (addr[0] == OP_JMP) { - addr = &addr[OP_JMP_SIZE] + *(int*)&addr[1]; - } - */ - - return addr; -} -/* -from ms detours package -static bool detour_is_imported(PBYTE pbCode, PBYTE pbAddress) -{ - MEMORY_BASIC_INFORMATION mbi; - VirtualQuery((PVOID)pbCode, &mbi, sizeof(mbi)); - __try { - PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)mbi.AllocationBase; - if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) { - return false; - } - - PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((PBYTE)pDosHeader + - pDosHeader->e_lfanew); - if (pNtHeader->Signature != IMAGE_NT_SIGNATURE) { - return false; - } - - if (pbAddress >= ((PBYTE)pDosHeader + - pNtHeader->OptionalHeader - .DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress) && - pbAddress < ((PBYTE)pDosHeader + - pNtHeader->OptionalHeader - .DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress + - pNtHeader->OptionalHeader - .DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].Size)) { - return true; - } - return false; - } - __except(EXCEPTION_EXECUTE_HANDLER) { - return false; - } -} -*/ diff --git a/extension/asm/asm.h b/extension/asm/asm.h deleted file mode 100644 index 6086232..0000000 --- a/extension/asm/asm.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef __ASM_H__ -#define __ASM_H__ - -#define OP_JMP 0xE9 -#define OP_JMP_SIZE 5 - -#define OP_NOP 0x90 -#define OP_NOP_SIZE 1 - -#define OP_PREFIX 0xFF -#define OP_JMP_SEG 0x25 - -#define OP_JMP_BYTE 0xEB -#define OP_JMP_BYTE_SIZE 2 - -#ifdef __cplusplus -extern "C" { -#endif - -void check_thunks(unsigned char *dest, unsigned char *pc); - -//if dest is NULL, returns minimum number of bytes needed to be copied -//if dest is not NULL, it will copy the bytes to dest as well as fix CALLs and JMPs -//http://www.devmaster.net/forums/showthread.php?t=2311 -int copy_bytes(unsigned char *func, unsigned char* dest, int required_len); - -//insert a specific JMP instruction at the given location -void inject_jmp(void* src, void* dest); - -//fill a given block with NOPs -void fill_nop(void* src, unsigned int len); - -//evaluate a JMP at the target -void* eval_jump(void* src); - -#ifdef __cplusplus -} -#endif - -#endif //__ASM_H__ From c753d18f7cab09c5f1438443341fce5a5fe0c28d Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Fri, 12 Dec 2025 16:23:01 +0800 Subject: [PATCH 63/73] Add build for multiple SM versions --- .github/workflows/build.yml | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fcafaa3..911ef67 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,6 +13,7 @@ jobs: strategy: matrix: os: [ubuntu-22.04, ubuntu-latest, windows-latest] + sm_version: [sm1.11, sm1.12, smlatest] include: - os: windows-latest os_short: win @@ -28,6 +29,17 @@ jobs: compiler_cc: clang-14 compiler_cxx: clang++-14 dbgopt: --enable-debug + + - sm_version: sm1.11 + sm_branch: 1.11-dev + mm_branch: 1.10-dev + - sm_version: sm1.12 + sm_branch: 1.12-dev + mm_branch: 1.12-dev + - sm_version: smlatest + sm_branch: master + mm_branch: 1.12-dev + fail-fast: false name: Build Project ${{ matrix.os_short }} @@ -96,7 +108,7 @@ jobs: - name: Prepare Sourcemod working-directory: alliedmodders run: | - git clone --recursive https://github.com/alliedmodders/sourcemod -b 1.11-dev + git clone --recursive https://github.com/alliedmodders/sourcemod -b ${{ matrix.sm_branch }} - name: Prepare L4D SDK working-directory: alliedmodders @@ -108,7 +120,7 @@ jobs: - name: Prepare Metamod Source working-directory: alliedmodders run: | - git clone https://github.com/alliedmodders/metamod-source mmsource-1.10 -b 1.10-dev + git clone https://github.com/alliedmodders/metamod-source mmsource-1.10 -b ${{ matrix.mm_branch }} - name: Checkout Repository uses: actions/checkout@v4 @@ -133,5 +145,5 @@ jobs: - name: Upload Binary (Package) uses: actions/upload-artifact@v4 with: - name: sendproxy-${{ matrix.os_short }}-${{ env.GITHUB_SHA_SHORT }} + name: sendproxy-${{ matrix.os_short }}-${{ matrix.sm_branch }}-${{ env.GITHUB_SHA_SHORT }} path: src/build/package \ No newline at end of file From 2784f226a3773b15ff16a8be717ebd3e0f279247 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Fri, 12 Dec 2025 16:26:06 +0800 Subject: [PATCH 64/73] Fix build step & name --- .github/workflows/build.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 911ef67..7afd1f4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -42,7 +42,7 @@ jobs: fail-fast: false - name: Build Project ${{ matrix.os_short }} + name: Build ${{ matrix.os_short }}-${{ matrix.sm_branch }} runs-on: ${{ matrix.os }} env: SDKS: '["l4d","l4d2"]' @@ -94,17 +94,17 @@ jobs: run: | python -m pip install --upgrade pip setuptools wheel + - name: Prepare Alliedmodders Directory + shell: bash + run: | + mkdir alliedmodders + - name: Install AMBuild working-directory: alliedmodders run: | git clone https://github.com/alliedmodders/ambuild pip install ./ambuild - - name: Prepare Alliedmodders Directory - shell: bash - run: | - mkdir alliedmodders - - name: Prepare Sourcemod working-directory: alliedmodders run: | From fb7b05262f7645707e0fd8ebe7d926e6b86110aa Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Fri, 12 Dec 2025 20:05:51 +0800 Subject: [PATCH 65/73] Attempt to fix hl2sdk manifest lookup --- .github/workflows/build.yml | 23 +++++++++++++++++++---- AMBuildScript | 18 +++++++----------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d9161b8..e923ae0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,16 +33,19 @@ jobs: - sm_version: sm1.11 sm_branch: 1.11-dev mm_branch: 1.10-dev + hl2sdk_manifest_prepare: true - sm_version: sm1.12 sm_branch: 1.12-dev mm_branch: 1.12-dev + hl2sdk_manifest_prepare: false - sm_version: smlatest sm_branch: master mm_branch: 1.12-dev + hl2sdk_manifest_prepare: false fail-fast: false - name: Build ${{ matrix.os_short }}-${{ matrix.sm_branch }} + name: Build ${{ matrix.os_short }}-${{ matrix.sm_version }} runs-on: ${{ matrix.os }} env: SDKS: '["l4d","l4d2"]' @@ -108,7 +111,19 @@ jobs: - name: Prepare Sourcemod working-directory: alliedmodders run: | - git clone --recursive https://github.com/alliedmodders/sourcemod -b ${{ matrix.sm_branch }} + git clone --recursive https://github.com/alliedmodders/sourcemod sourcemod -b ${{ matrix.sm_branch }} + + - name: Setup HL2SDK Manifest Environment + if: ${{ !matrix.hl2sdk_manifest_prepare }} + run: | + echo "HL2SDKMANIFEST=${{ github.workspace }}/alliedmodders/sourcemod/hl2sdk-manifests" >> $GITHUB_ENV + + - name: Prepare HL2SDK Manifest + working-directory: alliedmodders + if: matrix.hl2sdk_manifest_prepare + run: | + git clone https://github.com/alliedmodders/hl2sdk-manifests hl2sdk-manifests + echo "HL2SDKMANIFEST=${{ github.workspace }}/alliedmodders/hl2sdk-manifests" >> $GITHUB_ENV - name: Prepare L4D SDK working-directory: alliedmodders @@ -120,7 +135,7 @@ jobs: - name: Prepare Metamod Source working-directory: alliedmodders run: | - git clone https://github.com/alliedmodders/metamod-source mmsource-1.10 -b ${{ matrix.mm_branch }} + git clone https://github.com/alliedmodders/metamod-source metamod-source -b ${{ matrix.mm_branch }} - name: Checkout Repository uses: actions/checkout@v4 @@ -136,7 +151,7 @@ jobs: python ../configure.py \ --hl2sdk-root="${{ github.workspace }}/alliedmodders" \ --sm-path="${{ github.workspace }}/alliedmodders/sourcemod" \ - --mms-path="${{ github.workspace }}/alliedmodders/mmsource-1.10" \ + --mms-path="${{ github.workspace }}/alliedmodders/metamod-source" \ --sdks=${{ join(fromJSON(env.SDKS)) }} \ --enable-optimize \ ${{ matrix.dbgopt }} diff --git a/AMBuildScript b/AMBuildScript index 47371b1..1b7c0dd 100644 --- a/AMBuildScript +++ b/AMBuildScript @@ -33,19 +33,15 @@ def SetArchFlags(compiler): hl2sdk_manifests_path = None # First we check if the manifest exists in the current path -hl2sdk_manifests_path = os.path.join(builder.sourcePath, 'hl2sdk-manifests/SdkHelpers.ambuild') +if builder.options.hl2sdk_manifest: + hl2sdk_manifests_path = os.path.join(builder.options.hl2sdk_manifest, 'SdkHelpers.ambuild') +else: + hl2sdk_manifests_path = ResolveEnvPath('HL2SDKMANIFEST', 'hl2sdk-manifests') or os.path.join(builder.sourcePath, 'hl2sdk-manifests') + hl2sdk_manifests_path = os.path.join(hl2sdk_manifests_path, 'SdkHelpers.ambuild') if not os.path.exists(hl2sdk_manifests_path): - # manifests does not exists in the project file, use the path from --hl2sdk-manifest-path - if not builder.options.hl2sdk_manifest: - raise Exception('HL2SDK Manifest root path not set! (--hl2sdk-manifest-path)') - else: - hl2sdk_manifests_path = os.path.join(builder.options.hl2sdk_manifest, 'SdkHelpers.ambuild') - - if not os.path.exists(hl2sdk_manifests_path): - raise Exception('Could not find SdkHelpers.ambuild in the given HL2SDK Manifest path!') - - + raise Exception('Could not find SdkHelpers.ambuild in the given HL2SDK Manifest path!') + SdkHelpers = builder.Eval(hl2sdk_manifests_path, { 'Project': 'sm-extension' }) From f7874dd68e788bb7eb619d9a8cf5eb228ea6d633 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Fri, 12 Dec 2025 20:17:54 +0800 Subject: [PATCH 66/73] Fix build --- .github/workflows/build.yml | 2 ++ AMBuildScript | 2 +- extension/util.h | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e923ae0..475186a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -115,12 +115,14 @@ jobs: - name: Setup HL2SDK Manifest Environment if: ${{ !matrix.hl2sdk_manifest_prepare }} + shell: bash run: | echo "HL2SDKMANIFEST=${{ github.workspace }}/alliedmodders/sourcemod/hl2sdk-manifests" >> $GITHUB_ENV - name: Prepare HL2SDK Manifest working-directory: alliedmodders if: matrix.hl2sdk_manifest_prepare + shell: bash run: | git clone https://github.com/alliedmodders/hl2sdk-manifests hl2sdk-manifests echo "HL2SDKMANIFEST=${{ github.workspace }}/alliedmodders/hl2sdk-manifests" >> $GITHUB_ENV diff --git a/AMBuildScript b/AMBuildScript index 1b7c0dd..ec519ba 100644 --- a/AMBuildScript +++ b/AMBuildScript @@ -341,7 +341,7 @@ class ExtensionConfig(object): return binary def AddCxxCompat(self, binary): - if binary.compiler.target.platform == 'linux': + if binary.compiler.target.platform == 'linux' and os.path.exists( os.path.join(self.sm_root, 'public', 'amtl', 'compat', 'stdcxx.cpp') ): binary.sources += [ os.path.join(self.sm_root, 'public', 'amtl', 'compat', 'stdcxx.cpp'), ] diff --git a/extension/util.h b/extension/util.h index 4a2c557..ecb03d6 100644 --- a/extension/util.h +++ b/extension/util.h @@ -9,6 +9,38 @@ return false; \ } +#define DECL_DETOUR(name) \ + CDetour *name##_Detour = nullptr; + +#define CREATE_DETOUR(name, signname, var) \ + name##_Detour = DETOUR_CREATE_MEMBER(name, signname); \ + if (name##_Detour != NULL) \ + { \ + name##_Detour->EnableDetour(); \ + var &= true; \ + } else { \ + g_pSM->LogError(myself, "Failed to create " signname " detour, check error log.\n"); \ + var = false; \ + } + +#define CREATE_DETOUR_STATIC(name, signname, var) \ + name##_Detour = DETOUR_CREATE_STATIC(name, signname); \ + if (name##_Detour != NULL) \ + { \ + name##_Detour->EnableDetour(); \ + var &= true; \ + } else { \ + g_pSM->LogError(myself, "Failed to create " signname " detour, check error log.\n"); \ + var = false; \ + } + +#define DESTROY_DETOUR(name) \ + if (name##_Detour != nullptr) \ +{ \ + name##_Detour->Destroy(); \ + name##_Detour = nullptr; \ +} + class ConVarScopedSet { public: From 2d24f529986344ea08531c20e71c080ea6a3aa00 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Fri, 12 Dec 2025 22:51:28 +0800 Subject: [PATCH 67/73] Fix build with SM1.11 --- AMBuildScript | 1 + 1 file changed, 1 insertion(+) diff --git a/AMBuildScript b/AMBuildScript index ec519ba..922ffef 100644 --- a/AMBuildScript +++ b/AMBuildScript @@ -361,6 +361,7 @@ class ExtensionConfig(object): raise Exception('No suitable build of safetyhook was found.') elif os.path.exists( os.path.join(sm_public_path, 'asm') ) and os.path.exists( os.path.join(sm_public_path, 'libudis86') ): # sm1.10+ + binary.compiler.includes += [ sm_public_path ] binary.sources += [ os.path.join(sm_public_path, 'asm', 'asm.c') ] libudis_folder = os.path.join(sm_public_path, 'libudis86') if os.path.isdir(libudis_folder): From c0906e54f13278e789bd3cf3a335faafc7464480 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Fri, 12 Dec 2025 22:54:54 +0800 Subject: [PATCH 68/73] Fix build artifact name --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 475186a..ff6c0f4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -162,5 +162,5 @@ jobs: - name: Upload Binary (Package) uses: actions/upload-artifact@v4 with: - name: sendproxy-${{ matrix.os_short }}-${{ matrix.sm_branch }}-${{ env.GITHUB_SHA_SHORT }} + name: sendproxy-${{ matrix.os_short }}-${{ matrix.sm_version }}-${{ env.GITHUB_SHA_SHORT }} path: src/build/package From 75a123088f5796824476822dff76071355a76eee Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Fri, 6 Feb 2026 17:54:25 +0800 Subject: [PATCH 69/73] Restore local server support except when network backdoor is used --- extension/clientpacks_detours.cpp | 7 ++++++- extension/extension.cpp | 10 +++++----- gamedata/sendproxy.txt | 19 +++++++++++++++++++ 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/extension/clientpacks_detours.cpp b/extension/clientpacks_detours.cpp index 58e297f..c30d9f0 100644 --- a/extension/clientpacks_detours.cpp +++ b/extension/clientpacks_detours.cpp @@ -33,6 +33,8 @@ std::unordered_map g_EntityPackMap; ConVar ext_sendproxy_frame_callback("ext_sendproxy_frame_callback", "0", FCVAR_NONE, "Invoke hooked proxy every frame."); +void **g_ppLocalNetworkBackdoor = nullptr; + /*Call stack: ... 1. CGameServer::SendClientMessages //function we hooking to send props individually for each client @@ -95,7 +97,8 @@ DETOUR_DECL_STATIC3(PackEntities_Normal, void, int, iClientCount, CGameClient ** DETOUR_DECL_STATIC3(SV_ComputeClientPacks, void, int, iClientCount, CGameClient **, pClients, CFrameSnapshot *, pSnapShot) { if (playerhelpers->GetMaxClients() <= 1 - || !g_pSendPropHookManager->IsAnyEntityHooked()) + || !g_pSendPropHookManager->IsAnyEntityHooked() + || *g_ppLocalNetworkBackdoor != nullptr) { return DETOUR_STATIC_CALL(SV_ComputeClientPacks)(iClientCount, pClients, pSnapShot); } @@ -257,6 +260,8 @@ bool ClientPacksDetour::Init(IGameConfig *gc) CREATE_DETOUR(CFrameSnapshotManager_CreatePackedEntity, "CFrameSnapshotManager::CreatePackedEntity", bDetoursInited); CREATE_DETOUR_STATIC(PackEntities_Normal, "PackEntities_Normal", bDetoursInited); CREATE_DETOUR_STATIC(SV_ComputeClientPacks, "SV_ComputeClientPacks", bDetoursInited); + + GAMECONF_GETADDRESS(gc, "g_ppLocalNetworkBackdoor", &g_ppLocalNetworkBackdoor); if (!bDetoursInited) return false; diff --git a/extension/extension.cpp b/extension/extension.cpp index a0aa634..4aa0f35 100644 --- a/extension/extension.cpp +++ b/extension/extension.cpp @@ -218,11 +218,11 @@ void SendProxyManager::OnCoreMapEnd() bool SendProxyManager::SDK_OnMetamodLoad(ISmmAPI *ismm, char *error, size_t maxlen, bool late) { - if (!engine->IsDedicatedServer()) - { - ke::SafeStrcpy(error, maxlen, "Local server support is deprecated."); - return false; - } + // if (!engine->IsDedicatedServer()) + // { + // ke::SafeStrcpy(error, maxlen, "Local server support is deprecated."); + // return false; + // } GET_V_IFACE_ANY(GetEngineFactory, g_pCVar, ICvar, CVAR_INTERFACE_VERSION); gpGlobals = ismm->GetCGlobals(); diff --git a/gamedata/sendproxy.txt b/gamedata/sendproxy.txt index 26b2078..03f5b07 100644 --- a/gamedata/sendproxy.txt +++ b/gamedata/sendproxy.txt @@ -17,6 +17,19 @@ } "read" "0" } + + "g_ppLocalNetworkBackdoor" + { + "linux" + { + "signature" "g_pLocalNetworkBackdoor" + } + "windows" + { + "signature" "SV_ComputeClientPacks" + "read" "331" + } + } } "Signatures" @@ -27,6 +40,12 @@ "linux" "@framesnapshotmanager" } + "g_pLocalNetworkBackdoor" + { + "library" "engine" + "linux" "@g_pLocalNetworkBackdoor" + } + "SV_ComputeClientPacks" { "library" "engine" From 355cc5a0df8bb533ff00857c188948335e8a0c1e Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Fri, 6 Feb 2026 17:55:46 +0800 Subject: [PATCH 70/73] workflow: Remove L4D1 build to prevent confusion At the moment gamedata for L4D1 is not ready yet. --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ff6c0f4..7d70157 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -48,7 +48,7 @@ jobs: name: Build ${{ matrix.os_short }}-${{ matrix.sm_version }} runs-on: ${{ matrix.os }} env: - SDKS: '["l4d","l4d2"]' + SDKS: '["l4d2"]' steps: - name: Setup Environment From 30f0a37ee8aeb5c9f330ba94d7bb0c44a4eb9837 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Fri, 6 Feb 2026 18:01:15 +0800 Subject: [PATCH 71/73] Fix compile errors --- extension/clientpacks_detours.cpp | 4 ---- extension/extension.cpp | 3 +++ extension/extension.h | 1 + 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/extension/clientpacks_detours.cpp b/extension/clientpacks_detours.cpp index c30d9f0..522f478 100644 --- a/extension/clientpacks_detours.cpp +++ b/extension/clientpacks_detours.cpp @@ -33,8 +33,6 @@ std::unordered_map g_EntityPackMap; ConVar ext_sendproxy_frame_callback("ext_sendproxy_frame_callback", "0", FCVAR_NONE, "Invoke hooked proxy every frame."); -void **g_ppLocalNetworkBackdoor = nullptr; - /*Call stack: ... 1. CGameServer::SendClientMessages //function we hooking to send props individually for each client @@ -260,8 +258,6 @@ bool ClientPacksDetour::Init(IGameConfig *gc) CREATE_DETOUR(CFrameSnapshotManager_CreatePackedEntity, "CFrameSnapshotManager::CreatePackedEntity", bDetoursInited); CREATE_DETOUR_STATIC(PackEntities_Normal, "PackEntities_Normal", bDetoursInited); CREATE_DETOUR_STATIC(SV_ComputeClientPacks, "SV_ComputeClientPacks", bDetoursInited); - - GAMECONF_GETADDRESS(gc, "g_ppLocalNetworkBackdoor", &g_ppLocalNetworkBackdoor); if (!bDetoursInited) return false; diff --git a/extension/extension.cpp b/extension/extension.cpp index 4aa0f35..672d923 100644 --- a/extension/extension.cpp +++ b/extension/extension.cpp @@ -56,6 +56,8 @@ ICallWrapper* CFrameSnapshotManager::s_callRemoveEntityReference = nullptr; void* CFrameSnapshot::s_pfnReleaseReference = nullptr; ICallWrapper *CFrameSnapshot::s_callReleaseReference = nullptr; +void **g_ppLocalNetworkBackdoor = nullptr; + bool SendProxyManager::SDK_OnLoad(char *error, size_t maxlen, bool late) { auto gc_sdktools = *AutoGameConfig::Load("sdktools.games"); @@ -69,6 +71,7 @@ bool SendProxyManager::SDK_OnLoad(char *error, size_t maxlen, bool late) GAMECONF_GETSIGNATURE(gc, "CFrameSnapshotManager::RemoveEntityReference", &CFrameSnapshotManager::s_pfnRemoveEntityReference); GAMECONF_GETSIGNATURE(gc, "CFrameSnapshot::ReleaseReference", &CFrameSnapshot::s_pfnReleaseReference); GAMECONF_GETADDRESS(gc, "framesnapshotmanager", &framesnapshotmanager); + GAMECONF_GETADDRESS(gc, "g_ppLocalNetworkBackdoor", &g_ppLocalNetworkBackdoor); if (!ClientPacksDetour::Init(gc)) return false; diff --git a/extension/extension.h b/extension/extension.h index c0f0fd4..62d10bd 100644 --- a/extension/extension.h +++ b/extension/extension.h @@ -123,6 +123,7 @@ class SendProxyManager : extern SendProxyManager g_SendProxyManager; extern ConVar *sv_parallel_packentities; extern CFrameSnapshotManager *framesnapshotmanager; +extern void **g_ppLocalNetworkBackdoor; CBaseEntity *GetGameRulesProxyEnt(); From 48dbdefd5fd2d0281e2e58026274bbfc9813cd48 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Fri, 6 Feb 2026 19:57:37 +0800 Subject: [PATCH 72/73] Notify user when used in single player --- extension/clientpacks_detours.cpp | 10 +++++++++- extension/extension.cpp | 6 +++--- extension/sendprop_hookmanager.cpp | 4 ++-- extension/util.h | 18 +++++++++++++++--- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/extension/clientpacks_detours.cpp b/extension/clientpacks_detours.cpp index 522f478..b21665d 100644 --- a/extension/clientpacks_detours.cpp +++ b/extension/clientpacks_detours.cpp @@ -98,6 +98,14 @@ DETOUR_DECL_STATIC3(SV_ComputeClientPacks, void, int, iClientCount, CGameClient || !g_pSendPropHookManager->IsAnyEntityHooked() || *g_ppLocalNetworkBackdoor != nullptr) { + static bool isLocalBackdoorEncountered = false; + + if (!isLocalBackdoorEncountered && *g_ppLocalNetworkBackdoor != nullptr) + { + isLocalBackdoorEncountered = true; + LogError("SendProxy will NOT work in single player."); + } + return DETOUR_STATIC_CALL(SV_ComputeClientPacks)(iClientCount, pClients, pSnapShot); } @@ -277,7 +285,7 @@ void ClientPacksDetour::Shutdown() void ClientPacksDetour::Clear() { #ifdef DEBUG_SENDPROXY_MEMORY - g_pSM->LogMessage(myself, "=== PACKED ENTITIES COUNT (%d) ===", framesnapshotmanager->m_PackedEntities.Count()); + LogMessage("=== PACKED ENTITIES COUNT (%d) ===", framesnapshotmanager->m_PackedEntities.Count()); #endif g_EntityPackMap.clear(); diff --git a/extension/extension.cpp b/extension/extension.cpp index 672d923..6f5675a 100644 --- a/extension/extension.cpp +++ b/extension/extension.cpp @@ -109,19 +109,19 @@ void SendProxyManager::SDK_OnAllLoaded() CFrameSnapshot::s_callReleaseReference = bintools->CreateCall(CFrameSnapshot::s_pfnReleaseReference, CallConv_ThisCall, NULL, params, 0); if (CFrameSnapshot::s_callReleaseReference == NULL) { - smutils->LogError(myself, "Unable to create ICallWrapper for \"CFrameSnapshot::ReleaseReference\"!"); + LogError("Unable to create ICallWrapper for \"CFrameSnapshot::ReleaseReference\"!"); return; } CFrameSnapshotManager::s_callCreateEmptySnapshot = bintools->CreateCall(CFrameSnapshotManager::s_pfnCreateEmptySnapshot, CallConv_ThisCall, ¶ms[2], ¶ms[0], 2); if (CFrameSnapshotManager::s_callCreateEmptySnapshot == NULL) { - smutils->LogError(myself, "Unable to create ICallWrapper for \"CFrameSnapshotManager::CreateEmptySnapshot\"!"); + LogError("Unable to create ICallWrapper for \"CFrameSnapshotManager::CreateEmptySnapshot\"!"); return; } CFrameSnapshotManager::s_callRemoveEntityReference = bintools->CreateCall(CFrameSnapshotManager::s_pfnRemoveEntityReference, CallConv_ThisCall, NULL, ¶ms[3], 1); if (CFrameSnapshotManager::s_callRemoveEntityReference == NULL) { - smutils->LogError(myself, "Unable to create ICallWrapper for \"CFrameSnapshotManager::RemoveEntityReference\"!"); + LogError("Unable to create ICallWrapper for \"CFrameSnapshotManager::RemoveEntityReference\"!"); return; } } diff --git a/extension/sendprop_hookmanager.cpp b/extension/sendprop_hookmanager.cpp index c49e554..feec2f4 100644 --- a/extension/sendprop_hookmanager.cpp +++ b/extension/sendprop_hookmanager.cpp @@ -203,7 +203,7 @@ void GlobalProxy(const SendProp *pProp, const void *pStructBase, const void * pD Assert(pHook != nullptr); if (!pHook) { - g_pSM->LogError(myself, "FATAL: Leftover entity proxy %s", pProp->GetName()); + LogError("FATAL: Leftover entity proxy %s", pProp->GetName()); return; } @@ -253,7 +253,7 @@ void GlobalProxy(const SendProp *pProp, const void *pStructBase, const void * pD } else if (hook.type == PropType::Prop_EHandle) { pEntHook->data = *reinterpret_cast(pData); } else { - g_pSM->LogError(myself, "%s: SendProxy report: Unknown prop type (%s).", __func__, pProp->GetName()); + LogError("%s: SendProxy report: Unknown prop type (%s).", __func__, pProp->GetName()); continue; } diff --git a/extension/util.h b/extension/util.h index ecb03d6..868bfda 100644 --- a/extension/util.h +++ b/extension/util.h @@ -19,7 +19,7 @@ name##_Detour->EnableDetour(); \ var &= true; \ } else { \ - g_pSM->LogError(myself, "Failed to create " signname " detour, check error log.\n"); \ + LogError("Failed to create " signname " detour, check error log.\n"); \ var = false; \ } @@ -30,7 +30,7 @@ name##_Detour->EnableDetour(); \ var &= true; \ } else { \ - g_pSM->LogError(myself, "Failed to create " signname " detour, check error log.\n"); \ + LogError("Failed to create " signname " detour, check error log.\n"); \ var = false; \ } @@ -41,6 +41,18 @@ name##_Detour = nullptr; \ } +template +inline void LogMessage(const char *format, Args... args) +{ + g_pSM->LogMessage(myself, format, args...); +} + +template +inline void LogError(const char *format, Args... args) +{ + g_pSM->LogError(myself, format, args...); +} + class ConVarScopedSet { public: @@ -72,7 +84,7 @@ class AutoGameConfig IGameConfig *gc; if (!gameconfs->LoadGameConfigFile(name, &gc, buffer, sizeof(buffer))) { - smutils->LogError(myself, "Could not read config file (%s) (%s)", name, buffer); + LogError("Could not read config file (%s) (%s)", name, buffer); return {}; } return AutoGameConfig(gc); From 9a173150fb38a4b5fd03de37a55c862a2054f9a7 Mon Sep 17 00:00:00 2001 From: Forgetest <33988868+jensewe@users.noreply.github.com> Date: Fri, 6 Feb 2026 19:58:30 +0800 Subject: [PATCH 73/73] .gitignore: Add safetyhook folder --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6a4ed91..3e85166 100644 --- a/.gitignore +++ b/.gitignore @@ -34,4 +34,5 @@ .vscode/*.* extension/build/* -build/* \ No newline at end of file +build/* +safetyhook/* \ No newline at end of file