/**
* 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$
*/
#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"
//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>
#define DECL_DETOUR(name) \
CDetour *name##_Detour = nullptr;
#define CREATE_DETOUR(name, signname, var) \
if (name##_Detour == NULL) \
{ \
name##_Detour = DETOUR_CREATE_MEMBER(name, signname); \
if(name##_Detour) { \
name##_Detour->EnableDetour(); \
var = true; \
} else { \
g_pSM->LogError(myself, "Failed to create " signname " detour, check error log.\n"); \
var = false; \
} \
} else { \
name##_Detour->EnableDetour(); \
var = true; \
}
#define CREATE_DETOUR_STATIC(name, signname, var) \
if (name##_Detour == NULL) \
{ \
name##_Detour = DETOUR_CREATE_STATIC(name, signname); \
if(name##_Detour) { \
name##_Detour->EnableDetour(); \
var = true; \
} else { \
g_pSM->LogError(myself, "Failed to create " signname " detour, check error log.\n"); \
var = false; \
} \
} else { \
name##_Detour->EnableDetour(); \
var = true; \
}
#define DISABLE_DETOUR(name) \
if (name##_Detour != nullptr) \
{ \
name##_Detour->DisableDetour(); \
}
#define DESTROY_DETOUR(name) \
if (name##_Detour != nullptr) \
{ \
name##_Detour->Destroy(); \
name##_Detour = nullptr; \
}
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(SV_ComputeClientPacks);
class CGameClient;
class CFrameSnapshot;
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 = true;
IServer * g_pIServer = nullptr;
SendProxyManager g_SendProxyManager;
SendProxyManagerInterfaceImpl * g_pMyInterface = nullptr;
SMEXT_LINK(&g_SendProxyManager);
CThreadFastMutex g_WorkMutex;
#include
std::vector g_Hooks;
std::vector g_HooksGamerules;
std::vector g_ChangeHooks;
std::vector g_ChangeHooksGamerules;
std::vector g_vHookedEdicts;
IServerGameEnts * gameents = nullptr;
IServerGameClients * gameclients = nullptr;
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;
void * g_pGameRules = nullptr;
bool g_bShouldChangeGameRulesState = false;
bool g_bSendSnapshots = false;
size_t g_numPerClientHooks = 0;
CGlobalVars * g_pGlobals = nullptr;
static CBaseEntity * FindEntityByServerClassname(int, const char *);
static void CallChangeCallbacks(const PropChangeHook &pInfo, void * pOldValue, void * pNewValue);
static void CallChangeGamerulesCallbacks(const PropChangeHookGamerules &pInfo, void * pOldValue, void * pNewValue);
const char * g_szGameRulesProxy;
//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
*/
DETOUR_DECL_MEMBER1(CGameServer_SendClientMessages, void, bool, bSendSnapshots)
{
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();
#endif
SH_ADD_HOOK(IServer, GetClientCount, g_pIServer, SH_MEMBER(&g_SendProxyManager, &SendProxyManager::GetClientCount), false);
g_bFirstTimeCalled = false;
}
bool bCalledForNullIClientsThisTime = false;
for (int iClients = 1; iClients <= playerhelpers->GetMaxClients(); iClients++)
{
IGamePlayer * pPlayer = playerhelpers->GetGamePlayer(iClients);
bool bFake = (pPlayer->IsFakeClient() && !(pPlayer->IsSourceTV()
#if SOURCE_ENGINE == SE_TF2
|| 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)
{
if (!bCalledForNullIClientsThisTime && !g_bCallingForNullClients)
{
g_bCurrentGameClientCallFwd = false;
g_bCallingForNullClients = true;
DETOUR_MEMBER_CALL(CGameServer_SendClientMessages)(true);
g_bCallingForNullClients = false;
}
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 (auto pEdict : g_vHookedEdicts)
{
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);
}
#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
__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 DoOnEntityDestroyed(int idx)
{
auto it = g_Hooks.begin();
while(it != g_Hooks.end())
{
auto &hook = *it;
if (hook.objectID == idx)
{
g_SendProxyManager.UnhookProxy(hook);
g_Hooks.erase(it);
continue;
}
++it;
}
auto it2 = g_ChangeHooks.begin();
while(it2 != g_ChangeHooks.end())
{
if (it2->objectID == idx) {
g_ChangeHooks.erase(it2);
continue;
}
++it2;
}
}
void SendProxyManager::OnEntityDestroyed(CBaseEntity* pEnt)
{
int idx = gamehelpers->EntityToBCompatRef(pEnt);
DoOnEntityDestroyed(idx);
}
void Hook_ClientDisconnect(edict_t * pEnt)
{
int idx = gamehelpers->IndexOfEdict(pEnt);
DoOnEntityDestroyed(idx);
RETURN_META(MRES_IGNORED);
}
void Hook_GameFrame(bool simulating)
{
if (simulating)
{
for (auto &it : g_ChangeHooks)
{
switch(it.propType)
{
case PropType::Prop_Int:
{
edict_t * pEnt = gamehelpers->EdictOfIndex(it.objectID);
CBaseEntity * pEntity = gameents->EdictToBaseEntity(pEnt);
int iCurrent = *(int *)((unsigned char *)pEntity + it.Offset);
if (iCurrent != it.iLastValue)
{
CallChangeCallbacks(it, (void *)&it.iLastValue, (void *)&iCurrent);
it.iLastValue = iCurrent;
}
break;
}
case PropType::Prop_Float:
{
edict_t * pEnt = gamehelpers->EdictOfIndex(it.objectID);
CBaseEntity * pEntity = gameents->EdictToBaseEntity(pEnt);
float flCurrent = *(float *)((unsigned char *)pEntity + it.Offset);
if (flCurrent != it.flLastValue)
{
CallChangeCallbacks(it, (void *)&it.flLastValue, (void *)&flCurrent);
it.flLastValue = flCurrent;
}
break;
}
case PropType::Prop_String:
{
edict_t * pEnt = gamehelpers->EdictOfIndex(it.objectID);
CBaseEntity * pEntity = gameents->EdictToBaseEntity(pEnt);
const char * szCurrent = (const char *)((unsigned char *)pEntity + it.Offset);
if (strcmp(szCurrent, it.cLastValue) != 0)
{
CallChangeCallbacks(it, (void *)it.cLastValue, (void *)szCurrent);
memset(it.cLastValue, 0, sizeof(it.cLastValue));
strncpynull(it.cLastValue, szCurrent, sizeof(it.cLastValue));
}
break;
}
case PropType::Prop_Vector:
{
edict_t * pEnt = gamehelpers->EdictOfIndex(it.objectID);
CBaseEntity * pEntity = gameents->EdictToBaseEntity(pEnt);
Vector * pVec = (Vector *)((unsigned char *)pEntity + it.Offset);
if (it.vecLastValue != *pVec)
{
CallChangeCallbacks(it, (void *)&it.vecLastValue, (void *)pVec);
it.vecLastValue = *pVec;
}
break;
}
default: rootconsole->ConsolePrint("%s: SendProxy report: Unknown prop type (%s).", __func__, it.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 (auto &it : g_ChangeHooksGamerules)
{
switch(it.propType)
{
case PropType::Prop_Int:
{
int iCurrent = *(int *)((unsigned char *)g_pGameRules + it.Offset);
if (iCurrent != it.iLastValue)
{
CallChangeGamerulesCallbacks(it, (void *)&it.iLastValue, (void *)&iCurrent);
it.iLastValue = iCurrent;
}
break;
}
case PropType::Prop_Float:
{
float flCurrent = *(float *)((unsigned char *)g_pGameRules + it.Offset);
if (flCurrent != it.flLastValue)
{
CallChangeGamerulesCallbacks(it, (void *)&it.flLastValue, (void *)&flCurrent);
it.flLastValue = flCurrent;
}
break;
}
case PropType::Prop_String:
{
const char * szCurrent = (const char *)((unsigned char *)g_pGameRules + it.Offset);
if (strcmp(szCurrent, it.cLastValue) != 0)
{
CallChangeGamerulesCallbacks(it, (void *)it.cLastValue, (void *)szCurrent);
memset(it.cLastValue, 0, sizeof(it.cLastValue));
strncpynull(it.cLastValue, szCurrent, sizeof(it.cLastValue));
}
break;
}
case PropType::Prop_Vector:
{
Vector * pVec = (Vector *)((unsigned char *)g_pGameRules + it.Offset);
if (it.vecLastValue != *pVec)
{
CallChangeGamerulesCallbacks(it, (void *)&it.vecLastValue, (void *)pVec);
it.vecLastValue = *pVec;
}
break;
}
default: rootconsole->ConsolePrint("%s: SendProxy report: Unknown prop type (%s).", __func__, it.pVar->GetName());
}
}
}
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)
{
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);
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;
}
CDetourManager::Init(smutils->GetScriptingEngine(), g_pGameConf);
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);
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, "sendproxy14");
plsys->AddPluginsListener(this);
return true;
}
void SendProxyManager::SDK_OnAllLoaded()
{
sharesys->AddNatives(myself, g_MyNatives);
SM_GET_LATE_IFACE(SDKTOOLS, g_pSDKTools);
SM_GET_LATE_IFACE(SDKHOOKS, g_pSDKHooks);
if (g_pSDKHooks)
{
g_pSDKHooks->AddEntityListener(this);
}
}
void SendProxyManager::SDK_OnUnload()
{
for (auto &it : g_Hooks)
{
it.pVar->SetProxyFn(it.pRealProxy);
}
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(SV_ComputeClientPacks);
gameconfs->CloseGameConfigFile(g_pGameConf);
gameconfs->CloseGameConfigFile(g_pGameConfSDKTools);
plsys->RemovePluginsListener(this);
if( g_pSDKHooks )
{
g_pSDKHooks->RemoveEntityListener(this);
}
delete g_pMyInterface;
}
void SendProxyManager::OnCoreMapEnd()
{
for (auto &it : g_HooksGamerules)
{
UnhookProxyGamerules(it);
}
g_HooksGamerules.clear();
g_pGameRulesProxyEdict = nullptr;
if(g_numPerClientHooks > 0) {
DISABLE_DETOUR(CGameServer_SendClientMessages);
DISABLE_DETOUR(CGameClient_ShouldSendMessages);
DISABLE_DETOUR(SV_ComputeClientPacks);
g_iCurrentClientIndexInLoop = -1;
g_bSVComputePacksDone = true;
}
}
void SendProxyManager::OnCoreMapStart(edict_t * pEdictList, int edictCount, int clientMax)
{
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_numPerClientHooks > 0) {
bool bDetoursInited = false;
g_bSVComputePacksDone = false;
g_iCurrentClientIndexInLoop = -1;
CREATE_DETOUR(CGameServer_SendClientMessages, "CGameServer::SendClientMessages", bDetoursInited);
CREATE_DETOUR(CGameClient_ShouldSendMessages, "CGameClient::ShouldSendMessages", bDetoursInited);
CREATE_DETOUR_STATIC(SV_ComputeClientPacks, "SV_ComputeClientPacks", bDetoursInited);
}
}
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);
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);
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();
auto it = g_Hooks.begin();
while(it != g_Hooks.end())
{
auto &hook = *it;
if (hook.sCallbackInfo.iCallbackType == CallBackType::Callback_PluginFunction &&
((IPluginFunction *)hook.sCallbackInfo.pCallback)->GetParentContext() == pCtx)
{
UnhookProxy(hook);
g_Hooks.erase(it);
continue;
}
++it;
}
auto it2 = g_HooksGamerules.begin();
while(it2 != g_HooksGamerules.end())
{
auto &hook = *it2;
if (hook.sCallbackInfo.iCallbackType == CallBackType::Callback_PluginFunction &&
((IPluginFunction *)hook.sCallbackInfo.pCallback)->GetParentContext() == pCtx)
{
UnhookProxyGamerules(hook);
g_HooksGamerules.erase(it2);
continue;
}
++it2;
}
auto it3 = g_ChangeHooks.begin();
while(it3 != g_ChangeHooks.end())
{
auto &pCallbacks = it3->vCallbacksInfo;
auto it4 = pCallbacks.begin();
while(it4 != pCallbacks.end()) {
auto &cb = *it4;
if (cb.iCallbackType == CallBackType::Callback_PluginFunction &&
(IPluginContext *)cb.pOwner == pCtx)
{
pCallbacks.erase(it4);
continue;
}
++it4;
}
//else do not needed here
if (pCallbacks.empty()) {
g_ChangeHooks.erase(it3);
continue;
}
++it3;
}
auto it5 = g_ChangeHooksGamerules.begin();
while(it5 != g_ChangeHooksGamerules.end())
{
auto &pCallbacks = it5->vCallbacksInfo;
auto it6 = pCallbacks.begin();
while(it6 != pCallbacks.end()) {
auto &cb = *it6;
if (cb.iCallbackType == CallBackType::Callback_PluginFunction &&
(IPluginContext *)cb.pOwner == pCtx)
{
pCallbacks.erase(it6);
continue;
}
++it6;
}
//else do not needed here
if (pCallbacks.empty()) {
g_ChangeHooksGamerules.erase(it5);
continue;
}
++it5;
}
}
//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 (auto &it : g_Hooks)
{
if (it.objectID == hook.objectID)
{
//we don't care anymore
//if (g_Hooks[i].pVar == hook.pVar)
// return false;
//else
bEdictHooked = true;
}
}
if(hook.per_client) {
if(++g_numPerClientHooks == 1) {
bool bDetoursInited = false;
g_bSVComputePacksDone = false;
g_iCurrentClientIndexInLoop = -1;
CREATE_DETOUR(CGameServer_SendClientMessages, "CGameServer::SendClientMessages", bDetoursInited);
CREATE_DETOUR(CGameClient_ShouldSendMessages, "CGameClient::ShouldSendMessages", bDetoursInited);
CREATE_DETOUR_STATIC(SV_ComputeClientPacks, "SV_ComputeClientPacks", bDetoursInited);
}
}
g_Hooks.emplace_back(std::move(hook));
if (!bEdictHooked) {
g_vHookedEdicts.emplace_back(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.size(); i++)
{
if (g_HooksGamerules[i].pVar == hook.pVar)
return false;
}*/
if(hook.per_client) {
if(++g_numPerClientHooks == 1) {
bool bDetoursInited = false;
g_bSVComputePacksDone = false;
g_iCurrentClientIndexInLoop = -1;
CREATE_DETOUR(CGameServer_SendClientMessages, "CGameServer::SendClientMessages", bDetoursInited);
CREATE_DETOUR(CGameClient_ShouldSendMessages, "CGameClient::ShouldSendMessages", bDetoursInited);
CREATE_DETOUR_STATIC(SV_ComputeClientPacks, "SV_ComputeClientPacks", bDetoursInited);
}
}
g_HooksGamerules.emplace_back(std::move(hook));
return true;
}
bool SendProxyManager::AddChangeHookToList(PropChangeHook &&sHook, CallBackInfo &&pInfo)
{
PropChangeHook *pHookInfo = nullptr;
for (auto &it : g_ChangeHooks)
{
if (it.pVar == sHook.pVar)
{
pHookInfo = ⁢
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.emplace_back(std::move(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;
}
sHook.vCallbacksInfo.emplace_back(std::move(pInfo));
g_ChangeHooks.emplace_back(std::move(sHook));
}
return true;
}
bool SendProxyManager::AddChangeHookToListGamerules(PropChangeHookGamerules &&sHook, CallBackInfo &&pInfo)
{
PropChangeHookGamerules *pHookInfo = nullptr;
for (auto &it : g_ChangeHooksGamerules)
{
if (it.pVar == sHook.pVar)
{
pHookInfo = ⁢
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.emplace_back(std::move(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;
}
sHook.vCallbacksInfo.emplace_back(std::move(pInfo));
g_ChangeHooksGamerules.emplace_back(std::move(sHook));
}
return true;
}
bool SendProxyManager::UnhookProxy(const SendPropHook &hook)
{
if(hook.per_client) {
if(--g_numPerClientHooks == 0) {
DISABLE_DETOUR(CGameServer_SendClientMessages);
DISABLE_DETOUR(CGameClient_ShouldSendMessages);
DISABLE_DETOUR(SV_ComputeClientPacks);
g_iCurrentClientIndexInLoop = -1;
g_bSVComputePacksDone = true;
}
}
//if there are other hooks for this prop, don't change the proxy, just remove it from our list
auto it2 = g_Hooks.begin();
while(it2 != g_Hooks.end()) {
if (it2->pVar == hook.pVar &&
&*it2 != &hook)
{
CallListenersForHook(hook);
return true;
}
++it2;
}
auto it = g_vHookedEdicts.begin();
while(it != g_vHookedEdicts.end()) {
if(*it == hook.pEnt) {
g_vHookedEdicts.erase(it);
break;
}
++it;
}
CallListenersForHook(hook);
hook.pVar->SetProxyFn(hook.pRealProxy);
return false;
}
bool SendProxyManager::UnhookProxyGamerules(const SendPropHookGamerules &hook)
{
if(hook.per_client) {
if(--g_numPerClientHooks == 0) {
DISABLE_DETOUR(CGameServer_SendClientMessages);
DISABLE_DETOUR(CGameClient_ShouldSendMessages);
DISABLE_DETOUR(SV_ComputeClientPacks);
g_iCurrentClientIndexInLoop = -1;
g_bSVComputePacksDone = true;
}
}
//if there are other hooks for this prop, don't change the proxy, just remove it from our list
auto it = g_HooksGamerules.begin();
while(it != g_HooksGamerules.end())
{
if (it->pVar == hook.pVar &&
&*it != &hook)
{
CallListenersForHookGamerules(hook);
return true;
}
++it;
}
CallListenersForHookGamerules(hook);
hook.pVar->SetProxyFn(hook.pRealProxy);
return false;
}
bool SendProxyManager::UnhookChange(PropChangeHook &hook, const CallBackInfo &pInfo)
{
auto &pCallbacks = hook.vCallbacksInfo;
auto it2 = pCallbacks.begin();
while(it2 != pCallbacks.end()) {
auto &cb = *it2;
if (cb.iCallbackType == pInfo.iCallbackType &&
cb.pCallback == (void *)pInfo.pCallback)
{
pCallbacks.erase(it2);
continue;
}
++it2;
}
//if there no any callbacks anymore, then remove all info about this hook
if (pCallbacks.empty()) {
return true;
}
return false;
}
bool SendProxyManager::UnhookChangeGamerules(PropChangeHookGamerules &hook, const CallBackInfo &pInfo)
{
auto &pCallbacks = hook.vCallbacksInfo;
auto it2 = pCallbacks.begin();
while(it2 != pCallbacks.end()) {
auto &cb = *it2;
if (cb.iCallbackType == pInfo.iCallbackType &&
cb.pCallback == (void *)pInfo.pCallback)
{
pCallbacks.erase(it2);
continue;
}
++it2;
}
//if there no any callbacks anymore, then remove all info about this hook
if (pCallbacks.empty()) {
return true;
}
return false;
}
//callbacks
//Change
void CallChangeCallbacks(const PropChangeHook &pInfo, void * pOldValue, void * pNewValue)
{
for (auto &sCallback : pInfo.vCallbacksInfo)
{
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[0]);
vector[1][1] = sp_ftoc(pInfo.vecLastValue[1]);
vector[1][2] = sp_ftoc(pInfo.vecLastValue[2]);
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;
}
}
}
}
void CallChangeGamerulesCallbacks(const PropChangeHookGamerules &pInfo, void * pOldValue, void * pNewValue)
{
for (auto &sCallback : pInfo.vCallbacksInfo)
{
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[0]);
vector[1][1] = sp_ftoc(pInfo.vecLastValue[1]);
vector[1][2] = sp_ftoc(pInfo.vecLastValue[2]);
pCallBack->PushString(pInfo.pVar->GetName());
pCallBack->PushArray(vector[1], 3);
pCallBack->PushArray(vector[0], 3);
pCallBack->PushCell(pInfo.Element);
pCallBack->Execute(0);
break;
}
}
break;
}
}
}
}
//Proxy
bool CallInt(const SendPropHook &hook, int &ret)
{
if (!g_bSVComputePacksDone)
return false;
AUTO_LOCK_FM(g_WorkMutex);
if(hook.per_client && g_iCurrentClientIndexInLoop == -1) {
return false;
}
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(hook.Element);
if(hook.per_client) {
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 = false;
if(hook.per_client) {
bChange = pCallbacks->OnEntityPropProxyFunctionCalls(gameents->EdictToBaseEntity(hook.pEnt), hook.pVar, (CBasePlayer *)gamehelpers->ReferenceToEntity(g_iCurrentClientIndexInLoop + 1), (void *)&iValue, hook.propType, hook.Element);
} else {
bChange = pCallbacks->OnEntityPropProxyFunctionCalls(gameents->EdictToBaseEntity(hook.pEnt), hook.pVar, (void *)&iValue, hook.propType, hook.Element);
}
if (bChange)
{
ret = iValue;
return true;
}
break;
}
}
return false;
}
bool CallIntGamerules(const SendPropHookGamerules &hook, int &ret)
{
if (!g_bSVComputePacksDone)
return false;
AUTO_LOCK_FM(g_WorkMutex);
if(hook.per_client && g_iCurrentClientIndexInLoop == -1) {
return false;
}
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(hook.Element);
if(hook.per_client) {
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 = false;
if(hook.per_client) {
bChange = pCallbacks->OnGamerulesPropProxyFunctionCalls(hook.pVar, (CBasePlayer *)gamehelpers->ReferenceToEntity(g_iCurrentClientIndexInLoop + 1), (void *)&iValue, hook.propType, hook.Element);
} else {
bChange = pCallbacks->OnGamerulesPropProxyFunctionCalls(hook.pVar, (void *)&iValue, hook.propType, hook.Element);
}
if (bChange)
{
ret = iValue;
return true;
}
break;
}
}
return false;
}
bool CallFloat(const SendPropHook &hook, float &ret)
{
if (!g_bSVComputePacksDone)
return false;
AUTO_LOCK_FM(g_WorkMutex);
if(hook.per_client && g_iCurrentClientIndexInLoop == -1) {
return false;
}
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(hook.Element);
if(hook.per_client) {
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 = false;
if(hook.per_client) {
bChange = pCallbacks->OnEntityPropProxyFunctionCalls(gameents->EdictToBaseEntity(hook.pEnt), hook.pVar, (CBasePlayer *)gamehelpers->ReferenceToEntity(g_iCurrentClientIndexInLoop + 1), (void *)&flValue, hook.propType, hook.Element);
} else {
bChange = pCallbacks->OnEntityPropProxyFunctionCalls(gameents->EdictToBaseEntity(hook.pEnt), hook.pVar, (void *)&flValue, hook.propType, hook.Element);
}
if (bChange)
{
ret = flValue;
return true;
}
break;
}
}
return false;
}
bool CallFloatGamerules(const SendPropHookGamerules &hook, float &ret)
{
if (!g_bSVComputePacksDone)
return false;
AUTO_LOCK_FM(g_WorkMutex);
if(hook.per_client && g_iCurrentClientIndexInLoop == -1) {
return false;
}
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(hook.Element);
if(hook.per_client) {
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 = false;
if(hook.per_client) {
bChange = pCallbacks->OnGamerulesPropProxyFunctionCalls(hook.pVar, (CBasePlayer *)gamehelpers->ReferenceToEntity(g_iCurrentClientIndexInLoop + 1), (void *)&flValue, hook.propType, hook.Element);
} else {
bChange = pCallbacks->OnGamerulesPropProxyFunctionCalls(hook.pVar, (void *)&flValue, hook.propType, hook.Element);
}
if (bChange)
{
ret = flValue;
return true;
}
break;
}
}
return false;
}
bool CallString(const SendPropHook &hook, char *&ret)
{
if (!g_bSVComputePacksDone)
return false;
AUTO_LOCK_FM(g_WorkMutex);
if(hook.per_client && g_iCurrentClientIndexInLoop == -1) {
return false;
}
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(hook.Element);
if(hook.per_client) {
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 = false;
if(hook.per_client) {
bChange = pCallbacks->OnEntityPropProxyFunctionCalls(gameents->EdictToBaseEntity(hook.pEnt), hook.pVar, (CBasePlayer *)gamehelpers->ReferenceToEntity(g_iCurrentClientIndexInLoop + 1), (void *)value, hook.propType, hook.Element);
} else {
bChange = pCallbacks->OnEntityPropProxyFunctionCalls(gameents->EdictToBaseEntity(hook.pEnt), hook.pVar, (void *)value, hook.propType, hook.Element);
}
if (bChange)
{
ret = value;
return true;
}
break;
}
}
return false;
}
bool CallStringGamerules(const SendPropHookGamerules &hook, char *&ret)
{
if (!g_bSVComputePacksDone)
return false;
AUTO_LOCK_FM(g_WorkMutex);
if(hook.per_client && g_iCurrentClientIndexInLoop == -1) {
return false;
}
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(hook.Element);
if(hook.per_client) {
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 = false;
if(hook.per_client) {
bChange = pCallbacks->OnGamerulesPropProxyFunctionCalls(hook.pVar, (CBasePlayer *)gamehelpers->ReferenceToEntity(g_iCurrentClientIndexInLoop + 1), (void *)value, hook.propType, hook.Element);
} else {
bChange = pCallbacks->OnGamerulesPropProxyFunctionCalls(hook.pVar, (void *)value, hook.propType, hook.Element);
}
if (bChange)
{
ret = value;
return true;
}
break;
}
}
return false;
}
bool CallVector(const SendPropHook &hook, Vector &vec)
{
if (!g_bSVComputePacksDone)
return false;
AUTO_LOCK_FM(g_WorkMutex);
if(hook.per_client && g_iCurrentClientIndexInLoop == -1) {
return false;
}
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(hook.Element);
if(hook.per_client) {
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 = false;
if(hook.per_client) {
bChange = pCallbacks->OnGamerulesPropProxyFunctionCalls(hook.pVar, (CBasePlayer *)gamehelpers->ReferenceToEntity(g_iCurrentClientIndexInLoop + 1), (void *)&vNewVec, hook.propType, hook.Element);
} else {
bChange = pCallbacks->OnGamerulesPropProxyFunctionCalls(hook.pVar, (void *)&vNewVec, hook.propType, hook.Element);
}
if (bChange)
{
vec.x = vNewVec.x;
vec.y = vNewVec.y;
vec.z = vNewVec.z;
return true;
}
break;
}
}
return false;
}
bool CallVectorGamerules(const SendPropHookGamerules &hook, Vector &vec)
{
if (!g_bSVComputePacksDone)
return false;
AUTO_LOCK_FM(g_WorkMutex);
if(hook.per_client && g_iCurrentClientIndexInLoop == -1) {
return false;
}
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->PushString(hook.pVar->GetName());
callback->PushArray(vector, 3, SM_PARAM_COPYBACK);
callback->PushCell(hook.Element);
if(hook.per_client) {
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 = false;
if(hook.per_client) {
bChange = pCallbacks->OnGamerulesPropProxyFunctionCalls(hook.pVar, (CBasePlayer *)gamehelpers->ReferenceToEntity(g_iCurrentClientIndexInLoop + 1), (void *)&vNewVec, hook.propType, hook.Element);
} else {
bChange = pCallbacks->OnGamerulesPropProxyFunctionCalls(hook.pVar, (void *)&vNewVec, hook.propType, hook.Element);
}
if (bChange)
{
vec.x = vNewVec.x;
vec.y = vNewVec.y;
vec.z = vNewVec.z;
return true;
}
break;
}
}
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 (auto &it : g_Hooks)
{
if (it.objectID == objectID &&
it.pVar == pProp &&
pEnt == it.pEnt)
{
switch (it.propType)
{
case PropType::Prop_Int:
{
int result = *(int *)pData;
if (CallInt(it, result))
{
long data = result;
it.pRealProxy(pProp, pStructBase, &data, pOut, iElement, objectID);
return; // If somebody already handled this call, do not call other hooks for this entity & prop
}
else
{
it.pRealProxy(pProp, pStructBase, pData, pOut, iElement, objectID);
}
bHandled = true;
continue;
}
case PropType::Prop_Float:
{
float result = *(float *)pData;
if (CallFloat(it, result))
{
it.pRealProxy(pProp, pStructBase, &result, pOut, iElement, objectID);
return; // If somebody already handled this call, do not call other hooks for this entity & prop
}
else
{
it.pRealProxy(pProp, pStructBase, pData, pOut, iElement, objectID);
}
bHandled = true;
continue;
}
case PropType::Prop_String:
{
char * result = *(char **)pData;
if (CallString(it, result))
{
it.pRealProxy(pProp, pStructBase, &result, pOut, iElement, objectID);
return; // If somebody already handled this call, do not call other hooks for this entity & prop
}
else
{
it.pRealProxy(pProp, pStructBase, pData, pOut, iElement, objectID);
}
bHandled = true;
continue;
}
case PropType::Prop_Vector:
{
Vector result = *(Vector *)pData;
if (CallVector(it, result))
{
it.pRealProxy(pProp, pStructBase, &result, pOut, iElement, objectID);
return; // If somebody already handled this call, do not call other hooks for this entity & prop
}
else
{
it.pRealProxy(pProp, pStructBase, pData, pOut, iElement, objectID);
}
bHandled = true;
continue;
}
default: rootconsole->ConsolePrint("%s: SendProxy report: Unknown prop type (%s).", __func__, it.pVar->GetName());
}
}
}
if (!bHandled)
{
//perhaps we aren't hooked, but we can still find the real proxy for this prop
for (auto &it : g_Hooks)
{
if (it.pVar == pProp)
{
it.pRealProxy(pProp, pStructBase, pData, pOut, iElement, objectID);
return;
}
}
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)
{
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 (auto &it : g_HooksGamerules)
{
if (it.pVar == pProp)
{
switch (it.propType)
{
case PropType::Prop_Int:
{
int result = *(int *)pData;
if (CallIntGamerules(it, result))
{
long data = result;
it.pRealProxy(pProp, pStructBase, &data, pOut, iElement, objectID);
return; // If somebody already handled this call, do not call other hooks for this entity & prop
}
else
{
it.pRealProxy(pProp, pStructBase, pData, pOut, iElement, objectID);
}
bHandled = true;
continue;
}
case PropType::Prop_Float:
{
float result = *(float *)pData;
if (CallFloatGamerules(it, result))
{
it.pRealProxy(pProp, pStructBase, &result, pOut, iElement, objectID);
return; // If somebody already handled this call, do not call other hooks for this entity & prop
}
else
{
it.pRealProxy(pProp, pStructBase, pData, pOut, iElement, objectID);
}
bHandled = true;
continue;
}
case PropType::Prop_String:
{
char * result = *(char **)pData; //We need to use const because of C++11 restriction
if (CallStringGamerules(it, result))
{
it.pRealProxy(pProp, pStructBase, &result, pOut, iElement, objectID);
return; // If somebody already handled this call, do not call other hooks for this entity & prop
}
else
{
it.pRealProxy(pProp, pStructBase, pData, pOut, iElement, objectID);
}
bHandled = true;
continue;
}
case PropType::Prop_Vector:
{
Vector result = *(Vector *)pData;
if (CallVectorGamerules(it, result))
{
it.pRealProxy(pProp, pStructBase, &result, pOut, iElement, objectID);
return; // If somebody already handled this call, do not call other hooks for this entity & prop
}
else
{
it.pRealProxy(pProp, pStructBase, pData, pOut, iElement, objectID);
}
bHandled = true;
continue;
}
default: rootconsole->ConsolePrint("%s: SendProxy report: Unknown prop type (%s).", __func__, it.pVar->GetName());
}
}
}
if (!bHandled)
{
//perhaps we aren't hooked, but we can still find the real proxy for this prop
for (auto &it : g_HooksGamerules)
{
if (it.pVar == pProp)
{
it.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)
{
switch (iType)
{
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;
}
}
return false;
}
char * strncpynull(char * pDestination, const char * pSource, size_t szCount)
{
if(!pSource) {
pSource = "";
}
strncpy(pDestination, pSource, szCount);
pDestination[szCount - 1] = 0;
return pDestination;
}