remake - v1
This commit is contained in:
277
src/detour.cpp
277
src/detour.cpp
@@ -1,17 +1,46 @@
|
||||
#include "detour.h"
|
||||
|
||||
#include "common.h"
|
||||
#include "entity2/entitysystem.h"
|
||||
#include "entity2/entityinstance.h"
|
||||
#include "entityhandle.h"
|
||||
#include "gameconfig.h"
|
||||
#include "networksystem/inetworkserializer.h"
|
||||
#include "overrides.h"
|
||||
#include "playerslot.h"
|
||||
#include "schema.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
extern CGameConfig* g_gameConfig;
|
||||
extern CGameEntitySystem* g_entitySystem;
|
||||
extern bool g_enabled;
|
||||
extern bool g_armed;
|
||||
extern CGameEntitySystem* GameEntitySystem();
|
||||
|
||||
using SendSnapshotFn = void (*)(void*, void*);
|
||||
using PackEntitiesFn = void (*)(void*, void*, int, void*, void*, void*, void*, void*, void*);
|
||||
using PackEntityFn = void (*)(void*, void*, int, void*, void*, void*, void*, void*, void*, void*);
|
||||
static SendSnapshotFn g_sendSnapshot = nullptr;
|
||||
static funchook_t* g_hook = 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_snapshotApplied = 0;
|
||||
static uint64_t g_snapshotChanged = 0;
|
||||
static uint64_t g_packCalls = 0;
|
||||
static uint64_t g_packApplied = 0;
|
||||
static uint64_t g_packChanged = 0;
|
||||
static uint64_t g_packEntityCalls = 0;
|
||||
static uint64_t g_packEntitySpoofs = 0;
|
||||
static uint64_t g_packEntityScopedWrites = 0;
|
||||
static uint64_t g_packEntitySelfSkips = 0;
|
||||
static uint64_t g_packEntityNoRecipient = 0;
|
||||
static uint64_t g_fullUpdateWrites = 0;
|
||||
static int g_fullUpdateBudget = 0;
|
||||
static thread_local CPlayerSlot g_currentRecipient(-1);
|
||||
|
||||
static CPlayerSlot SlotFromClient(void* client)
|
||||
{
|
||||
@@ -21,8 +50,36 @@ static CPlayerSlot SlotFromClient(void* client)
|
||||
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;
|
||||
ScopedRecipient recipient(client ? SlotFromClient(client) : CPlayerSlot(-1));
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (!g_enabled || !g_armed || !client || g_overrides.Empty())
|
||||
{
|
||||
g_sendSnapshot(client, snapshot);
|
||||
@@ -31,10 +88,150 @@ static void Detour_SendSnapshot(void* client, void* snapshot)
|
||||
|
||||
CPlayerSlot slot = SlotFromClient(client);
|
||||
auto applied = g_overrides.ApplyForRecipient(slot);
|
||||
if (!applied.empty())
|
||||
{
|
||||
++g_snapshotApplied;
|
||||
g_snapshotChanged += applied.size();
|
||||
}
|
||||
g_sendSnapshot(client, snapshot);
|
||||
g_overrides.Restore(applied);
|
||||
}
|
||||
|
||||
static void Detour_PackEntities(void* self, void* arg1, int arg2, void* arg3, void* arg4, void* arg5, void* arg6, void* arg7, void* arg8)
|
||||
{
|
||||
++g_packCalls;
|
||||
g_packEntities(self, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
|
||||
}
|
||||
|
||||
static CEntityIdentity* PlayerIdentityFromInstance(void* instance)
|
||||
{
|
||||
if (!instance)
|
||||
return nullptr;
|
||||
|
||||
auto* entity = static_cast<CEntityInstance*>(instance);
|
||||
CEntityIdentity* identity = entity->m_pEntity;
|
||||
const char* designer = identity ? identity->m_designerName.String() : nullptr;
|
||||
return designer && !V_stricmp(designer, "player") ? identity : nullptr;
|
||||
}
|
||||
|
||||
static bool DesignerContains(CEntityIdentity* identity, const char* needle)
|
||||
{
|
||||
const char* designer = identity ? identity->m_designerName.String() : nullptr;
|
||||
return designer && V_stristr(designer, needle);
|
||||
}
|
||||
|
||||
static int EntityIndex(CEntityIdentity* identity)
|
||||
{
|
||||
return identity ? identity->m_EHandle.GetEntryIndex() : -1;
|
||||
}
|
||||
|
||||
static int OwnerSlotForPawn(CEntityIdentity* pawnIdentity)
|
||||
{
|
||||
if (!pawnIdentity)
|
||||
return -1;
|
||||
if (!g_entitySystem)
|
||||
g_entitySystem = GameEntitySystem();
|
||||
if (!g_entitySystem)
|
||||
return -1;
|
||||
|
||||
auto pawnField = schema::FindField("CBasePlayerController", "m_hPawn");
|
||||
auto slotField = schema::FindField("CBasePlayerController", "m_nSplitScreenSlot");
|
||||
if (!pawnField.found || !slotField.found)
|
||||
return -1;
|
||||
|
||||
int pawnIndex = EntityIndex(pawnIdentity);
|
||||
int visited = 0;
|
||||
for (auto* identity = g_entitySystem->m_EntityList.m_pFirstActiveEntity; identity && visited < 16384; identity = identity->m_pNext, ++visited)
|
||||
{
|
||||
if (!identity->m_pInstance || !DesignerContains(identity, "controller"))
|
||||
continue;
|
||||
|
||||
auto* entity = reinterpret_cast<uint8_t*>(identity->m_pInstance);
|
||||
int controlledPawnIndex = reinterpret_cast<CEntityHandle*>(entity + pawnField.offset)->GetEntryIndex();
|
||||
if (controlledPawnIndex != pawnIndex)
|
||||
continue;
|
||||
|
||||
int slot = *reinterpret_cast<int*>(entity + slotField.offset);
|
||||
return slot >= 0 && slot < 64 ? slot : -1;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
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_packEntity(self, arg1, entityIndex, entityData, arg4, arg5, arg6, arg7, arg8, arg9);
|
||||
return;
|
||||
}
|
||||
|
||||
CEntityIdentity* identity = PlayerIdentityFromInstance(entityData);
|
||||
if (!identity)
|
||||
{
|
||||
g_packEntity(self, arg1, entityIndex, entityData, arg4, arg5, arg6, arg7, arg8, arg9);
|
||||
return;
|
||||
}
|
||||
|
||||
CPlayerSlot recipient = g_currentRecipient;
|
||||
int spoofHealth = 42;
|
||||
if (!g_overrides.TryGetHealth42Value(recipient, spoofHealth))
|
||||
{
|
||||
g_packEntity(self, arg1, entityIndex, entityData, arg4, arg5, arg6, arg7, arg8, arg9);
|
||||
return;
|
||||
}
|
||||
|
||||
if (recipient.Get() < 0)
|
||||
++g_packEntityNoRecipient;
|
||||
else if (OwnerSlotForPawn(identity) == recipient.Get())
|
||||
{
|
||||
++g_packEntitySelfSkips;
|
||||
g_packEntity(self, arg1, entityIndex, entityData, arg4, arg5, arg6, arg7, arg8, arg9);
|
||||
return;
|
||||
}
|
||||
|
||||
auto healthField = schema::FindField("CBaseEntity", "m_iHealth");
|
||||
if (!healthField.found || healthField.offset < 0)
|
||||
{
|
||||
g_packEntity(self, arg1, entityIndex, entityData, arg4, arg5, arg6, arg7, arg8, arg9);
|
||||
return;
|
||||
}
|
||||
|
||||
auto* healthAddress = reinterpret_cast<int*>(reinterpret_cast<uint8_t*>(entityData) + healthField.offset);
|
||||
int savedHealth = *healthAddress;
|
||||
*healthAddress = spoofHealth;
|
||||
|
||||
++g_packEntitySpoofs;
|
||||
++g_packEntityScopedWrites;
|
||||
g_packEntity(self, arg1, entityIndex, entityData, arg4, arg5, arg6, arg7, arg8, arg9);
|
||||
*healthAddress = savedHealth;
|
||||
}
|
||||
|
||||
static bool InitPackEntityDetour(CGameConfig* config)
|
||||
{
|
||||
void* packEntityTarget = config->ResolveSignature("CNetworkGameServer_PackEntity");
|
||||
if (!packEntityTarget)
|
||||
{
|
||||
SPWarning("CNetworkGameServer_PackEntity signature missing; scoped health 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");
|
||||
@@ -45,25 +242,85 @@ bool InitSnapshotDetour(CGameConfig* config)
|
||||
}
|
||||
|
||||
g_sendSnapshot = reinterpret_cast<SendSnapshotFn>(target);
|
||||
g_hook = funchook_create();
|
||||
if (!g_hook)
|
||||
g_snapshotHook = funchook_create();
|
||||
if (!g_snapshotHook)
|
||||
return false;
|
||||
|
||||
if (funchook_prepare(g_hook, reinterpret_cast<void**>(&g_sendSnapshot), reinterpret_cast<void*>(Detour_SendSnapshot)) != 0)
|
||||
if (funchook_prepare(g_snapshotHook, reinterpret_cast<void**>(&g_sendSnapshot), reinterpret_cast<void*>(Detour_SendSnapshot)) != 0)
|
||||
return false;
|
||||
if (funchook_install(g_hook, 0) != 0)
|
||||
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; pre-pack health_42 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_hook)
|
||||
return;
|
||||
funchook_uninstall(g_hook, 0);
|
||||
funchook_destroy(g_hook);
|
||||
g_hook = nullptr;
|
||||
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 applied=%llu changed=%llu; pack calls=%llu applied=%llu changed=%llu; pack_entity calls=%llu spoofs=%llu scoped_writes=%llu self_skips=%llu no_recipient=%llu; fullupdate writes=%llu budget=%d\n",
|
||||
static_cast<unsigned long long>(g_snapshotCalls),
|
||||
static_cast<unsigned long long>(g_snapshotApplied),
|
||||
static_cast<unsigned long long>(g_snapshotChanged),
|
||||
static_cast<unsigned long long>(g_packCalls),
|
||||
static_cast<unsigned long long>(g_packApplied),
|
||||
static_cast<unsigned long long>(g_packChanged),
|
||||
static_cast<unsigned long long>(g_packEntityCalls),
|
||||
static_cast<unsigned long long>(g_packEntitySpoofs),
|
||||
static_cast<unsigned long long>(g_packEntityScopedWrites),
|
||||
static_cast<unsigned long long>(g_packEntitySelfSkips),
|
||||
static_cast<unsigned long long>(g_packEntityNoRecipient),
|
||||
static_cast<unsigned long long>(g_fullUpdateWrites),
|
||||
g_fullUpdateBudget);
|
||||
}
|
||||
|
||||
void RequestSnapshotFullUpdates(int budget)
|
||||
{
|
||||
if (budget > g_fullUpdateBudget)
|
||||
g_fullUpdateBudget = budget;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user