remake - v1
This commit is contained in:
277
src/detour.cpp
277
src/detour.cpp
@@ -1,17 +1,46 @@
|
|||||||
#include "detour.h"
|
#include "detour.h"
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
#include "entity2/entitysystem.h"
|
||||||
|
#include "entity2/entityinstance.h"
|
||||||
|
#include "entityhandle.h"
|
||||||
#include "gameconfig.h"
|
#include "gameconfig.h"
|
||||||
|
#include "networksystem/inetworkserializer.h"
|
||||||
#include "overrides.h"
|
#include "overrides.h"
|
||||||
#include "playerslot.h"
|
#include "playerslot.h"
|
||||||
|
#include "schema.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
extern CGameConfig* g_gameConfig;
|
extern CGameConfig* g_gameConfig;
|
||||||
|
extern CGameEntitySystem* g_entitySystem;
|
||||||
extern bool g_enabled;
|
extern bool g_enabled;
|
||||||
extern bool g_armed;
|
extern bool g_armed;
|
||||||
|
extern CGameEntitySystem* GameEntitySystem();
|
||||||
|
|
||||||
using SendSnapshotFn = void (*)(void*, void*);
|
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 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)
|
static CPlayerSlot SlotFromClient(void* client)
|
||||||
{
|
{
|
||||||
@@ -21,8 +50,36 @@ static CPlayerSlot SlotFromClient(void* client)
|
|||||||
return *reinterpret_cast<CPlayerSlot*>(reinterpret_cast<uint8_t*>(client) + offset);
|
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)
|
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())
|
if (!g_enabled || !g_armed || !client || g_overrides.Empty())
|
||||||
{
|
{
|
||||||
g_sendSnapshot(client, snapshot);
|
g_sendSnapshot(client, snapshot);
|
||||||
@@ -31,10 +88,150 @@ static void Detour_SendSnapshot(void* client, void* snapshot)
|
|||||||
|
|
||||||
CPlayerSlot slot = SlotFromClient(client);
|
CPlayerSlot slot = SlotFromClient(client);
|
||||||
auto applied = g_overrides.ApplyForRecipient(slot);
|
auto applied = g_overrides.ApplyForRecipient(slot);
|
||||||
|
if (!applied.empty())
|
||||||
|
{
|
||||||
|
++g_snapshotApplied;
|
||||||
|
g_snapshotChanged += applied.size();
|
||||||
|
}
|
||||||
g_sendSnapshot(client, snapshot);
|
g_sendSnapshot(client, snapshot);
|
||||||
g_overrides.Restore(applied);
|
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)
|
bool InitSnapshotDetour(CGameConfig* config)
|
||||||
{
|
{
|
||||||
void* target = config->ResolveSignature("CServerSideClient_SendSnapshot");
|
void* target = config->ResolveSignature("CServerSideClient_SendSnapshot");
|
||||||
@@ -45,25 +242,85 @@ bool InitSnapshotDetour(CGameConfig* config)
|
|||||||
}
|
}
|
||||||
|
|
||||||
g_sendSnapshot = reinterpret_cast<SendSnapshotFn>(target);
|
g_sendSnapshot = reinterpret_cast<SendSnapshotFn>(target);
|
||||||
g_hook = funchook_create();
|
g_snapshotHook = funchook_create();
|
||||||
if (!g_hook)
|
if (!g_snapshotHook)
|
||||||
return false;
|
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;
|
return false;
|
||||||
if (funchook_install(g_hook, 0) != 0)
|
if (funchook_install(g_snapshotHook, 0) != 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
SPMessage("detoured CServerSideClient_SendSnapshot at %p\n", target);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShutdownSnapshotDetour()
|
void ShutdownSnapshotDetour()
|
||||||
{
|
{
|
||||||
if (!g_hook)
|
if (g_packEntityHook)
|
||||||
return;
|
{
|
||||||
funchook_uninstall(g_hook, 0);
|
funchook_uninstall(g_packEntityHook, 0);
|
||||||
funchook_destroy(g_hook);
|
funchook_destroy(g_packEntityHook);
|
||||||
g_hook = nullptr;
|
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_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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,4 +6,5 @@ class CGameConfig;
|
|||||||
|
|
||||||
bool InitSnapshotDetour(CGameConfig* config);
|
bool InitSnapshotDetour(CGameConfig* config);
|
||||||
void ShutdownSnapshotDetour();
|
void ShutdownSnapshotDetour();
|
||||||
|
void DumpDetourStats();
|
||||||
|
void RequestSnapshotFullUpdates(int budget = 512);
|
||||||
|
|||||||
@@ -116,4 +116,3 @@ void* CModule::FindInterface(const char* name) const
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
return fn(name, nullptr);
|
return fn(name, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,4 +48,3 @@ namespace modules
|
|||||||
extern CModule* server;
|
extern CModule* server;
|
||||||
extern CModule* schemasystem;
|
extern CModule* schemasystem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "schema.h"
|
#include "schema.h"
|
||||||
#include "entity2/entitysystem.h"
|
#include "entity2/entitysystem.h"
|
||||||
|
#include "entity2/entityinstance.h"
|
||||||
#include "entityhandle.h"
|
#include "entityhandle.h"
|
||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
@@ -109,6 +110,12 @@ static bool DesignerContains(CEntityIdentity* identity, const char* needle)
|
|||||||
return designer && V_stristr(designer, needle);
|
return designer && V_stristr(designer, needle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool DesignerEquals(CEntityIdentity* identity, const char* value)
|
||||||
|
{
|
||||||
|
const char* designer = identity ? identity->m_designerName.String() : nullptr;
|
||||||
|
return designer && !V_stricmp(designer, value);
|
||||||
|
}
|
||||||
|
|
||||||
static int EntityIndex(CEntityIdentity* identity)
|
static int EntityIndex(CEntityIdentity* identity)
|
||||||
{
|
{
|
||||||
return identity ? identity->m_EHandle.GetEntryIndex() : -1;
|
return identity ? identity->m_EHandle.GetEntryIndex() : -1;
|
||||||
@@ -274,13 +281,68 @@ bool OverrideManager::SetHealth42Test(bool enabled, std::string& error)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool OverrideManager::SetRecipientHealthValue(int recipientSlot, int value, std::string& error)
|
||||||
|
{
|
||||||
|
if (recipientSlot < -1 || recipientSlot >= static_cast<int>(m_recipientHealthSet.size()))
|
||||||
|
{
|
||||||
|
error = "recipient slot must be all/-1 or 0..63";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!RequiredField("CBaseEntity", "m_iHealth", error))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (recipientSlot == -1)
|
||||||
|
{
|
||||||
|
m_defaultHealth42Value = value;
|
||||||
|
SPMessage("health_42 default recipient value=%d\n", value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_recipientHealthSet[recipientSlot] = true;
|
||||||
|
m_recipientHealthValue[recipientSlot] = value;
|
||||||
|
SPMessage("health_42 recipient slot %d value=%d\n", recipientSlot, value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OverrideManager::ClearRecipientHealthValues()
|
||||||
|
{
|
||||||
|
m_defaultHealth42Value = 42;
|
||||||
|
m_recipientHealthSet.fill(false);
|
||||||
|
m_recipientHealthValue.fill(0);
|
||||||
|
SPMessage("health_42 recipient values cleared\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OverrideManager::TryGetHealth42Value(CPlayerSlot recipient, int& value) const
|
||||||
|
{
|
||||||
|
if (!m_health42Test)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int slot = recipient.Get();
|
||||||
|
if (slot >= 0 && slot < static_cast<int>(m_recipientHealthSet.size()) && m_recipientHealthSet[slot])
|
||||||
|
{
|
||||||
|
value = m_recipientHealthValue[slot];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = m_defaultHealth42Value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void OverrideManager::Dump() const
|
void OverrideManager::Dump() const
|
||||||
{
|
{
|
||||||
SPMessage("%zu static rules loaded; %zu dynamic rules loaded; trace=%d\n", m_rules.size(), DynamicRuleCount(), m_trace ? 1 : 0);
|
SPMessage("%zu static rules loaded; %zu dynamic rules loaded; trace=%d\n", m_rules.size(), DynamicRuleCount(), m_trace ? 1 : 0);
|
||||||
if (m_healthMoneyTest)
|
if (m_healthMoneyTest)
|
||||||
SPMessage("dynamic: teammate CBaseEntity::m_iHealth = recipient CCSPlayerController_InGameMoneyServices::m_iAccount\n");
|
SPMessage("dynamic: teammate CBaseEntity::m_iHealth = recipient CCSPlayerController_InGameMoneyServices::m_iAccount\n");
|
||||||
if (m_health42Test)
|
if (m_health42Test)
|
||||||
SPMessage("dynamic: all player CBaseEntity::m_iHealth = 42\n");
|
{
|
||||||
|
SPMessage("dynamic: all non-own player CBaseEntity::m_iHealth = per-recipient value, default=%d\n", m_defaultHealth42Value);
|
||||||
|
for (size_t i = 0; i < m_recipientHealthSet.size(); ++i)
|
||||||
|
{
|
||||||
|
if (m_recipientHealthSet[i])
|
||||||
|
SPMessage("dynamic: recipient slot %zu health=%d\n", i, m_recipientHealthValue[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
for (const auto& rule : m_rules)
|
for (const auto& rule : m_rules)
|
||||||
SPMessage("#%d recipient=%d target=%s field=%s::%s type=%s\n", rule.id, rule.recipientSlot, rule.target.c_str(), rule.className.c_str(), rule.fieldName.c_str(), TypeName(rule.type));
|
SPMessage("#%d recipient=%d target=%s field=%s::%s type=%s\n", rule.id, rule.recipientSlot, rule.target.c_str(), rule.className.c_str(), rule.fieldName.c_str(), TypeName(rule.type));
|
||||||
}
|
}
|
||||||
@@ -399,44 +461,71 @@ void OverrideManager::ApplyHealthMoneyTest(CPlayerSlot slot, std::vector<Applied
|
|||||||
SPMessage("health_money_test recipient=%d money=%d changed=%d\n", slot.Get(), money, changed);
|
SPMessage("health_money_test recipient=%d money=%d changed=%d\n", slot.Get(), money, changed);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OverrideManager::ApplyHealth42Test(CPlayerSlot slot, std::vector<AppliedOverride>& applied)
|
std::vector<AppliedOverride> OverrideManager::ApplyGlobalHealth42()
|
||||||
{
|
{
|
||||||
|
std::vector<AppliedOverride> applied;
|
||||||
if (!m_health42Test)
|
if (!m_health42Test)
|
||||||
return;
|
return applied;
|
||||||
if (!g_entitySystem)
|
if (!g_entitySystem)
|
||||||
g_entitySystem = GameEntitySystem();
|
g_entitySystem = GameEntitySystem();
|
||||||
if (!g_entitySystem)
|
if (!g_entitySystem)
|
||||||
return;
|
return applied;
|
||||||
|
|
||||||
int changed = 0;
|
auto healthField = schema::FindField("CBaseEntity", "m_iHealth");
|
||||||
|
if (!healthField.found)
|
||||||
|
return applied;
|
||||||
|
|
||||||
|
applied.reserve(16);
|
||||||
int visited = 0;
|
int visited = 0;
|
||||||
for (auto* identity = g_entitySystem->m_EntityList.m_pFirstActiveEntity; identity && visited < 16384; identity = identity->m_pNext, ++visited)
|
for (auto* identity = g_entitySystem->m_EntityList.m_pFirstActiveEntity; identity && visited < 16384; identity = identity->m_pNext, ++visited)
|
||||||
{
|
{
|
||||||
if (!identity->m_pInstance)
|
if (!identity->m_pInstance)
|
||||||
continue;
|
continue;
|
||||||
if (!DesignerContains(identity, "player") || DesignerContains(identity, "controller"))
|
if (!DesignerEquals(identity, "player"))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
uintptr_t entity = reinterpret_cast<uintptr_t>(identity->m_pInstance);
|
auto* healthAddress = reinterpret_cast<uint8_t*>(identity->m_pInstance) + healthField.offset;
|
||||||
auto* healthAddress = FieldAddress(entity, "CBaseEntity", "m_iHealth");
|
|
||||||
if (!healthAddress)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
AppliedOverride saved {healthAddress, FieldType::Int32, ReadBits(healthAddress, FieldType::Int32)};
|
AppliedOverride saved {healthAddress, FieldType::Int32, static_cast<uint32_t>(*reinterpret_cast<int*>(healthAddress))};
|
||||||
WriteBits(healthAddress, FieldType::Int32, 42);
|
*reinterpret_cast<int*>(healthAddress) = 42;
|
||||||
applied.push_back(saved);
|
applied.push_back(saved);
|
||||||
|
}
|
||||||
|
|
||||||
|
return applied;
|
||||||
|
}
|
||||||
|
|
||||||
|
int OverrideManager::MarkPlayerHealthDirty()
|
||||||
|
{
|
||||||
|
if (!g_entitySystem)
|
||||||
|
g_entitySystem = GameEntitySystem();
|
||||||
|
if (!g_entitySystem)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
auto healthField = schema::FindField("CBaseEntity", "m_iHealth");
|
||||||
|
if (!healthField.found)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
int changed = 0;
|
||||||
|
int visited = 0;
|
||||||
|
for (auto* identity = g_entitySystem->m_EntityList.m_pFirstActiveEntity; identity && visited < 16384; identity = identity->m_pNext, ++visited)
|
||||||
|
{
|
||||||
|
if (!identity->m_pInstance || !DesignerEquals(identity, "player"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
NetworkStateChangedData data(static_cast<uint32>(healthField.offset));
|
||||||
|
identity->m_pInstance->NetworkStateChanged(data);
|
||||||
++changed;
|
++changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_trace)
|
if (m_trace)
|
||||||
SPMessage("health_42_test recipient=%d changed=%d\n", slot.Get(), changed);
|
SPMessage("marked player health dirty changed=%d\n", changed);
|
||||||
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<AppliedOverride> OverrideManager::ApplyForRecipient(CPlayerSlot slot)
|
std::vector<AppliedOverride> OverrideManager::ApplyForRecipient(CPlayerSlot slot)
|
||||||
{
|
{
|
||||||
std::vector<AppliedOverride> applied;
|
std::vector<AppliedOverride> applied;
|
||||||
ApplyHealthMoneyTest(slot, applied);
|
ApplyHealthMoneyTest(slot, applied);
|
||||||
ApplyHealth42Test(slot, applied);
|
|
||||||
for (const auto& rule : m_rules)
|
for (const auto& rule : m_rules)
|
||||||
{
|
{
|
||||||
if (!rule.enabled || (rule.recipientSlot != -1 && rule.recipientSlot != slot.Get()))
|
if (!rule.enabled || (rule.recipientSlot != -1 && rule.recipientSlot != slot.Get()))
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "playerslot.h"
|
#include "playerslot.h"
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <array>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -54,14 +55,20 @@ public:
|
|||||||
bool AddWeaponItemDefRule(int recipientSlot, const char* target, uint16_t itemDefinitionIndex, std::string& error);
|
bool AddWeaponItemDefRule(int recipientSlot, const char* target, uint16_t itemDefinitionIndex, std::string& error);
|
||||||
bool SetHealthMoneyTest(bool enabled, std::string& error);
|
bool SetHealthMoneyTest(bool enabled, std::string& error);
|
||||||
bool SetHealth42Test(bool enabled, std::string& error);
|
bool SetHealth42Test(bool enabled, std::string& error);
|
||||||
|
bool SetRecipientHealthValue(int recipientSlot, int value, std::string& error);
|
||||||
|
void ClearRecipientHealthValues();
|
||||||
void Dump() const;
|
void Dump() const;
|
||||||
void SetTrace(bool value) { m_trace = value; }
|
void SetTrace(bool value) { m_trace = value; }
|
||||||
bool Trace() const { return m_trace; }
|
bool Trace() const { return m_trace; }
|
||||||
size_t RuleCount() const { return m_rules.size(); }
|
size_t RuleCount() const { return m_rules.size(); }
|
||||||
size_t DynamicRuleCount() const { return (m_healthMoneyTest ? 1 : 0) + (m_health42Test ? 1 : 0); }
|
size_t DynamicRuleCount() const { return (m_healthMoneyTest ? 1 : 0) + (m_health42Test ? 1 : 0); }
|
||||||
bool Empty() const { return m_rules.empty() && !m_healthMoneyTest && !m_health42Test; }
|
bool Empty() const { return m_rules.empty() && !m_healthMoneyTest && !m_health42Test; }
|
||||||
|
bool Health42Test() const { return m_health42Test; }
|
||||||
|
bool TryGetHealth42Value(CPlayerSlot recipient, int& value) const;
|
||||||
|
|
||||||
std::vector<AppliedOverride> ApplyForRecipient(CPlayerSlot slot);
|
std::vector<AppliedOverride> ApplyForRecipient(CPlayerSlot slot);
|
||||||
|
std::vector<AppliedOverride> ApplyGlobalHealth42();
|
||||||
|
int MarkPlayerHealthDirty();
|
||||||
void Restore(const std::vector<AppliedOverride>& applied);
|
void Restore(const std::vector<AppliedOverride>& applied);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -69,13 +76,15 @@ private:
|
|||||||
uintptr_t FindFirstEntityForTarget(const OverrideRule& rule) const;
|
uintptr_t FindFirstEntityForTarget(const OverrideRule& rule) const;
|
||||||
uint8_t* ResolveAddress(uintptr_t entity, const OverrideRule& rule) const;
|
uint8_t* ResolveAddress(uintptr_t entity, const OverrideRule& rule) const;
|
||||||
void ApplyHealthMoneyTest(CPlayerSlot slot, std::vector<AppliedOverride>& applied);
|
void ApplyHealthMoneyTest(CPlayerSlot slot, std::vector<AppliedOverride>& applied);
|
||||||
void ApplyHealth42Test(CPlayerSlot slot, std::vector<AppliedOverride>& applied);
|
|
||||||
|
|
||||||
std::vector<OverrideRule> m_rules;
|
std::vector<OverrideRule> m_rules;
|
||||||
int m_nextId {1};
|
int m_nextId {1};
|
||||||
bool m_trace {false};
|
bool m_trace {false};
|
||||||
bool m_healthMoneyTest {false};
|
bool m_healthMoneyTest {false};
|
||||||
bool m_health42Test {false};
|
bool m_health42Test {false};
|
||||||
|
int m_defaultHealth42Value {42};
|
||||||
|
std::array<bool, 64> m_recipientHealthSet {};
|
||||||
|
std::array<int, 64> m_recipientHealthValue {};
|
||||||
};
|
};
|
||||||
|
|
||||||
extern OverrideManager g_overrides;
|
extern OverrideManager g_overrides;
|
||||||
|
|||||||
135
src/plugin.cpp
135
src/plugin.cpp
@@ -10,6 +10,7 @@
|
|||||||
#include "schema.h"
|
#include "schema.h"
|
||||||
#include "schemasystem/schemasystem.h"
|
#include "schemasystem/schemasystem.h"
|
||||||
#include "interfaces/interfaces.h"
|
#include "interfaces/interfaces.h"
|
||||||
|
#include "entity2/entityclass.h"
|
||||||
#include "entity2/entitysystem.h"
|
#include "entity2/entitysystem.h"
|
||||||
|
|
||||||
#include "tier0/memdbgon.h"
|
#include "tier0/memdbgon.h"
|
||||||
@@ -26,6 +27,43 @@ CGameEntitySystem* g_entitySystem = nullptr;
|
|||||||
bool g_enabled = true;
|
bool g_enabled = true;
|
||||||
bool g_armed = false;
|
bool g_armed = false;
|
||||||
|
|
||||||
|
static int ForceFullUpdateForAllClients()
|
||||||
|
{
|
||||||
|
if (!g_networkServerService || !g_gameConfig)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
CNetworkGameServerBase* server = g_networkServerService->GetIGameServer();
|
||||||
|
if (!server)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
int listOffset = g_gameConfig->GetOffset("CNetworkGameServer_ClientList");
|
||||||
|
int deltaTickOffset = g_gameConfig->GetOffset("CServerSideClient_DeltaTick");
|
||||||
|
if (listOffset < 0 || deltaTickOffset < 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
auto* list = reinterpret_cast<uint8_t*>(server) + listOffset;
|
||||||
|
int count = *reinterpret_cast<int*>(list);
|
||||||
|
if (count <= 0 || count > 256)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
auto** clients = *reinterpret_cast<void***>(list + 8);
|
||||||
|
if (!clients)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
int forced = 0;
|
||||||
|
for (int i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
void* client = clients[i];
|
||||||
|
if (!client)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto* deltaTick = reinterpret_cast<int*>(reinterpret_cast<uint8_t*>(client) + deltaTickOffset);
|
||||||
|
*deltaTick = -1;
|
||||||
|
++forced;
|
||||||
|
}
|
||||||
|
return forced;
|
||||||
|
}
|
||||||
|
|
||||||
void SPMessage(const char* msg, ...)
|
void SPMessage(const char* msg, ...)
|
||||||
{
|
{
|
||||||
va_list args;
|
va_list args;
|
||||||
@@ -83,9 +121,57 @@ CON_COMMAND_F(sp_trace, "Enable SendProxy tracing", FCVAR_NONE)
|
|||||||
CON_COMMAND_F(sp_dump, "Dump SendProxy state", FCVAR_NONE)
|
CON_COMMAND_F(sp_dump, "Dump SendProxy state", FCVAR_NONE)
|
||||||
{
|
{
|
||||||
SPMessage("enabled=%d armed=%d entitySystem=%p\n", g_enabled ? 1 : 0, g_armed ? 1 : 0, GameEntitySystem());
|
SPMessage("enabled=%d armed=%d entitySystem=%p\n", g_enabled ? 1 : 0, g_armed ? 1 : 0, GameEntitySystem());
|
||||||
|
DumpDetourStats();
|
||||||
g_overrides.Dump();
|
g_overrides.Dump();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CON_COMMAND_F(sp_fullupdate, "Force full snapshot update for all connected clients", FCVAR_NONE)
|
||||||
|
{
|
||||||
|
int forced = ForceFullUpdateForAllClients();
|
||||||
|
RequestSnapshotFullUpdates();
|
||||||
|
SPMessage("forced full update for %d clients; queued snapshot fallback\n", forced);
|
||||||
|
}
|
||||||
|
|
||||||
|
CON_COMMAND_F(sp_entities, "sp_entities [substring] [limit]", FCVAR_NONE)
|
||||||
|
{
|
||||||
|
CGameEntitySystem* entitySystem = GameEntitySystem();
|
||||||
|
if (!entitySystem)
|
||||||
|
{
|
||||||
|
SPWarning("no entity system\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* filter = args.ArgC() >= 2 ? args[1] : "";
|
||||||
|
int limit = args.ArgC() >= 3 ? atoi(args[2]) : 80;
|
||||||
|
if (limit <= 0)
|
||||||
|
limit = 80;
|
||||||
|
|
||||||
|
auto healthField = schema::FindField("CBaseEntity", "m_iHealth");
|
||||||
|
int printed = 0;
|
||||||
|
int visited = 0;
|
||||||
|
for (auto* identity = entitySystem->m_EntityList.m_pFirstActiveEntity; identity && visited < 16384; identity = identity->m_pNext, ++visited)
|
||||||
|
{
|
||||||
|
const char* designer = identity->m_designerName.String();
|
||||||
|
const char* classDesigner = identity->m_pClass ? identity->m_pClass->m_designerName.String() : "";
|
||||||
|
if (filter && *filter && !V_stristr(designer ? designer : "", filter) && !V_stristr(classDesigner ? classDesigner : "", filter))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int health = -999999;
|
||||||
|
if (identity->m_pInstance && healthField.found)
|
||||||
|
health = *reinterpret_cast<int*>(reinterpret_cast<uint8_t*>(identity->m_pInstance) + healthField.offset);
|
||||||
|
|
||||||
|
SPMessage("entity idx=%d inst=%p designer=%s class=%s health=%d\n",
|
||||||
|
identity->m_EHandle.GetEntryIndex(),
|
||||||
|
identity->m_pInstance,
|
||||||
|
designer ? designer : "",
|
||||||
|
classDesigner ? classDesigner : "",
|
||||||
|
health);
|
||||||
|
if (++printed >= limit)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
SPMessage("entities printed=%d visited=%d filter=%s\n", printed, visited, filter ? filter : "");
|
||||||
|
}
|
||||||
|
|
||||||
CON_COMMAND_F(sp_clear, "Clear SendProxy rules", FCVAR_NONE)
|
CON_COMMAND_F(sp_clear, "Clear SendProxy rules", FCVAR_NONE)
|
||||||
{
|
{
|
||||||
g_overrides.Clear();
|
g_overrides.Clear();
|
||||||
@@ -135,11 +221,13 @@ static void SetHealthMoneyCommand(const CCommand& args)
|
|||||||
g_enabled = true;
|
g_enabled = true;
|
||||||
g_armed = true;
|
g_armed = true;
|
||||||
SPMessage("enabled=1 armed=1\n");
|
SPMessage("enabled=1 armed=1\n");
|
||||||
|
SPMessage("marked health dirty for %d players\n", g_overrides.MarkPlayerHealthDirty());
|
||||||
}
|
}
|
||||||
else if (g_overrides.Empty())
|
else if (g_overrides.Empty())
|
||||||
{
|
{
|
||||||
g_armed = false;
|
g_armed = false;
|
||||||
SPMessage("armed=0\n");
|
SPMessage("armed=0\n");
|
||||||
|
SPMessage("marked health dirty for %d players\n", g_overrides.MarkPlayerHealthDirty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,14 +262,61 @@ CON_COMMAND_F(sp_health_42, "sp_health_42 <0|1>", FCVAR_NONE)
|
|||||||
g_enabled = true;
|
g_enabled = true;
|
||||||
g_armed = true;
|
g_armed = true;
|
||||||
SPMessage("enabled=1 armed=1\n");
|
SPMessage("enabled=1 armed=1\n");
|
||||||
|
SPMessage("marked health dirty for %d players\n", g_overrides.MarkPlayerHealthDirty());
|
||||||
}
|
}
|
||||||
else if (g_overrides.Empty())
|
else if (g_overrides.Empty())
|
||||||
{
|
{
|
||||||
g_armed = false;
|
g_armed = false;
|
||||||
SPMessage("armed=0\n");
|
SPMessage("armed=0\n");
|
||||||
|
SPMessage("marked health dirty for %d players\n", g_overrides.MarkPlayerHealthDirty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CON_COMMAND_F(sp_health_recipient, "sp_health_recipient <slot|all|clear> [value]", FCVAR_NONE)
|
||||||
|
{
|
||||||
|
if (args.ArgC() < 2)
|
||||||
|
{
|
||||||
|
SPWarning("usage: sp_health_recipient <slot|all|clear> [value]\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!V_stricmp(args[1], "clear"))
|
||||||
|
{
|
||||||
|
g_overrides.ClearRecipientHealthValues();
|
||||||
|
SPMessage("marked health dirty for %d players\n", g_overrides.MarkPlayerHealthDirty());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.ArgC() < 3)
|
||||||
|
{
|
||||||
|
SPWarning("usage: sp_health_recipient <slot|all|clear> [value]\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int recipientSlot = !V_stricmp(args[1], "all") ? -1 : atoi(args[1]);
|
||||||
|
int value = atoi(args[2]);
|
||||||
|
std::string error;
|
||||||
|
if (!g_overrides.SetRecipientHealthValue(recipientSlot, value, error))
|
||||||
|
{
|
||||||
|
SPWarning("%s\n", error.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!g_overrides.Health42Test())
|
||||||
|
{
|
||||||
|
if (!g_overrides.SetHealth42Test(true, error))
|
||||||
|
{
|
||||||
|
SPWarning("%s\n", error.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g_enabled = true;
|
||||||
|
g_armed = true;
|
||||||
|
SPMessage("enabled=1 armed=1\n");
|
||||||
|
SPMessage("marked health dirty for %d players\n", g_overrides.MarkPlayerHealthDirty());
|
||||||
|
}
|
||||||
|
|
||||||
CON_COMMAND_F(sp_schema_fields, "sp_schema_fields <ClassName> [substring]", FCVAR_NONE)
|
CON_COMMAND_F(sp_schema_fields, "sp_schema_fields <ClassName> [substring]", FCVAR_NONE)
|
||||||
{
|
{
|
||||||
if (args.ArgC() < 2)
|
if (args.ArgC() < 2)
|
||||||
|
|||||||
Reference in New Issue
Block a user