initial commit -- v1 code

This commit is contained in:
2026-08-10 14:21:07 +02:00
commit 1ecb7f777e
25 changed files with 2472 additions and 0 deletions

463
src/overrides.cpp Normal file
View File

@@ -0,0 +1,463 @@
#include "overrides.h"
#include "common.h"
#include "schema.h"
#include "entity2/entitysystem.h"
#include "entityhandle.h"
#include <cstdlib>
#include <cstring>
extern CGameEntitySystem* g_entitySystem;
extern CGameEntitySystem* GameEntitySystem();
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; }
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;
}
}
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;
}
}
}
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";
}
return "unknown";
}
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 CEntityIdentity* FindIdentityByIndex(int index)
{
if (!g_entitySystem)
g_entitySystem = GameEntitySystem();
if (!g_entitySystem)
return nullptr;
int visited = 0;
for (auto* identity = g_entitySystem->m_EntityList.m_pFirstActiveEntity; identity && visited < 16384; identity = identity->m_pNext, ++visited)
{
if (EntityIndex(identity) == index)
return identity;
}
return nullptr;
}
static CEntityIdentity* FindControllerBySlot(CPlayerSlot slot)
{
if (!g_entitySystem)
g_entitySystem = GameEntitySystem();
if (!g_entitySystem)
return nullptr;
auto slotField = schema::FindField("CBasePlayerController", "m_nSplitScreenSlot");
if (!slotField.found)
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"))
continue;
uintptr_t entity = reinterpret_cast<uintptr_t>(identity->m_pInstance);
int controllerSlot = *reinterpret_cast<int*>(entity + slotField.offset);
if (controllerSlot == slot.Get())
return identity;
}
return nullptr;
}
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()
{
m_rules.clear();
}
bool OverrideManager::AddFromTokens(int argc, const char** argv, std::string& error)
{
if (argc < 7)
{
error = "usage: sp_set <recipient|all> <entity|bot|all> <ClassName> <field> <type> <value>";
return false;
}
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::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;
}
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 player CBaseEntity::m_iHealth = 42\n");
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));
}
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);
}
void OverrideManager::ApplyHealth42Test(CPlayerSlot slot, std::vector<AppliedOverride>& applied)
{
if (!m_health42Test)
return;
if (!g_entitySystem)
g_entitySystem = GameEntitySystem();
if (!g_entitySystem)
return;
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)
continue;
if (!DesignerContains(identity, "player") || DesignerContains(identity, "controller"))
continue;
uintptr_t entity = reinterpret_cast<uintptr_t>(identity->m_pInstance);
auto* healthAddress = FieldAddress(entity, "CBaseEntity", "m_iHealth");
if (!healthAddress)
continue;
AppliedOverride saved {healthAddress, FieldType::Int32, ReadBits(healthAddress, FieldType::Int32)};
WriteBits(healthAddress, FieldType::Int32, 42);
applied.push_back(saved);
++changed;
}
if (m_trace)
SPMessage("health_42_test recipient=%d changed=%d\n", slot.Get(), changed);
}
std::vector<AppliedOverride> OverrideManager::ApplyForRecipient(CPlayerSlot slot)
{
std::vector<AppliedOverride> applied;
ApplyHealthMoneyTest(slot, applied);
ApplyHealth42Test(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;
}
void OverrideManager::Restore(const std::vector<AppliedOverride>& applied)
{
for (auto it = applied.rbegin(); it != applied.rend(); ++it)
WriteBits(it->address, it->type, it->originalBits);
}