#include "overrides.h" #include "common.h" #include "schema.h" #include "entity2/entitysystem.h" #include "entity2/entityinstance.h" #include #include extern CGameEntitySystem* g_entitySystem; extern CGameEntitySystem* GameEntitySystem(); OverrideManager g_overrides; 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 std::vector SplitString(const char* value, char separator) { std::vector 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 std::string NormalizeSchemaClassName(std::string typeName) { while (!typeName.empty() && (typeName.back() == '*' || typeName.back() == '&' || typeName.back() == ' ')) typeName.pop_back(); while (!typeName.empty() && typeName.front() == ' ') typeName.erase(typeName.begin()); constexpr const char* classPrefix = "class "; constexpr const char* structPrefix = "struct "; if (typeName.rfind(classPrefix, 0) == 0) typeName.erase(0, strlen(classPrefix)); else if (typeName.rfind(structPrefix, 0) == 0) typeName.erase(0, strlen(structPrefix)); return typeName; } static bool IsPointerSchemaType(const std::string& typeName) { for (auto it = typeName.rbegin(); it != typeName.rend(); ++it) { if (*it == ' ') continue; return *it == '*'; } return false; } void OverrideManager::Clear() { for (const auto& rule : m_rules) { if (rule.enabled) MarkEntityFieldDirtyByOffset(rule.entityIndex, rule.offset); } m_rules.clear(); m_rulesByEntity.clear(); } void OverrideManager::Compact() { m_rules.erase( std::remove_if(m_rules.begin(), m_rules.end(), [](const CompiledOverrideRule& rule) { return !rule.enabled; }), m_rules.end()); RebuildIndex(); } bool OverrideManager::ResolveFieldPath(const char* className, const char* fieldPath, int32_t& offset, int32_t& size, std::vector& addressSteps, std::string& error) const { if (!className || !className[0] || !fieldPath || !fieldPath[0]) { error = "class and field path are required"; return false; } auto parts = SplitString(fieldPath, '.'); if (parts.empty()) { error = "field path is empty"; return false; } offset = 0; size = 0; addressSteps.clear(); std::string currentClass = className; for (size_t i = 0; i < parts.size(); ++i) { auto field = schema::FindField(currentClass.c_str(), parts[i].c_str()); if (!field.found) { error = "schema field not found: " + currentClass + "::" + parts[i]; return false; } size = field.size; FieldAddressStep step {}; step.offset = field.offset; step.dereference = i + 1 < parts.size() && IsPointerSchemaType(field.typeName); addressSteps.push_back(step); if (i + 1 < parts.size()) { if (field.typeName.empty()) { error = "intermediate field has no schema type name"; return false; } currentClass = NormalizeSchemaClassName(field.typeName); } } if (!addressSteps.empty()) offset = addressSteps.front().offset; return true; } static uint8_t* ResolveRuleAddress(const CompiledOverrideRule& rule, void* entityData) { auto* address = static_cast(entityData); for (const auto& step : rule.addressSteps) { address += step.offset; if (!step.dereference) continue; address = *reinterpret_cast(address); if (!address) return nullptr; } return address; } static uint8_t* ResolveAddressSteps(const std::vector& steps, void* entityData) { auto* address = static_cast(entityData); for (const auto& step : steps) { address += step.offset; if (!step.dereference) continue; address = *reinterpret_cast(address); if (!address) return nullptr; } return address; } static void SaveAndWrite(std::vector& applied, uint8_t* address, const void* value, size_t size) { AppliedOverride saved {}; saved.address = address; saved.original.resize(size); memcpy(saved.original.data(), address, saved.original.size()); memcpy(address, value, size); applied.push_back(saved); } static void SaveAndWriteIfChanged(std::vector& applied, uint8_t* address, const void* value, size_t size) { if (memcmp(address, value, size) == 0) return; SaveAndWrite(applied, address, value, size); } static void ApplyVectorFirstFromField(const CompiledOverrideRule& rule, void* entityData, std::vector& applied) { auto* vectorAddress = ResolveAddressSteps(rule.addressSteps, entityData); auto* sourceAddress = ResolveAddressSteps(rule.sourceAddressSteps, entityData); if (!vectorAddress || !sourceAddress) return; const uint32_t sourceHandle = *reinterpret_cast(sourceAddress); constexpr uint32_t invalidHandle = 0xffffffff; const int32_t count = *reinterpret_cast(vectorAddress); auto* elements = *reinterpret_cast(vectorAddress + 8); if (!rule.debugLogged) { uint32_t h0 = elements && count > 0 ? *reinterpret_cast(elements) : invalidHandle; uint32_t h1 = elements && count > 1 ? *reinterpret_cast(elements + 4) : invalidHandle; uint32_t h2 = elements && count > 2 ? *reinterpret_cast(elements + 8) : invalidHandle; const auto* raw = reinterpret_cast(vectorAddress); SPMessage("vector apply rule %d recipient=%d entity=%d vec=%p src=%p count=%d elems=%p active=%08x first=[%08x %08x %08x] raw=%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", rule.id, rule.recipientSlot, rule.entityIndex, vectorAddress, sourceAddress, count, elements, sourceHandle, h0, h1, h2, raw[0], raw[1], raw[2], raw[3], raw[4], raw[5], raw[6], raw[7], raw[8], raw[9], raw[10], raw[11], raw[12], raw[13], raw[14], raw[15], raw[16], raw[17], raw[18], raw[19], raw[20], raw[21], raw[22], raw[23]); rule.debugLogged = true; } if (count < 0 || count > 128) return; auto* countAddress = reinterpret_cast(&*reinterpret_cast(vectorAddress)); if (sourceHandle == invalidHandle) { const int32_t emptyCount = 0; SaveAndWriteIfChanged(applied, countAddress, &emptyCount, sizeof(emptyCount)); return; } if (!elements) return; const int32_t activeOnlyCount = 1; SaveAndWriteIfChanged(applied, countAddress, &activeOnlyCount, sizeof(activeOnlyCount)); SaveAndWriteIfChanged(applied, elements, &sourceHandle, sizeof(sourceHandle)); } void OverrideManager::RebuildIndex() { m_rulesByEntity.clear(); for (size_t i = 0; i < m_rules.size(); ++i) { if (m_rules[i].enabled) m_rulesByEntity[m_rules[i].entityIndex].push_back(i); } } bool OverrideManager::MarkEntityFieldDirtyByOffset(int entityIndex, int32_t offset) { auto* identity = FindIdentityByIndex(entityIndex); if (!identity || !identity->m_pInstance) return false; NetworkStateChangedData data(static_cast(offset)); identity->m_pInstance->NetworkStateChanged(data); return true; } bool OverrideManager::MarkEntityFullDirty(int entityIndex) { auto* identity = FindIdentityByIndex(entityIndex); if (!identity || !identity->m_pInstance) return false; NetworkStateChangedData data(true); identity->m_pInstance->NetworkStateChanged(data); return true; } 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) { error = "recipient slot must be all/-1 or 0..63"; return 0; } if (entityIndex < 0) { error = "entity index must be >= 0"; return 0; } if (!value || valueSize <= 0) { error = "value bytes are required"; return 0; } CompiledOverrideRule rule; rule.id = m_nextId++; rule.recipientSlot = recipientSlot; rule.entityIndex = entityIndex; rule.className = className ? className : ""; rule.fieldPath = fieldPath ? fieldPath : ""; if (!ResolveFieldPath(rule.className.c_str(), rule.fieldPath.c_str(), rule.offset, rule.size, rule.addressSteps, error)) return 0; if (rule.size > 0 && valueSize > rule.size) { error = "value is larger than schema field"; return 0; } auto* bytes = static_cast(value); rule.value.assign(bytes, bytes + valueSize); m_rules.push_back(rule); if (rule.fieldPath.find("m_hMyWeapons") != std::string::npos) { SPMessage("rule %d recipient=%d entity=%d %s::%s size=%d valueSize=%d topOffset=%d steps=%zu\n", rule.id, rule.recipientSlot, rule.entityIndex, rule.className.c_str(), rule.fieldPath.c_str(), rule.size, valueSize, rule.offset, rule.addressSteps.size()); } RebuildIndex(); MarkEntityFullDirty(entityIndex); return rule.id; } int OverrideManager::AddRuleVectorFirstFromField(int recipientSlot, int entityIndex, const char* className, const char* vectorFieldPath, const char* sourceFieldPath, std::string& error) { if (recipientSlot < -1 || recipientSlot >= 64) { error = "recipient slot must be all/-1 or 0..63"; return 0; } if (entityIndex < 0) { error = "entity index must be >= 0"; return 0; } CompiledOverrideRule rule; rule.id = m_nextId++; rule.kind = OverrideRuleKind::VectorFirstFromField; rule.recipientSlot = recipientSlot; rule.entityIndex = entityIndex; rule.className = className ? className : ""; rule.fieldPath = vectorFieldPath ? vectorFieldPath : ""; rule.sourceFieldPath = sourceFieldPath ? sourceFieldPath : ""; if (!ResolveFieldPath(rule.className.c_str(), rule.fieldPath.c_str(), rule.offset, rule.size, rule.addressSteps, error)) return 0; int32_t sourceOffset = 0; if (!ResolveFieldPath(rule.className.c_str(), rule.sourceFieldPath.c_str(), sourceOffset, rule.sourceSize, rule.sourceAddressSteps, error)) return 0; if (rule.sourceSize > 0 && rule.sourceSize < 4) { error = "source field is smaller than a handle"; return 0; } m_rules.push_back(rule); SPMessage("vector-first rule %d recipient=%d entity=%d %s::%s <- %s vectorSize=%d sourceSize=%d topOffset=%d steps=%zu sourceSteps=%zu\n", rule.id, rule.recipientSlot, rule.entityIndex, rule.className.c_str(), rule.fieldPath.c_str(), rule.sourceFieldPath.c_str(), rule.size, rule.sourceSize, rule.offset, rule.addressSteps.size(), rule.sourceAddressSteps.size()); RebuildIndex(); MarkEntityFullDirty(entityIndex); return rule.id; } bool OverrideManager::RemoveRule(int id) { for (auto& rule : m_rules) { if (rule.id != id) continue; rule.enabled = false; MarkEntityFullDirty(rule.entityIndex); RebuildIndex(); return true; } return false; } bool OverrideManager::MarkEntityFieldDirty(int entityIndex, const char* className, const char* fieldPath, std::string& error) { int32_t offset = 0; int32_t size = 0; std::vector addressSteps; if (!ResolveFieldPath(className, fieldPath, offset, size, addressSteps, error)) return false; return MarkEntityFieldDirtyByOffset(entityIndex, offset); } std::vector OverrideManager::ApplyForPackedEntity(CPlayerSlot recipient, int entityIndex, void* entityData) { std::vector applied; if (!entityData || m_rulesByEntity.empty()) return applied; auto found = m_rulesByEntity.find(entityIndex); if (found == m_rulesByEntity.end()) return applied; int effectiveRecipient = recipient.Get(); int uniqueRuleRecipient = -2; for (size_t ruleIndex : found->second) { const auto& rule = m_rules[ruleIndex]; if (!rule.enabled || rule.recipientSlot < 0) continue; if (uniqueRuleRecipient == -2) uniqueRuleRecipient = rule.recipientSlot; else if (uniqueRuleRecipient != rule.recipientSlot) { uniqueRuleRecipient = -2; break; } } if ((effectiveRecipient < 0 || effectiveRecipient == 0 || effectiveRecipient == 1) && uniqueRuleRecipient >= 0) { static int fallbackLogs = 0; if (fallbackLogs < 64) { SPMessage("entity=%d unresolved recipient current=%d using unique rule recipient=%d\n", entityIndex, effectiveRecipient, uniqueRuleRecipient); ++fallbackLogs; } effectiveRecipient = uniqueRuleRecipient; } for (size_t ruleIndex : found->second) { const auto& rule = m_rules[ruleIndex]; if (!rule.enabled) continue; if (rule.recipientSlot != -1 && rule.recipientSlot != effectiveRecipient) { if (!rule.recipientMismatchLogged) { SPMessage("rule %d entity=%d matched packed entity but skipped recipient rule=%d current=%d field=%s::%s\n", rule.id, entityIndex, rule.recipientSlot, effectiveRecipient, rule.className.c_str(), rule.fieldPath.c_str()); rule.recipientMismatchLogged = true; } continue; } if (rule.kind == OverrideRuleKind::VectorFirstFromField) { ApplyVectorFirstFromField(rule, entityData, applied); continue; } auto* address = ResolveRuleAddress(rule, entityData); if (!address) continue; SaveAndWrite(applied, address, rule.value.data(), rule.value.size()); } return applied; } void OverrideManager::Restore(const std::vector& applied) { for (auto it = applied.rbegin(); it != applied.rend(); ++it) memcpy(it->address, it->original.data(), it->original.size()); }