slimmed down
This commit is contained in:
@@ -57,11 +57,10 @@ The C# wrapper is in:
|
|||||||
examples/CounterStrikeSharp/SendProxyNative.cs
|
examples/CounterStrikeSharp/SendProxyNative.cs
|
||||||
```
|
```
|
||||||
|
|
||||||
It loads the native addon with `NativeLibrary.Load` and exposes byte-oriented and typed helpers. In a CSS plugin, resolve the native library from the normal addons layout:
|
It loads the native addon with `NativeLibrary.Load` and exposes byte-oriented and typed helpers. In a CSS plugin, resolve the native library from `Server.GameDirectory`:
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
string addonsDirectory = Path.GetFullPath(Path.Combine(ModuleDirectory, "..", "..", ".."));
|
string libraryPath = Path.Combine(Server.GameDirectory, "addons", "sendproxy", "bin", "linuxsteamrt64", "sendproxy.so");
|
||||||
string libraryPath = Path.Combine(addonsDirectory, "sendproxy", "bin", "linuxsteamrt64", "sendproxy.so");
|
|
||||||
var sendProxy = new SendProxyNative(libraryPath);
|
var sendProxy = new SendProxyNative(libraryPath);
|
||||||
|
|
||||||
int ruleId = sendProxy.SetInt32(
|
int ruleId = sendProxy.SetInt32(
|
||||||
|
|||||||
@@ -213,8 +213,7 @@ public sealed class SendProxyWeaponVisibilityPlugin : BasePlugin
|
|||||||
|
|
||||||
private string GetSendProxyLibraryPath()
|
private string GetSendProxyLibraryPath()
|
||||||
{
|
{
|
||||||
string addonsDirectory = Path.GetFullPath(Path.Combine(ModuleDirectory, "..", "..", ".."));
|
string libraryPath = Path.Combine(Server.GameDirectory, "addons", "sendproxy", "bin", "linuxsteamrt64", "sendproxy.so");
|
||||||
string libraryPath = Path.Combine(addonsDirectory, "sendproxy", "bin", "linuxsteamrt64", "sendproxy.so");
|
|
||||||
if (!File.Exists(libraryPath))
|
if (!File.Exists(libraryPath))
|
||||||
throw new FileNotFoundException("SendProxy Metamod addon was not found. Install it under game/csgo/addons/sendproxy.", libraryPath);
|
throw new FileNotFoundException("SendProxy Metamod addon was not found. Install it under game/csgo/addons/sendproxy.", libraryPath);
|
||||||
return libraryPath;
|
return libraryPath;
|
||||||
|
|||||||
131
src/detour.cpp
131
src/detour.cpp
@@ -1,22 +1,16 @@
|
|||||||
#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 "networksystem/inetworkserializer.h"
|
||||||
#include "overrides.h"
|
#include "overrides.h"
|
||||||
#include "playerslot.h"
|
#include "playerslot.h"
|
||||||
#include "schema.h"
|
|
||||||
|
|
||||||
#include <cstring>
|
#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 PackEntitiesFn = void (*)(void*, void*, int, void*, void*, void*, void*, void*, void*);
|
||||||
@@ -28,17 +22,9 @@ static funchook_t* g_snapshotHook = nullptr;
|
|||||||
static funchook_t* g_packHook = nullptr;
|
static funchook_t* g_packHook = nullptr;
|
||||||
static funchook_t* g_packEntityHook = nullptr;
|
static funchook_t* g_packEntityHook = nullptr;
|
||||||
static uint64_t g_snapshotCalls = 0;
|
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_packCalls = 0;
|
||||||
static uint64_t g_packApplied = 0;
|
|
||||||
static uint64_t g_packChanged = 0;
|
|
||||||
static uint64_t g_packEntityCalls = 0;
|
static uint64_t g_packEntityCalls = 0;
|
||||||
static uint64_t g_packEntitySpoofs = 0;
|
|
||||||
static uint64_t g_packEntityScopedWrites = 0;
|
|
||||||
static uint64_t g_packEntityGenericSpoofs = 0;
|
static uint64_t g_packEntityGenericSpoofs = 0;
|
||||||
static uint64_t g_packEntitySelfSkips = 0;
|
|
||||||
static uint64_t g_packEntityNoRecipient = 0;
|
|
||||||
static uint64_t g_fullUpdateWrites = 0;
|
static uint64_t g_fullUpdateWrites = 0;
|
||||||
static int g_fullUpdateBudget = 0;
|
static int g_fullUpdateBudget = 0;
|
||||||
static thread_local CPlayerSlot g_currentRecipient(-1);
|
static thread_local CPlayerSlot g_currentRecipient(-1);
|
||||||
@@ -81,21 +67,7 @@ static void Detour_SendSnapshot(void* client, void* snapshot)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!g_enabled || !g_armed || !client || g_overrides.Empty())
|
|
||||||
{
|
|
||||||
g_sendSnapshot(client, snapshot);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
CPlayerSlot slot = SlotFromClient(client);
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Detour_PackEntities(void* self, void* arg1, int arg2, void* arg3, void* arg4, void* arg5, void* arg6, void* arg7, void* arg8)
|
static void Detour_PackEntities(void* self, void* arg1, int arg2, void* arg3, void* arg4, void* arg5, void* arg6, void* arg7, void* arg8)
|
||||||
@@ -104,61 +76,6 @@ static void Detour_PackEntities(void* self, void* arg1, int arg2, void* arg3, vo
|
|||||||
g_packEntities(self, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
|
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)
|
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;
|
++g_packEntityCalls;
|
||||||
@@ -172,41 +89,7 @@ static void Detour_PackEntity(void* self, void* arg1, int entityIndex, void* ent
|
|||||||
CPlayerSlot recipient = g_currentRecipient;
|
CPlayerSlot recipient = g_currentRecipient;
|
||||||
auto applied = g_overrides.ApplyForPackedEntity(recipient, entityIndex, entityData);
|
auto applied = g_overrides.ApplyForPackedEntity(recipient, entityIndex, entityData);
|
||||||
g_packEntityGenericSpoofs += applied.size();
|
g_packEntityGenericSpoofs += applied.size();
|
||||||
|
|
||||||
bool spoofedHealth = false;
|
|
||||||
int savedHealth = 0;
|
|
||||||
int* healthAddress = nullptr;
|
|
||||||
int spoofHealth = 42;
|
|
||||||
if (g_overrides.TryGetHealth42Value(recipient, spoofHealth))
|
|
||||||
{
|
|
||||||
CEntityIdentity* identity = PlayerIdentityFromInstance(entityData);
|
|
||||||
if (identity)
|
|
||||||
{
|
|
||||||
if (recipient.Get() < 0)
|
|
||||||
++g_packEntityNoRecipient;
|
|
||||||
else if (OwnerSlotForPawn(identity) == recipient.Get())
|
|
||||||
{
|
|
||||||
++g_packEntitySelfSkips;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
auto healthField = schema::FindField("CBaseEntity", "m_iHealth");
|
|
||||||
if (healthField.found && healthField.offset >= 0)
|
|
||||||
{
|
|
||||||
healthAddress = reinterpret_cast<int*>(reinterpret_cast<uint8_t*>(entityData) + healthField.offset);
|
|
||||||
savedHealth = *healthAddress;
|
|
||||||
*healthAddress = spoofHealth;
|
|
||||||
spoofedHealth = true;
|
|
||||||
++g_packEntitySpoofs;
|
|
||||||
++g_packEntityScopedWrites;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
g_packEntity(self, arg1, entityIndex, entityData, arg4, arg5, arg6, arg7, arg8, arg9);
|
g_packEntity(self, arg1, entityIndex, entityData, arg4, arg5, arg6, arg7, arg8, arg9);
|
||||||
if (spoofedHealth)
|
|
||||||
*healthAddress = savedHealth;
|
|
||||||
g_overrides.Restore(applied);
|
g_overrides.Restore(applied);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,7 +98,7 @@ static bool InitPackEntityDetour(CGameConfig* config)
|
|||||||
void* packEntityTarget = config->ResolveSignature("CNetworkGameServer_PackEntity");
|
void* packEntityTarget = config->ResolveSignature("CNetworkGameServer_PackEntity");
|
||||||
if (!packEntityTarget)
|
if (!packEntityTarget)
|
||||||
{
|
{
|
||||||
SPWarning("CNetworkGameServer_PackEntity signature missing; scoped health hook disabled\n");
|
SPWarning("CNetworkGameServer_PackEntity signature missing; override hook disabled\n");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,7 +140,7 @@ bool InitSnapshotDetour(CGameConfig* config)
|
|||||||
void* packTarget = config->ResolveSignature("CNetworkGameServer_PackEntities_Normal");
|
void* packTarget = config->ResolveSignature("CNetworkGameServer_PackEntities_Normal");
|
||||||
if (!packTarget)
|
if (!packTarget)
|
||||||
{
|
{
|
||||||
SPWarning("CNetworkGameServer_PackEntities_Normal signature missing; pre-pack health_42 hook disabled\n");
|
SPWarning("CNetworkGameServer_PackEntities_Normal signature missing; pack counter hook disabled\n");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,19 +187,11 @@ void ShutdownSnapshotDetour()
|
|||||||
|
|
||||||
void DumpDetourStats()
|
void DumpDetourStats()
|
||||||
{
|
{
|
||||||
SPMessage("hooks: snapshot calls=%llu applied=%llu changed=%llu; pack calls=%llu applied=%llu changed=%llu; pack_entity calls=%llu health_spoofs=%llu generic_spoofs=%llu scoped_writes=%llu self_skips=%llu no_recipient=%llu; fullupdate writes=%llu budget=%d\n",
|
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_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_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_packEntityCalls),
|
||||||
static_cast<unsigned long long>(g_packEntitySpoofs),
|
|
||||||
static_cast<unsigned long long>(g_packEntityGenericSpoofs),
|
static_cast<unsigned long long>(g_packEntityGenericSpoofs),
|
||||||
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),
|
static_cast<unsigned long long>(g_fullUpdateWrites),
|
||||||
g_fullUpdateBudget);
|
g_fullUpdateBudget);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,206 +4,15 @@
|
|||||||
#include "schema.h"
|
#include "schema.h"
|
||||||
#include "entity2/entitysystem.h"
|
#include "entity2/entitysystem.h"
|
||||||
#include "entity2/entityinstance.h"
|
#include "entity2/entityinstance.h"
|
||||||
#include "entityhandle.h"
|
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <sstream>
|
#include <algorithm>
|
||||||
|
|
||||||
extern CGameEntitySystem* g_entitySystem;
|
extern CGameEntitySystem* g_entitySystem;
|
||||||
extern CGameEntitySystem* GameEntitySystem();
|
extern CGameEntitySystem* GameEntitySystem();
|
||||||
|
|
||||||
OverrideManager g_overrides;
|
OverrideManager g_overrides;
|
||||||
|
|
||||||
static bool ParseType(const char* s, FieldType& out)
|
|
||||||
{
|
|
||||||
if (!V_stricmp(s, "bool")) { out = FieldType::Bool; return true; }
|
|
||||||
if (!V_stricmp(s, "int8")) { out = FieldType::Int8; return true; }
|
|
||||||
if (!V_stricmp(s, "uint8")) { out = FieldType::UInt8; return true; }
|
|
||||||
if (!V_stricmp(s, "int16")) { out = FieldType::Int16; return true; }
|
|
||||||
if (!V_stricmp(s, "uint16")) { out = FieldType::UInt16; return true; }
|
|
||||||
if (!V_stricmp(s, "int") || !V_stricmp(s, "int32")) { out = FieldType::Int32; return true; }
|
|
||||||
if (!V_stricmp(s, "uint") || !V_stricmp(s, "uint32")) { out = FieldType::UInt32; return true; }
|
|
||||||
if (!V_stricmp(s, "int64")) { out = FieldType::Int64; return true; }
|
|
||||||
if (!V_stricmp(s, "uint64")) { out = FieldType::UInt64; return true; }
|
|
||||||
if (!V_stricmp(s, "float")) { out = FieldType::Float; return true; }
|
|
||||||
if (!V_stricmp(s, "handle") || !V_stricmp(s, "ehandle")) { out = FieldType::UInt32; return true; }
|
|
||||||
if (!V_stricmp(s, "color") || !V_stricmp(s, "rgba")) { out = FieldType::Bytes; return true; }
|
|
||||||
if (!V_stricmp(s, "bytes") || !V_stricmp(s, "raw")) { out = FieldType::Bytes; return true; }
|
|
||||||
if (!V_stricmp(s, "string") || !V_stricmp(s, "charptr")) { out = FieldType::StringPtr; return true; }
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint64_t ParseValue(FieldType type, const char* value)
|
|
||||||
{
|
|
||||||
if (type == FieldType::Float)
|
|
||||||
{
|
|
||||||
float f = static_cast<float>(atof(value));
|
|
||||||
uint32_t bits;
|
|
||||||
memcpy(&bits, &f, sizeof(bits));
|
|
||||||
return bits;
|
|
||||||
}
|
|
||||||
if (type == FieldType::Bool)
|
|
||||||
return (!V_stricmp(value, "true") || atoi(value) != 0) ? 1 : 0;
|
|
||||||
return strtoull(value, nullptr, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint64_t ReadBits(uint8_t* address, FieldType type)
|
|
||||||
{
|
|
||||||
switch (type)
|
|
||||||
{
|
|
||||||
case FieldType::Bool: return *reinterpret_cast<bool*>(address) ? 1 : 0;
|
|
||||||
case FieldType::Int8: return static_cast<uint8_t>(*reinterpret_cast<int8_t*>(address));
|
|
||||||
case FieldType::UInt8: return *reinterpret_cast<uint8_t*>(address);
|
|
||||||
case FieldType::Int16: return static_cast<uint16_t>(*reinterpret_cast<int16_t*>(address));
|
|
||||||
case FieldType::UInt16: return *reinterpret_cast<uint16_t*>(address);
|
|
||||||
case FieldType::Int32: return static_cast<uint32_t>(*reinterpret_cast<int32_t*>(address));
|
|
||||||
case FieldType::UInt32: return *reinterpret_cast<uint32_t*>(address);
|
|
||||||
case FieldType::Int64: return static_cast<uint64_t>(*reinterpret_cast<int64_t*>(address));
|
|
||||||
case FieldType::UInt64: return *reinterpret_cast<uint64_t*>(address);
|
|
||||||
case FieldType::Float: {
|
|
||||||
uint32_t bits;
|
|
||||||
memcpy(&bits, address, sizeof(bits));
|
|
||||||
return bits;
|
|
||||||
}
|
|
||||||
case FieldType::Bytes:
|
|
||||||
case FieldType::StringPtr:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void WriteBits(uint8_t* address, FieldType type, uint64_t bits)
|
|
||||||
{
|
|
||||||
switch (type)
|
|
||||||
{
|
|
||||||
case FieldType::Bool: *reinterpret_cast<bool*>(address) = bits != 0; break;
|
|
||||||
case FieldType::Int8: *reinterpret_cast<int8_t*>(address) = static_cast<int8_t>(bits); break;
|
|
||||||
case FieldType::UInt8: *reinterpret_cast<uint8_t*>(address) = static_cast<uint8_t>(bits); break;
|
|
||||||
case FieldType::Int16: *reinterpret_cast<int16_t*>(address) = static_cast<int16_t>(bits); break;
|
|
||||||
case FieldType::UInt16: *reinterpret_cast<uint16_t*>(address) = static_cast<uint16_t>(bits); break;
|
|
||||||
case FieldType::Int32: *reinterpret_cast<int32_t*>(address) = static_cast<int32_t>(bits); break;
|
|
||||||
case FieldType::UInt32: *reinterpret_cast<uint32_t*>(address) = static_cast<uint32_t>(bits); break;
|
|
||||||
case FieldType::Int64: *reinterpret_cast<int64_t*>(address) = static_cast<int64_t>(bits); break;
|
|
||||||
case FieldType::UInt64: *reinterpret_cast<uint64_t*>(address) = bits; break;
|
|
||||||
case FieldType::Float: {
|
|
||||||
uint32_t narrowed = static_cast<uint32_t>(bits);
|
|
||||||
memcpy(address, &narrowed, sizeof(narrowed));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case FieldType::Bytes:
|
|
||||||
case FieldType::StringPtr:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static const char* TypeName(FieldType type)
|
|
||||||
{
|
|
||||||
switch (type)
|
|
||||||
{
|
|
||||||
case FieldType::Bool: return "bool";
|
|
||||||
case FieldType::Int8: return "int8";
|
|
||||||
case FieldType::UInt8: return "uint8";
|
|
||||||
case FieldType::Int16: return "int16";
|
|
||||||
case FieldType::UInt16: return "uint16";
|
|
||||||
case FieldType::Int32: return "int32";
|
|
||||||
case FieldType::UInt32: return "uint32";
|
|
||||||
case FieldType::Int64: return "int64";
|
|
||||||
case FieldType::UInt64: return "uint64";
|
|
||||||
case FieldType::Float: return "float";
|
|
||||||
case FieldType::Bytes: return "bytes";
|
|
||||||
case FieldType::StringPtr: return "string";
|
|
||||||
}
|
|
||||||
return "unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::vector<std::string> SplitString(const char* value, char separator)
|
|
||||||
{
|
|
||||||
std::vector<std::string> out;
|
|
||||||
if (!value)
|
|
||||||
return out;
|
|
||||||
|
|
||||||
const char* start = value;
|
|
||||||
for (const char* p = value; ; ++p)
|
|
||||||
{
|
|
||||||
if (*p != separator && *p != '\0')
|
|
||||||
continue;
|
|
||||||
out.emplace_back(start, p - start);
|
|
||||||
if (*p == '\0')
|
|
||||||
break;
|
|
||||||
start = p + 1;
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool ParseByteToken(const std::string& token, uint8_t& out)
|
|
||||||
{
|
|
||||||
char* end = nullptr;
|
|
||||||
unsigned long value = strtoul(token.c_str(), &end, 0);
|
|
||||||
if (!end || *end || value > 0xFF)
|
|
||||||
return false;
|
|
||||||
out = static_cast<uint8_t>(value);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool ParseBytes(const char* value, std::vector<uint8_t>& out)
|
|
||||||
{
|
|
||||||
out.clear();
|
|
||||||
if (!value)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!V_strnicmp(value, "0x", 2))
|
|
||||||
{
|
|
||||||
const char* hex = value + 2;
|
|
||||||
size_t len = strlen(hex);
|
|
||||||
if ((len % 2) != 0)
|
|
||||||
return false;
|
|
||||||
out.reserve(len / 2);
|
|
||||||
for (size_t i = 0; i < len; i += 2)
|
|
||||||
{
|
|
||||||
char token[3] {hex[i], hex[i + 1], '\0'};
|
|
||||||
char* end = nullptr;
|
|
||||||
unsigned long byte = strtoul(token, &end, 16);
|
|
||||||
if (!end || *end || byte > 0xFF)
|
|
||||||
return false;
|
|
||||||
out.push_back(static_cast<uint8_t>(byte));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto parts = SplitString(value, ',');
|
|
||||||
if (parts.empty())
|
|
||||||
return false;
|
|
||||||
out.reserve(parts.size());
|
|
||||||
for (const auto& part : parts)
|
|
||||||
{
|
|
||||||
uint8_t byte = 0;
|
|
||||||
if (!ParseByteToken(part, byte))
|
|
||||||
return false;
|
|
||||||
out.push_back(byte);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
static void AppendValueBytes(std::vector<uint8_t>& out, T value)
|
|
||||||
{
|
|
||||||
out.resize(sizeof(T));
|
|
||||||
memcpy(out.data(), &value, sizeof(T));
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool DesignerContains(CEntityIdentity* identity, const char* needle)
|
|
||||||
{
|
|
||||||
const char* designer = identity ? identity->m_designerName.String() : nullptr;
|
|
||||||
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;
|
||||||
@@ -225,106 +34,48 @@ static CEntityIdentity* FindIdentityByIndex(int index)
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static CEntityIdentity* FindControllerBySlot(CPlayerSlot slot)
|
static std::vector<std::string> SplitString(const char* value, char separator)
|
||||||
{
|
{
|
||||||
if (!g_entitySystem)
|
std::vector<std::string> out;
|
||||||
g_entitySystem = GameEntitySystem();
|
if (!value)
|
||||||
if (!g_entitySystem)
|
return out;
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
auto slotField = schema::FindField("CBasePlayerController", "m_nSplitScreenSlot");
|
const char* start = value;
|
||||||
if (!slotField.found)
|
for (const char* p = value; ; ++p)
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
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"))
|
if (*p != separator && *p != '\0')
|
||||||
continue;
|
continue;
|
||||||
uintptr_t entity = reinterpret_cast<uintptr_t>(identity->m_pInstance);
|
out.emplace_back(start, p - start);
|
||||||
int controllerSlot = *reinterpret_cast<int*>(entity + slotField.offset);
|
if (*p == '\0')
|
||||||
if (controllerSlot == slot.Get())
|
break;
|
||||||
return identity;
|
start = p + 1;
|
||||||
}
|
}
|
||||||
return nullptr;
|
return out;
|
||||||
}
|
|
||||||
|
|
||||||
static uint8_t* FieldAddress(uintptr_t entity, const char* className, const char* fieldName)
|
|
||||||
{
|
|
||||||
auto field = schema::FindField(className, fieldName);
|
|
||||||
if (!field.found)
|
|
||||||
return nullptr;
|
|
||||||
return reinterpret_cast<uint8_t*>(entity + field.offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool RequiredField(const char* className, const char* fieldName, std::string& error)
|
|
||||||
{
|
|
||||||
auto field = schema::FindField(className, fieldName);
|
|
||||||
if (!field.found)
|
|
||||||
{
|
|
||||||
error = std::string("schema field not found: ") + className + "::" + fieldName;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool ValidatePath(const OverrideRule& rule, std::string& error)
|
|
||||||
{
|
|
||||||
for (const auto& segment : rule.path)
|
|
||||||
{
|
|
||||||
auto field = schema::FindField(segment.className.c_str(), segment.fieldName.c_str());
|
|
||||||
if (!field.found)
|
|
||||||
{
|
|
||||||
error = "schema field not found";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void OverrideManager::Clear()
|
void OverrideManager::Clear()
|
||||||
{
|
{
|
||||||
for (const auto& rule : m_compiledRules)
|
for (const auto& rule : m_rules)
|
||||||
{
|
{
|
||||||
if (rule.enabled)
|
if (rule.enabled)
|
||||||
MarkEntityFieldDirtyByOffset(rule.entityIndex, rule.offset);
|
MarkEntityFieldDirtyByOffset(rule.entityIndex, rule.offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_rules.clear();
|
m_rules.clear();
|
||||||
m_compiledRules.clear();
|
m_rulesByEntity.clear();
|
||||||
m_compiledRulesByEntity.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OverrideManager::AddFromTokens(int argc, const char** argv, std::string& error)
|
void OverrideManager::Compact()
|
||||||
{
|
{
|
||||||
if (argc < 7)
|
m_rules.erase(
|
||||||
{
|
std::remove_if(m_rules.begin(), m_rules.end(), [](const CompiledOverrideRule& rule) {
|
||||||
error = "usage: sp_set <recipient|all> <entity|bot|all> <ClassName> <field> <type> <value>";
|
return !rule.enabled;
|
||||||
return false;
|
}),
|
||||||
}
|
m_rules.end());
|
||||||
|
RebuildIndex();
|
||||||
OverrideRule rule;
|
|
||||||
rule.id = m_nextId++;
|
|
||||||
rule.recipientSlot = !V_stricmp(argv[1], "all") ? -1 : atoi(argv[1]);
|
|
||||||
rule.target = argv[2];
|
|
||||||
rule.className = argv[3];
|
|
||||||
rule.fieldName = argv[4];
|
|
||||||
rule.path.push_back({rule.className, rule.fieldName});
|
|
||||||
if (!ParseType(argv[5], rule.type))
|
|
||||||
{
|
|
||||||
error = "unknown type";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
rule.bits = ParseValue(rule.type, argv[6]);
|
|
||||||
|
|
||||||
if (!ValidatePath(rule, error))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
m_rules.push_back(rule);
|
|
||||||
SPMessage("rule #%d: recipient=%d target=%s %s::%s type=%s\n", rule.id, rule.recipientSlot, rule.target.c_str(), rule.className.c_str(), rule.fieldName.c_str(), TypeName(rule.type));
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OverrideManager::ResolveFieldPath(const char* className, const char* fieldPath, int32_t& offset, int32_t& size, std::string& typeName, std::string& error) const
|
bool OverrideManager::ResolveFieldPath(const char* className, const char* fieldPath, int32_t& offset, int32_t& size, std::string& error) const
|
||||||
{
|
{
|
||||||
if (!className || !className[0] || !fieldPath || !fieldPath[0])
|
if (!className || !className[0] || !fieldPath || !fieldPath[0])
|
||||||
{
|
{
|
||||||
@@ -353,7 +104,6 @@ bool OverrideManager::ResolveFieldPath(const char* className, const char* fieldP
|
|||||||
|
|
||||||
offset += field.offset;
|
offset += field.offset;
|
||||||
size = field.size;
|
size = field.size;
|
||||||
typeName = field.typeName;
|
|
||||||
if (i + 1 < parts.size())
|
if (i + 1 < parts.size())
|
||||||
{
|
{
|
||||||
if (field.typeName.empty())
|
if (field.typeName.empty())
|
||||||
@@ -368,71 +118,13 @@ bool OverrideManager::ResolveFieldPath(const char* className, const char* fieldP
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OverrideManager::CompileValue(FieldType type, int32_t fieldSize, const char* value, std::vector<uint8_t>& out, std::string& stringValue, std::string& error) const
|
void OverrideManager::RebuildIndex()
|
||||||
{
|
{
|
||||||
out.clear();
|
m_rulesByEntity.clear();
|
||||||
stringValue.clear();
|
for (size_t i = 0; i < m_rules.size(); ++i)
|
||||||
|
|
||||||
switch (type)
|
|
||||||
{
|
{
|
||||||
case FieldType::Bool: {
|
if (m_rules[i].enabled)
|
||||||
bool b = !V_stricmp(value, "true") || atoi(value) != 0;
|
m_rulesByEntity[m_rules[i].entityIndex].push_back(i);
|
||||||
AppendValueBytes(out, b);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
case FieldType::Int8: AppendValueBytes(out, static_cast<int8_t>(strtol(value, nullptr, 0))); return true;
|
|
||||||
case FieldType::UInt8: AppendValueBytes(out, static_cast<uint8_t>(strtoul(value, nullptr, 0))); return true;
|
|
||||||
case FieldType::Int16: AppendValueBytes(out, static_cast<int16_t>(strtol(value, nullptr, 0))); return true;
|
|
||||||
case FieldType::UInt16: AppendValueBytes(out, static_cast<uint16_t>(strtoul(value, nullptr, 0))); return true;
|
|
||||||
case FieldType::Int32: AppendValueBytes(out, static_cast<int32_t>(strtol(value, nullptr, 0))); return true;
|
|
||||||
case FieldType::UInt32: AppendValueBytes(out, static_cast<uint32_t>(strtoul(value, nullptr, 0))); return true;
|
|
||||||
case FieldType::Int64: AppendValueBytes(out, static_cast<int64_t>(strtoll(value, nullptr, 0))); return true;
|
|
||||||
case FieldType::UInt64: AppendValueBytes(out, static_cast<uint64_t>(strtoull(value, nullptr, 0))); return true;
|
|
||||||
case FieldType::Float: AppendValueBytes(out, static_cast<float>(atof(value))); return true;
|
|
||||||
case FieldType::Bytes: {
|
|
||||||
if (!ParseBytes(value, out))
|
|
||||||
{
|
|
||||||
error = "bytes value must be 0xAABBCC or comma-separated byte values";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (fieldSize > 0 && static_cast<int32_t>(out.size()) > fieldSize)
|
|
||||||
{
|
|
||||||
error = "bytes value is larger than schema field";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
case FieldType::StringPtr: {
|
|
||||||
if (fieldSize > 0 && fieldSize != static_cast<int32_t>(sizeof(const char*)))
|
|
||||||
{
|
|
||||||
error = "string currently supports pointer-sized char* fields only; use bytes for fixed buffers";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
stringValue = value ? value : "";
|
|
||||||
const char* pointer = stringValue.c_str();
|
|
||||||
out.resize(sizeof(pointer));
|
|
||||||
memcpy(out.data(), &pointer, sizeof(pointer));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
error = "unsupported type";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OverrideManager::RebuildCompiledIndex()
|
|
||||||
{
|
|
||||||
m_compiledRulesByEntity.clear();
|
|
||||||
for (size_t i = 0; i < m_compiledRules.size(); ++i)
|
|
||||||
{
|
|
||||||
if (m_compiledRules[i].type == FieldType::StringPtr)
|
|
||||||
{
|
|
||||||
const char* pointer = m_compiledRules[i].stringValue.c_str();
|
|
||||||
m_compiledRules[i].value.resize(sizeof(pointer));
|
|
||||||
memcpy(m_compiledRules[i].value.data(), &pointer, sizeof(pointer));
|
|
||||||
}
|
|
||||||
if (m_compiledRules[i].enabled)
|
|
||||||
m_compiledRulesByEntity[m_compiledRules[i].entityIndex].push_back(i);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,7 +139,7 @@ bool OverrideManager::MarkEntityFieldDirtyByOffset(int entityIndex, int32_t offs
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int OverrideManager::AddCompiledRule(int recipientSlot, int entityIndex, const char* className, const char* fieldPath, const char* type, const char* value, std::string& error)
|
int OverrideManager::AddRuleBytes(int recipientSlot, int entityIndex, const char* className, const char* fieldPath, const void* value, int valueSize, std::string& error)
|
||||||
{
|
{
|
||||||
if (recipientSlot < -1 || recipientSlot >= 64)
|
if (recipientSlot < -1 || recipientSlot >= 64)
|
||||||
{
|
{
|
||||||
@@ -459,11 +151,9 @@ int OverrideManager::AddCompiledRule(int recipientSlot, int entityIndex, const c
|
|||||||
error = "entity index must be >= 0";
|
error = "entity index must be >= 0";
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
if (!value || valueSize <= 0)
|
||||||
FieldType fieldType {};
|
|
||||||
if (!ParseType(type, fieldType))
|
|
||||||
{
|
{
|
||||||
error = "unknown type";
|
error = "value bytes are required";
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -473,57 +163,33 @@ int OverrideManager::AddCompiledRule(int recipientSlot, int entityIndex, const c
|
|||||||
rule.entityIndex = entityIndex;
|
rule.entityIndex = entityIndex;
|
||||||
rule.className = className ? className : "";
|
rule.className = className ? className : "";
|
||||||
rule.fieldPath = fieldPath ? fieldPath : "";
|
rule.fieldPath = fieldPath ? fieldPath : "";
|
||||||
std::string schemaType;
|
if (!ResolveFieldPath(rule.className.c_str(), rule.fieldPath.c_str(), rule.offset, rule.size, error))
|
||||||
if (!ResolveFieldPath(rule.className.c_str(), rule.fieldPath.c_str(), rule.offset, rule.size, schemaType, error))
|
|
||||||
return 0;
|
return 0;
|
||||||
rule.type = fieldType;
|
if (rule.size > 0 && valueSize > rule.size)
|
||||||
if (!CompileValue(fieldType, rule.size, value, rule.value, rule.stringValue, error))
|
|
||||||
return 0;
|
|
||||||
if (rule.size > 0 && fieldType != FieldType::Bytes && fieldType != FieldType::StringPtr && static_cast<int32_t>(rule.value.size()) > rule.size)
|
|
||||||
{
|
{
|
||||||
error = "value type is larger than schema field";
|
error = "value is larger than schema field";
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_compiledRules.push_back(rule);
|
auto* bytes = static_cast<const uint8_t*>(value);
|
||||||
RebuildCompiledIndex();
|
rule.value.assign(bytes, bytes + valueSize);
|
||||||
|
m_rules.push_back(rule);
|
||||||
|
RebuildIndex();
|
||||||
MarkEntityFieldDirtyByOffset(entityIndex, rule.offset);
|
MarkEntityFieldDirtyByOffset(entityIndex, rule.offset);
|
||||||
SPMessage("compiled rule #%d: recipient=%d entity=%d %s::%s offset=%d size=%d schema_type=%s value_type=%s\n",
|
|
||||||
rule.id, rule.recipientSlot, rule.entityIndex, rule.className.c_str(), rule.fieldPath.c_str(), rule.offset, rule.size,
|
|
||||||
schemaType.c_str(), TypeName(rule.type));
|
|
||||||
return rule.id;
|
return rule.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OverrideManager::RemoveRule(int id)
|
bool OverrideManager::RemoveRule(int id)
|
||||||
{
|
{
|
||||||
for (auto& rule : m_compiledRules)
|
|
||||||
{
|
|
||||||
if (rule.id != id)
|
|
||||||
continue;
|
|
||||||
rule.enabled = false;
|
|
||||||
MarkEntityFieldDirtyByOffset(rule.entityIndex, rule.offset);
|
|
||||||
RebuildCompiledIndex();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& rule : m_rules)
|
for (auto& rule : m_rules)
|
||||||
{
|
{
|
||||||
if (rule.id != id)
|
if (rule.id != id)
|
||||||
continue;
|
continue;
|
||||||
rule.enabled = false;
|
rule.enabled = false;
|
||||||
|
MarkEntityFieldDirtyByOffset(rule.entityIndex, rule.offset);
|
||||||
|
RebuildIndex();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OverrideManager::MarkRuleDirty(int id)
|
|
||||||
{
|
|
||||||
for (const auto& rule : m_compiledRules)
|
|
||||||
{
|
|
||||||
if (rule.id == id && rule.enabled)
|
|
||||||
return MarkEntityFieldDirtyByOffset(rule.entityIndex, rule.offset);
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -531,374 +197,41 @@ bool OverrideManager::MarkEntityFieldDirty(int entityIndex, const char* classNam
|
|||||||
{
|
{
|
||||||
int32_t offset = 0;
|
int32_t offset = 0;
|
||||||
int32_t size = 0;
|
int32_t size = 0;
|
||||||
std::string typeName;
|
if (!ResolveFieldPath(className, fieldPath, offset, size, error))
|
||||||
if (!ResolveFieldPath(className, fieldPath, offset, size, typeName, error))
|
|
||||||
return false;
|
return false;
|
||||||
return MarkEntityFieldDirtyByOffset(entityIndex, offset);
|
return MarkEntityFieldDirtyByOffset(entityIndex, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OverrideManager::AddWeaponItemDefRule(int recipientSlot, const char* target, uint16_t itemDefinitionIndex, std::string& error)
|
|
||||||
{
|
|
||||||
OverrideRule rule;
|
|
||||||
rule.id = m_nextId++;
|
|
||||||
rule.recipientSlot = recipientSlot;
|
|
||||||
rule.target = target;
|
|
||||||
rule.className = "CEconEntity";
|
|
||||||
rule.fieldName = "m_AttributeManager.m_Item.m_iItemDefinitionIndex";
|
|
||||||
rule.path.push_back({"CEconEntity", "m_AttributeManager"});
|
|
||||||
rule.path.push_back({"CAttributeContainer", "m_Item"});
|
|
||||||
rule.path.push_back({"CEconItemView", "m_iItemDefinitionIndex"});
|
|
||||||
rule.type = FieldType::UInt16;
|
|
||||||
rule.bits = itemDefinitionIndex;
|
|
||||||
|
|
||||||
if (!ValidatePath(rule, error))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
m_rules.push_back(rule);
|
|
||||||
SPMessage("rule #%d: recipient=%d target=%s weapon item_definition_index=%u\n", rule.id, rule.recipientSlot, rule.target.c_str(), itemDefinitionIndex);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OverrideManager::SetHealthMoneyTest(bool enabled, std::string& error)
|
|
||||||
{
|
|
||||||
if (enabled)
|
|
||||||
{
|
|
||||||
if (!RequiredField("CCSPlayerController", "m_pInGameMoneyServices", error) ||
|
|
||||||
!RequiredField("CCSPlayerController_InGameMoneyServices", "m_iAccount", error) ||
|
|
||||||
!RequiredField("CBasePlayerController", "m_hPawn", error) ||
|
|
||||||
!RequiredField("CBasePlayerController", "m_nSplitScreenSlot", error) ||
|
|
||||||
!RequiredField("CBaseEntity", "m_iHealth", error) ||
|
|
||||||
!RequiredField("CBaseEntity", "m_iTeamNum", error))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_healthMoneyTest = enabled;
|
|
||||||
SPMessage("health_money_test=%d\n", m_healthMoneyTest ? 1 : 0);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OverrideManager::SetHealth42Test(bool enabled, std::string& error)
|
|
||||||
{
|
|
||||||
if (enabled && !RequiredField("CBaseEntity", "m_iHealth", error))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
m_health42Test = enabled;
|
|
||||||
SPMessage("health_42_test=%d\n", m_health42Test ? 1 : 0);
|
|
||||||
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
|
|
||||||
{
|
|
||||||
SPMessage("%zu static rules loaded; %zu dynamic rules loaded; trace=%d\n", m_rules.size(), DynamicRuleCount(), m_trace ? 1 : 0);
|
|
||||||
if (m_healthMoneyTest)
|
|
||||||
SPMessage("dynamic: teammate CBaseEntity::m_iHealth = recipient CCSPlayerController_InGameMoneyServices::m_iAccount\n");
|
|
||||||
if (m_health42Test)
|
|
||||||
{
|
|
||||||
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)
|
|
||||||
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));
|
|
||||||
for (const auto& rule : m_compiledRules)
|
|
||||||
{
|
|
||||||
if (rule.enabled)
|
|
||||||
SPMessage("#%d recipient=%d entity=%d field=%s::%s offset=%d size=%d type=%s\n",
|
|
||||||
rule.id, rule.recipientSlot, rule.entityIndex, rule.className.c_str(), rule.fieldPath.c_str(), rule.offset, rule.size, TypeName(rule.type));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uintptr_t OverrideManager::FindFirstEntityForTarget(const OverrideRule& rule) const
|
|
||||||
{
|
|
||||||
if (!g_entitySystem)
|
|
||||||
g_entitySystem = GameEntitySystem();
|
|
||||||
if (!g_entitySystem)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
if (!V_stricmp(rule.target.c_str(), "entity"))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
int visited = 0;
|
|
||||||
for (auto* identity = g_entitySystem->m_EntityList.m_pFirstActiveEntity; identity && visited < 16384; identity = identity->m_pNext, ++visited)
|
|
||||||
{
|
|
||||||
auto* entity = identity->m_pInstance;
|
|
||||||
if (!entity)
|
|
||||||
continue;
|
|
||||||
const char* designer = identity->m_designerName.String();
|
|
||||||
if (!designer)
|
|
||||||
continue;
|
|
||||||
if (!V_stricmp(rule.target.c_str(), "all") || V_stristr(designer, rule.target.c_str()))
|
|
||||||
return reinterpret_cast<uintptr_t>(entity);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t* OverrideManager::ResolveAddress(uintptr_t entity, const OverrideRule& rule) const
|
|
||||||
{
|
|
||||||
uintptr_t address = entity;
|
|
||||||
for (const auto& segment : rule.path)
|
|
||||||
{
|
|
||||||
auto field = schema::FindField(segment.className.c_str(), segment.fieldName.c_str());
|
|
||||||
if (!field.found)
|
|
||||||
return nullptr;
|
|
||||||
address += field.offset;
|
|
||||||
}
|
|
||||||
return reinterpret_cast<uint8_t*>(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OverrideManager::ApplyHealthMoneyTest(CPlayerSlot slot, std::vector<AppliedOverride>& applied)
|
|
||||||
{
|
|
||||||
if (!m_healthMoneyTest)
|
|
||||||
return;
|
|
||||||
if (!g_entitySystem)
|
|
||||||
g_entitySystem = GameEntitySystem();
|
|
||||||
if (!g_entitySystem)
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto* recipientIdentity = FindControllerBySlot(slot);
|
|
||||||
if (!recipientIdentity || !DesignerContains(recipientIdentity, "controller") || !recipientIdentity->m_pInstance)
|
|
||||||
{
|
|
||||||
if (m_trace)
|
|
||||||
SPMessage("health_money_test recipient=%d no controller\n", slot.Get());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uintptr_t recipient = reinterpret_cast<uintptr_t>(recipientIdentity->m_pInstance);
|
|
||||||
auto* moneyServicesAddress = FieldAddress(recipient, "CCSPlayerController", "m_pInGameMoneyServices");
|
|
||||||
auto* teamAddress = FieldAddress(recipient, "CBaseEntity", "m_iTeamNum");
|
|
||||||
auto* pawnHandleAddress = FieldAddress(recipient, "CBasePlayerController", "m_hPawn");
|
|
||||||
if (!moneyServicesAddress || !teamAddress || !pawnHandleAddress)
|
|
||||||
{
|
|
||||||
if (m_trace)
|
|
||||||
SPMessage("health_money_test recipient=%d missing addresses\n", slot.Get());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uintptr_t moneyServices = *reinterpret_cast<uintptr_t*>(moneyServicesAddress);
|
|
||||||
if (!moneyServices)
|
|
||||||
{
|
|
||||||
if (m_trace)
|
|
||||||
SPMessage("health_money_test recipient=%d no money services\n", slot.Get());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto* moneyAddress = FieldAddress(moneyServices, "CCSPlayerController_InGameMoneyServices", "m_iAccount");
|
|
||||||
if (!moneyAddress)
|
|
||||||
return;
|
|
||||||
|
|
||||||
int money = static_cast<int>(ReadBits(moneyAddress, FieldType::Int32));
|
|
||||||
int team = static_cast<int>(*teamAddress);
|
|
||||||
if (team < 2)
|
|
||||||
{
|
|
||||||
if (m_trace)
|
|
||||||
SPMessage("health_money_test recipient=%d invalid team=%d\n", slot.Get(), team);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int ownPawnIndex = reinterpret_cast<CEntityHandle*>(pawnHandleAddress)->GetEntryIndex();
|
|
||||||
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 || EntityIndex(identity) == ownPawnIndex)
|
|
||||||
continue;
|
|
||||||
if (!DesignerContains(identity, "player") || DesignerContains(identity, "controller"))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
uintptr_t entity = reinterpret_cast<uintptr_t>(identity->m_pInstance);
|
|
||||||
auto* targetTeamAddress = FieldAddress(entity, "CBaseEntity", "m_iTeamNum");
|
|
||||||
auto* healthAddress = FieldAddress(entity, "CBaseEntity", "m_iHealth");
|
|
||||||
if (!targetTeamAddress || !healthAddress)
|
|
||||||
continue;
|
|
||||||
if (static_cast<int>(*targetTeamAddress) != team)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
AppliedOverride saved {healthAddress, FieldType::Int32, ReadBits(healthAddress, FieldType::Int32)};
|
|
||||||
WriteBits(healthAddress, FieldType::Int32, static_cast<uint32_t>(money));
|
|
||||||
applied.push_back(saved);
|
|
||||||
++changed;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_trace)
|
|
||||||
SPMessage("health_money_test recipient=%d money=%d changed=%d\n", slot.Get(), money, changed);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<AppliedOverride> OverrideManager::ApplyGlobalHealth42()
|
|
||||||
{
|
|
||||||
std::vector<AppliedOverride> applied;
|
|
||||||
if (!m_health42Test)
|
|
||||||
return applied;
|
|
||||||
if (!g_entitySystem)
|
|
||||||
g_entitySystem = GameEntitySystem();
|
|
||||||
if (!g_entitySystem)
|
|
||||||
return applied;
|
|
||||||
|
|
||||||
auto healthField = schema::FindField("CBaseEntity", "m_iHealth");
|
|
||||||
if (!healthField.found)
|
|
||||||
return applied;
|
|
||||||
|
|
||||||
applied.reserve(16);
|
|
||||||
int visited = 0;
|
|
||||||
for (auto* identity = g_entitySystem->m_EntityList.m_pFirstActiveEntity; identity && visited < 16384; identity = identity->m_pNext, ++visited)
|
|
||||||
{
|
|
||||||
if (!identity->m_pInstance)
|
|
||||||
continue;
|
|
||||||
if (!DesignerEquals(identity, "player"))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
auto* healthAddress = reinterpret_cast<uint8_t*>(identity->m_pInstance) + healthField.offset;
|
|
||||||
|
|
||||||
AppliedOverride saved {healthAddress, FieldType::Int32, static_cast<uint32_t>(*reinterpret_cast<int*>(healthAddress))};
|
|
||||||
*reinterpret_cast<int*>(healthAddress) = 42;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_trace)
|
|
||||||
SPMessage("marked player health dirty changed=%d\n", changed);
|
|
||||||
return changed;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<AppliedOverride> OverrideManager::ApplyForRecipient(CPlayerSlot slot)
|
|
||||||
{
|
|
||||||
std::vector<AppliedOverride> applied;
|
|
||||||
ApplyHealthMoneyTest(slot, applied);
|
|
||||||
for (const auto& rule : m_rules)
|
|
||||||
{
|
|
||||||
if (!rule.enabled || (rule.recipientSlot != -1 && rule.recipientSlot != slot.Get()))
|
|
||||||
continue;
|
|
||||||
uintptr_t entity = FindFirstEntityForTarget(rule);
|
|
||||||
if (!entity)
|
|
||||||
continue;
|
|
||||||
auto* address = ResolveAddress(entity, rule);
|
|
||||||
if (!address)
|
|
||||||
continue;
|
|
||||||
AppliedOverride saved {address, rule.type, ReadBits(address, rule.type)};
|
|
||||||
WriteBits(address, rule.type, rule.bits);
|
|
||||||
applied.push_back(saved);
|
|
||||||
if (m_trace)
|
|
||||||
SPMessage("applied #%d for recipient %d at %p\n", rule.id, slot.Get(), address);
|
|
||||||
}
|
|
||||||
return applied;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<AppliedOverride> OverrideManager::ApplyForPackedEntity(CPlayerSlot recipient, int entityIndex, void* entityData)
|
std::vector<AppliedOverride> OverrideManager::ApplyForPackedEntity(CPlayerSlot recipient, int entityIndex, void* entityData)
|
||||||
{
|
{
|
||||||
std::vector<AppliedOverride> applied;
|
std::vector<AppliedOverride> applied;
|
||||||
if (!entityData || m_compiledRulesByEntity.empty())
|
if (!entityData || m_rulesByEntity.empty())
|
||||||
return applied;
|
return applied;
|
||||||
|
|
||||||
auto found = m_compiledRulesByEntity.find(entityIndex);
|
auto found = m_rulesByEntity.find(entityIndex);
|
||||||
if (found == m_compiledRulesByEntity.end())
|
if (found == m_rulesByEntity.end())
|
||||||
return applied;
|
return applied;
|
||||||
|
|
||||||
for (size_t ruleIndex : found->second)
|
for (size_t ruleIndex : found->second)
|
||||||
{
|
{
|
||||||
const auto& rule = m_compiledRules[ruleIndex];
|
const auto& rule = m_rules[ruleIndex];
|
||||||
if (!rule.enabled || (rule.recipientSlot != -1 && rule.recipientSlot != recipient.Get()))
|
if (!rule.enabled || (rule.recipientSlot != -1 && rule.recipientSlot != recipient.Get()))
|
||||||
continue;
|
continue;
|
||||||
if (rule.value.empty())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
auto* address = reinterpret_cast<uint8_t*>(entityData) + rule.offset;
|
auto* address = reinterpret_cast<uint8_t*>(entityData) + rule.offset;
|
||||||
AppliedOverride saved {};
|
AppliedOverride saved {};
|
||||||
saved.address = address;
|
saved.address = address;
|
||||||
saved.type = rule.type;
|
saved.original.resize(rule.value.size());
|
||||||
saved.originalBytes.resize(rule.value.size());
|
memcpy(saved.original.data(), address, saved.original.size());
|
||||||
memcpy(saved.originalBytes.data(), address, saved.originalBytes.size());
|
|
||||||
memcpy(address, rule.value.data(), rule.value.size());
|
memcpy(address, rule.value.data(), rule.value.size());
|
||||||
applied.push_back(saved);
|
applied.push_back(saved);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_trace && !applied.empty())
|
|
||||||
SPMessage("packed entity overrides recipient=%d entity=%d changed=%zu\n", recipient.Get(), entityIndex, applied.size());
|
|
||||||
return applied;
|
return applied;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OverrideManager::Restore(const std::vector<AppliedOverride>& applied)
|
void OverrideManager::Restore(const std::vector<AppliedOverride>& applied)
|
||||||
{
|
{
|
||||||
for (auto it = applied.rbegin(); it != applied.rend(); ++it)
|
for (auto it = applied.rbegin(); it != applied.rend(); ++it)
|
||||||
{
|
memcpy(it->address, it->original.data(), it->original.size());
|
||||||
if (!it->originalBytes.empty())
|
|
||||||
memcpy(it->address, it->originalBytes.data(), it->originalBytes.size());
|
|
||||||
else
|
|
||||||
WriteBits(it->address, it->type, it->originalBits);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,52 +3,14 @@
|
|||||||
#include "playerslot.h"
|
#include "playerslot.h"
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <array>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
enum class FieldType
|
|
||||||
{
|
|
||||||
Bool,
|
|
||||||
Int8,
|
|
||||||
UInt8,
|
|
||||||
Int16,
|
|
||||||
UInt16,
|
|
||||||
Int32,
|
|
||||||
UInt32,
|
|
||||||
Int64,
|
|
||||||
UInt64,
|
|
||||||
Float,
|
|
||||||
Bytes,
|
|
||||||
StringPtr,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct FieldPathSegment
|
|
||||||
{
|
|
||||||
std::string className;
|
|
||||||
std::string fieldName;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct OverrideRule
|
|
||||||
{
|
|
||||||
int id {};
|
|
||||||
int recipientSlot {-1};
|
|
||||||
std::string target;
|
|
||||||
std::string className;
|
|
||||||
std::string fieldName;
|
|
||||||
std::vector<FieldPathSegment> path;
|
|
||||||
FieldType type {FieldType::Int32};
|
|
||||||
uint64_t bits {};
|
|
||||||
bool enabled {true};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct AppliedOverride
|
struct AppliedOverride
|
||||||
{
|
{
|
||||||
uint8_t* address {};
|
uint8_t* address {};
|
||||||
FieldType type {};
|
std::vector<uint8_t> original;
|
||||||
uint64_t originalBits {};
|
|
||||||
std::vector<uint8_t> originalBytes;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CompiledOverrideRule
|
struct CompiledOverrideRule
|
||||||
@@ -60,9 +22,7 @@ struct CompiledOverrideRule
|
|||||||
std::string fieldPath;
|
std::string fieldPath;
|
||||||
int32_t offset {};
|
int32_t offset {};
|
||||||
int32_t size {};
|
int32_t size {};
|
||||||
FieldType type {FieldType::Int32};
|
|
||||||
std::vector<uint8_t> value;
|
std::vector<uint8_t> value;
|
||||||
std::string stringValue;
|
|
||||||
bool enabled {true};
|
bool enabled {true};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -70,52 +30,24 @@ class OverrideManager
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void Clear();
|
void Clear();
|
||||||
bool AddFromTokens(int argc, const char** argv, std::string& error);
|
void Compact();
|
||||||
int AddCompiledRule(int recipientSlot, int entityIndex, const char* className, const char* fieldPath, const char* type, const char* value, std::string& error);
|
int AddRuleBytes(int recipientSlot, int entityIndex, const char* className, const char* fieldPath, const void* value, int valueSize, std::string& error);
|
||||||
bool RemoveRule(int id);
|
bool RemoveRule(int id);
|
||||||
bool MarkRuleDirty(int id);
|
|
||||||
bool MarkEntityFieldDirty(int entityIndex, const char* className, const char* fieldPath, std::string& error);
|
bool MarkEntityFieldDirty(int entityIndex, const char* className, const char* fieldPath, std::string& error);
|
||||||
bool AddWeaponItemDefRule(int recipientSlot, const char* target, uint16_t itemDefinitionIndex, std::string& error);
|
bool Empty() const { return m_rules.empty(); }
|
||||||
bool SetHealthMoneyTest(bool enabled, std::string& error);
|
bool HasPackedOverrides() const { return !m_rulesByEntity.empty(); }
|
||||||
bool SetHealth42Test(bool enabled, std::string& error);
|
|
||||||
bool SetRecipientHealthValue(int recipientSlot, int value, std::string& error);
|
|
||||||
void ClearRecipientHealthValues();
|
|
||||||
void Dump() const;
|
|
||||||
void SetTrace(bool value) { m_trace = value; }
|
|
||||||
bool Trace() const { return m_trace; }
|
|
||||||
size_t RuleCount() const { return m_rules.size() + m_compiledRules.size(); }
|
|
||||||
size_t DynamicRuleCount() const { return (m_healthMoneyTest ? 1 : 0) + (m_health42Test ? 1 : 0); }
|
|
||||||
bool Empty() const { return m_rules.empty() && m_compiledRules.empty() && !m_healthMoneyTest && !m_health42Test; }
|
|
||||||
bool Health42Test() const { return m_health42Test; }
|
|
||||||
bool TryGetHealth42Value(CPlayerSlot recipient, int& value) const;
|
|
||||||
bool HasPackedOverrides() const { return !m_compiledRules.empty() || m_health42Test; }
|
|
||||||
|
|
||||||
std::vector<AppliedOverride> ApplyForRecipient(CPlayerSlot slot);
|
|
||||||
std::vector<AppliedOverride> ApplyForPackedEntity(CPlayerSlot recipient, int entityIndex, void* entityData);
|
std::vector<AppliedOverride> ApplyForPackedEntity(CPlayerSlot recipient, int entityIndex, void* entityData);
|
||||||
std::vector<AppliedOverride> ApplyGlobalHealth42();
|
|
||||||
int MarkPlayerHealthDirty();
|
|
||||||
void Restore(const std::vector<AppliedOverride>& applied);
|
void Restore(const std::vector<AppliedOverride>& applied);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool EntityMatches(uintptr_t entity, const OverrideRule& rule) const;
|
bool ResolveFieldPath(const char* className, const char* fieldPath, int32_t& offset, int32_t& size, std::string& error) const;
|
||||||
uintptr_t FindFirstEntityForTarget(const OverrideRule& rule) const;
|
void RebuildIndex();
|
||||||
uint8_t* ResolveAddress(uintptr_t entity, const OverrideRule& rule) const;
|
|
||||||
void ApplyHealthMoneyTest(CPlayerSlot slot, std::vector<AppliedOverride>& applied);
|
|
||||||
bool ResolveFieldPath(const char* className, const char* fieldPath, int32_t& offset, int32_t& size, std::string& typeName, std::string& error) const;
|
|
||||||
bool CompileValue(FieldType type, int32_t fieldSize, const char* value, std::vector<uint8_t>& out, std::string& stringValue, std::string& error) const;
|
|
||||||
void RebuildCompiledIndex();
|
|
||||||
bool MarkEntityFieldDirtyByOffset(int entityIndex, int32_t offset);
|
bool MarkEntityFieldDirtyByOffset(int entityIndex, int32_t offset);
|
||||||
|
|
||||||
std::vector<OverrideRule> m_rules;
|
std::vector<CompiledOverrideRule> m_rules;
|
||||||
std::vector<CompiledOverrideRule> m_compiledRules;
|
std::unordered_map<int, std::vector<size_t>> m_rulesByEntity;
|
||||||
std::unordered_map<int, std::vector<size_t>> m_compiledRulesByEntity;
|
|
||||||
int m_nextId {1};
|
int m_nextId {1};
|
||||||
bool m_trace {false};
|
|
||||||
bool m_healthMoneyTest {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;
|
||||||
|
|||||||
@@ -59,10 +59,10 @@ CGameEntitySystem* GameEntitySystem()
|
|||||||
return g_entitySystem;
|
return g_entitySystem;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" __attribute__((visibility("default"))) int SendProxy_SetOverride(int recipientSlot, int entityIndex, const char* className, const char* fieldPath, const char* type, const char* value)
|
extern "C" __attribute__((visibility("default"))) int SendProxy_SetOverride(int recipientSlot, int entityIndex, const char* className, const char* fieldPath, const void* value, int valueSize)
|
||||||
{
|
{
|
||||||
std::string error;
|
std::string error;
|
||||||
int id = g_overrides.AddCompiledRule(recipientSlot, entityIndex, className, fieldPath, type, value, error);
|
int id = g_overrides.AddRuleBytes(recipientSlot, entityIndex, className, fieldPath, value, valueSize, error);
|
||||||
if (!id)
|
if (!id)
|
||||||
{
|
{
|
||||||
SPWarning("SendProxy_SetOverride failed: %s\n", error.c_str());
|
SPWarning("SendProxy_SetOverride failed: %s\n", error.c_str());
|
||||||
@@ -151,6 +151,7 @@ bool SendProxyPlugin::Unload(char*, size_t)
|
|||||||
|
|
||||||
void SendProxyPlugin::OnLevelInit(char const* map, char const*, char const*, char const*, bool, bool)
|
void SendProxyPlugin::OnLevelInit(char const* map, char const*, char const*, char const*, bool, bool)
|
||||||
{
|
{
|
||||||
|
g_overrides.Compact();
|
||||||
g_armed = false;
|
g_armed = false;
|
||||||
SPMessage("level init %s\n", map ? map : "");
|
SPMessage("level init %s\n", map ? map : "");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user