initial commit -- v1 code
This commit is contained in:
463
src/overrides.cpp
Normal file
463
src/overrides.cpp
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user