245 lines
7.1 KiB
C++
245 lines
7.1 KiB
C++
#include "detour.h"
|
|
|
|
#include "common.h"
|
|
#include "gameconfig.h"
|
|
#include "networksystem/inetworkserializer.h"
|
|
#include "overrides.h"
|
|
#include "playerslot.h"
|
|
|
|
#include <cstring>
|
|
|
|
extern CGameConfig* g_gameConfig;
|
|
extern bool g_enabled;
|
|
extern bool g_armed;
|
|
|
|
using SendSnapshotFn = void (*)(void*, void*);
|
|
using PackEntitiesFn = void (*)(void*, void*, int, void*, void*, void*, void*);
|
|
using PackEntityFn = void (*)(void*, void*, int, void*, void*, void*, void*, void*, void*, void*);
|
|
static SendSnapshotFn g_sendSnapshot = nullptr;
|
|
static PackEntitiesFn g_packEntities = nullptr;
|
|
static PackEntityFn g_packEntity = nullptr;
|
|
static funchook_t* g_snapshotHook = nullptr;
|
|
static funchook_t* g_packHook = nullptr;
|
|
static funchook_t* g_packEntityHook = nullptr;
|
|
static uint64_t g_snapshotCalls = 0;
|
|
static uint64_t g_packCalls = 0;
|
|
static uint64_t g_packEntityCalls = 0;
|
|
static uint64_t g_packEntityGenericSpoofs = 0;
|
|
static uint64_t g_fullUpdateWrites = 0;
|
|
static uint64_t g_packEntitiesDebugLogs = 0;
|
|
static uint64_t g_packEntityRuleDebugLogs = 0;
|
|
static int g_fullUpdateBudget = 0;
|
|
static thread_local CPlayerSlot g_currentRecipient(-1);
|
|
static thread_local CPlayerSlot g_lastSnapshotRecipient(-1);
|
|
|
|
static CPlayerSlot SlotFromClient(void* client)
|
|
{
|
|
int offset = g_gameConfig ? g_gameConfig->GetOffset("CServerSideClient_Slot") : -1;
|
|
if (offset < 0)
|
|
return CPlayerSlot(-1);
|
|
return *reinterpret_cast<CPlayerSlot*>(reinterpret_cast<uint8_t*>(client) + offset);
|
|
}
|
|
|
|
struct ScopedRecipient
|
|
{
|
|
explicit ScopedRecipient(CPlayerSlot slot) : previous(g_currentRecipient)
|
|
{
|
|
g_currentRecipient = slot;
|
|
}
|
|
|
|
~ScopedRecipient()
|
|
{
|
|
g_currentRecipient = previous;
|
|
}
|
|
|
|
CPlayerSlot previous;
|
|
};
|
|
|
|
static void Detour_SendSnapshot(void* client, void* snapshot)
|
|
{
|
|
++g_snapshotCalls;
|
|
CPlayerSlot slot(client ? SlotFromClient(client) : CPlayerSlot(-1));
|
|
g_lastSnapshotRecipient = slot;
|
|
ScopedRecipient recipient(slot);
|
|
if (client && g_fullUpdateBudget > 0)
|
|
{
|
|
int offset = g_gameConfig ? g_gameConfig->GetOffset("CServerSideClient_DeltaTick") : -1;
|
|
if (offset >= 0)
|
|
{
|
|
*reinterpret_cast<int*>(reinterpret_cast<uint8_t*>(client) + offset) = -1;
|
|
--g_fullUpdateBudget;
|
|
++g_fullUpdateWrites;
|
|
}
|
|
}
|
|
|
|
g_sendSnapshot(client, snapshot);
|
|
}
|
|
|
|
static void Detour_PackEntities(void* self, void* arg1, int arg2, void* arg3, void* arg4, void* arg5, void* arg6)
|
|
{
|
|
++g_packCalls;
|
|
CPlayerSlot recipient((arg2 >= 0 && arg2 < 64) ? arg2 : -1);
|
|
if (g_packEntitiesDebugLogs < 16)
|
|
{
|
|
SPMessage("pack_entities call %llu self=%p arg1=%p arg2=%d recipient=%d args=[%p %p %p %p]\n",
|
|
static_cast<unsigned long long>(g_packCalls),
|
|
self,
|
|
arg1,
|
|
arg2,
|
|
recipient.Get(),
|
|
arg3,
|
|
arg4,
|
|
arg5,
|
|
arg6);
|
|
++g_packEntitiesDebugLogs;
|
|
}
|
|
ScopedRecipient scopedRecipient(recipient);
|
|
g_packEntities(self, arg1, arg2, arg3, arg4, arg5, arg6);
|
|
}
|
|
|
|
static void Detour_PackEntity(void* self, void* arg1, int entityIndex, void* entityData, void* arg4, void* arg5, void* arg6, void* arg7, void* arg8, void* arg9)
|
|
{
|
|
++g_packEntityCalls;
|
|
|
|
if (!g_enabled || !g_armed || !entityData || !g_overrides.HasPackedOverrides())
|
|
{
|
|
g_packEntity(self, arg1, entityIndex, entityData, arg4, arg5, arg6, arg7, arg8, arg9);
|
|
return;
|
|
}
|
|
|
|
CPlayerSlot recipient = g_currentRecipient;
|
|
if (recipient.Get() < 0 && g_lastSnapshotRecipient.Get() >= 0)
|
|
recipient = g_lastSnapshotRecipient;
|
|
if (g_packEntityRuleDebugLogs < 64 && g_overrides.HasRulesForEntity(entityIndex))
|
|
{
|
|
SPMessage("pack_entity ruled call %llu entity=%d current=%d lastSnapshot=%d args self=%p arg1=%p data=%p arg4=%p arg5=%p arg6=%p arg7=%p arg8=%p arg9=%p\n",
|
|
static_cast<unsigned long long>(g_packEntityCalls),
|
|
entityIndex,
|
|
g_currentRecipient.Get(),
|
|
g_lastSnapshotRecipient.Get(),
|
|
self,
|
|
arg1,
|
|
entityData,
|
|
arg4,
|
|
arg5,
|
|
arg6,
|
|
arg7,
|
|
arg8,
|
|
arg9);
|
|
++g_packEntityRuleDebugLogs;
|
|
}
|
|
auto applied = g_overrides.ApplyForPackedEntity(recipient, entityIndex, entityData);
|
|
g_packEntityGenericSpoofs += applied.size();
|
|
g_packEntity(self, arg1, entityIndex, entityData, arg4, arg5, arg6, arg7, arg8, arg9);
|
|
g_overrides.Restore(applied);
|
|
}
|
|
|
|
static bool InitPackEntityDetour(CGameConfig* config)
|
|
{
|
|
void* packEntityTarget = config->ResolveSignature("CNetworkGameServer_PackEntity");
|
|
if (!packEntityTarget)
|
|
{
|
|
SPWarning("CNetworkGameServer_PackEntity signature missing; override hook disabled\n");
|
|
return true;
|
|
}
|
|
|
|
g_packEntity = reinterpret_cast<PackEntityFn>(packEntityTarget);
|
|
g_packEntityHook = funchook_create();
|
|
if (!g_packEntityHook)
|
|
return false;
|
|
|
|
if (funchook_prepare(g_packEntityHook, reinterpret_cast<void**>(&g_packEntity), reinterpret_cast<void*>(Detour_PackEntity)) != 0)
|
|
return false;
|
|
if (funchook_install(g_packEntityHook, 0) != 0)
|
|
return false;
|
|
|
|
SPMessage("detoured CNetworkGameServer_PackEntity at %p\n", packEntityTarget);
|
|
return true;
|
|
}
|
|
|
|
bool InitSnapshotDetour(CGameConfig* config)
|
|
{
|
|
void* target = config->ResolveSignature("CServerSideClient_SendSnapshot");
|
|
if (!target)
|
|
{
|
|
SPWarning("CServerSideClient_SendSnapshot signature missing; snapshot hook disabled\n");
|
|
return true;
|
|
}
|
|
|
|
g_sendSnapshot = reinterpret_cast<SendSnapshotFn>(target);
|
|
g_snapshotHook = funchook_create();
|
|
if (!g_snapshotHook)
|
|
return false;
|
|
|
|
if (funchook_prepare(g_snapshotHook, reinterpret_cast<void**>(&g_sendSnapshot), reinterpret_cast<void*>(Detour_SendSnapshot)) != 0)
|
|
return false;
|
|
if (funchook_install(g_snapshotHook, 0) != 0)
|
|
return false;
|
|
|
|
SPMessage("detoured CServerSideClient_SendSnapshot at %p\n", target);
|
|
|
|
void* packTarget = config->ResolveSignature("CNetworkGameServer_PackEntities_Normal");
|
|
if (!packTarget)
|
|
{
|
|
SPWarning("CNetworkGameServer_PackEntities_Normal signature missing; pack counter hook disabled\n");
|
|
return true;
|
|
}
|
|
|
|
g_packEntities = reinterpret_cast<PackEntitiesFn>(packTarget);
|
|
g_packHook = funchook_create();
|
|
if (!g_packHook)
|
|
return false;
|
|
|
|
if (funchook_prepare(g_packHook, reinterpret_cast<void**>(&g_packEntities), reinterpret_cast<void*>(Detour_PackEntities)) != 0)
|
|
return false;
|
|
if (funchook_install(g_packHook, 0) != 0)
|
|
return false;
|
|
|
|
SPMessage("detoured CNetworkGameServer_PackEntities_Normal at %p\n", packTarget);
|
|
if (!InitPackEntityDetour(config))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
void ShutdownSnapshotDetour()
|
|
{
|
|
if (g_packEntityHook)
|
|
{
|
|
funchook_uninstall(g_packEntityHook, 0);
|
|
funchook_destroy(g_packEntityHook);
|
|
g_packEntityHook = nullptr;
|
|
}
|
|
if (g_packHook)
|
|
{
|
|
funchook_uninstall(g_packHook, 0);
|
|
funchook_destroy(g_packHook);
|
|
g_packHook = nullptr;
|
|
}
|
|
if (g_snapshotHook)
|
|
{
|
|
funchook_uninstall(g_snapshotHook, 0);
|
|
funchook_destroy(g_snapshotHook);
|
|
g_snapshotHook = nullptr;
|
|
}
|
|
g_sendSnapshot = nullptr;
|
|
g_packEntities = nullptr;
|
|
g_packEntity = nullptr;
|
|
}
|
|
|
|
void DumpDetourStats()
|
|
{
|
|
SPMessage("hooks: snapshot calls=%llu; pack calls=%llu; pack_entity calls=%llu generic_spoofs=%llu; fullupdate writes=%llu budget=%d\n",
|
|
static_cast<unsigned long long>(g_snapshotCalls),
|
|
static_cast<unsigned long long>(g_packCalls),
|
|
static_cast<unsigned long long>(g_packEntityCalls),
|
|
static_cast<unsigned long long>(g_packEntityGenericSpoofs),
|
|
static_cast<unsigned long long>(g_fullUpdateWrites),
|
|
g_fullUpdateBudget);
|
|
}
|
|
|
|
void RequestSnapshotFullUpdates(int budget)
|
|
{
|
|
if (budget > g_fullUpdateBudget)
|
|
g_fullUpdateBudget = budget;
|
|
}
|