remake - v2

This commit is contained in:
2026-08-10 14:22:03 +02:00
parent f1a5250613
commit b0e8588bef
6 changed files with 533 additions and 39 deletions

View File

@@ -8,6 +8,7 @@
#include <cstdlib>
#include <cstring>
#include <sstream>
extern CGameEntitySystem* g_entitySystem;
extern CGameEntitySystem* GameEntitySystem();
@@ -26,6 +27,10 @@ static bool ParseType(const char* s, FieldType& out)
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;
}
@@ -61,6 +66,9 @@ static uint64_t ReadBits(uint8_t* address, FieldType type)
memcpy(&bits, address, sizeof(bits));
return bits;
}
case FieldType::Bytes:
case FieldType::StringPtr:
return 0;
}
return 0;
}
@@ -83,6 +91,9 @@ static void WriteBits(uint8_t* address, FieldType type, uint64_t bits)
memcpy(address, &narrowed, sizeof(narrowed));
break;
}
case FieldType::Bytes:
case FieldType::StringPtr:
break;
}
}
@@ -100,10 +111,87 @@ static const char* TypeName(FieldType type)
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;
@@ -196,7 +284,14 @@ static bool ValidatePath(const OverrideRule& rule, std::string& error)
void OverrideManager::Clear()
{
for (const auto& rule : m_compiledRules)
{
if (rule.enabled)
MarkEntityFieldDirtyByOffset(rule.entityIndex, rule.offset);
}
m_rules.clear();
m_compiledRules.clear();
m_compiledRulesByEntity.clear();
}
bool OverrideManager::AddFromTokens(int argc, const char** argv, std::string& error)
@@ -229,6 +324,219 @@ bool OverrideManager::AddFromTokens(int argc, const char** argv, std::string& er
return true;
}
bool OverrideManager::ResolveFieldPath(const char* className, const char* fieldPath, int32_t& offset, int32_t& size, std::string& typeName, 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;
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;
}
offset += field.offset;
size = field.size;
typeName = field.typeName;
if (i + 1 < parts.size())
{
if (field.typeName.empty())
{
error = "intermediate field has no schema type name";
return false;
}
currentClass = field.typeName;
}
}
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
{
out.clear();
stringValue.clear();
switch (type)
{
case FieldType::Bool: {
bool b = !V_stricmp(value, "true") || atoi(value) != 0;
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);
}
}
bool OverrideManager::MarkEntityFieldDirtyByOffset(int entityIndex, int32_t offset)
{
auto* identity = FindIdentityByIndex(entityIndex);
if (!identity || !identity->m_pInstance)
return false;
NetworkStateChangedData data(static_cast<uint32>(offset));
identity->m_pInstance->NetworkStateChanged(data);
return true;
}
int OverrideManager::AddCompiledRule(int recipientSlot, int entityIndex, const char* className, const char* fieldPath, const char* type, const char* value, 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;
}
FieldType fieldType {};
if (!ParseType(type, fieldType))
{
error = "unknown type";
return 0;
}
CompiledOverrideRule rule;
rule.id = m_nextId++;
rule.recipientSlot = recipientSlot;
rule.entityIndex = entityIndex;
rule.className = className ? className : "";
rule.fieldPath = fieldPath ? fieldPath : "";
std::string schemaType;
if (!ResolveFieldPath(rule.className.c_str(), rule.fieldPath.c_str(), rule.offset, rule.size, schemaType, error))
return 0;
rule.type = fieldType;
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";
return 0;
}
m_compiledRules.push_back(rule);
RebuildCompiledIndex();
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;
}
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)
{
if (rule.id != id)
continue;
rule.enabled = false;
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;
}
bool OverrideManager::MarkEntityFieldDirty(int entityIndex, const char* className, const char* fieldPath, std::string& error)
{
int32_t offset = 0;
int32_t size = 0;
std::string typeName;
if (!ResolveFieldPath(className, fieldPath, offset, size, typeName, error))
return false;
return MarkEntityFieldDirtyByOffset(entityIndex, offset);
}
bool OverrideManager::AddWeaponItemDefRule(int recipientSlot, const char* target, uint16_t itemDefinitionIndex, std::string& error)
{
OverrideRule rule;
@@ -345,6 +653,12 @@ void OverrideManager::Dump() const
}
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
@@ -545,8 +859,46 @@ std::vector<AppliedOverride> OverrideManager::ApplyForRecipient(CPlayerSlot slot
return applied;
}
std::vector<AppliedOverride> OverrideManager::ApplyForPackedEntity(CPlayerSlot recipient, int entityIndex, void* entityData)
{
std::vector<AppliedOverride> applied;
if (!entityData || m_compiledRulesByEntity.empty())
return applied;
auto found = m_compiledRulesByEntity.find(entityIndex);
if (found == m_compiledRulesByEntity.end())
return applied;
for (size_t ruleIndex : found->second)
{
const auto& rule = m_compiledRules[ruleIndex];
if (!rule.enabled || (rule.recipientSlot != -1 && rule.recipientSlot != recipient.Get()))
continue;
if (rule.value.empty())
continue;
auto* address = reinterpret_cast<uint8_t*>(entityData) + rule.offset;
AppliedOverride saved {};
saved.address = address;
saved.type = rule.type;
saved.originalBytes.resize(rule.value.size());
memcpy(saved.originalBytes.data(), address, saved.originalBytes.size());
memcpy(address, rule.value.data(), rule.value.size());
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;
}
void OverrideManager::Restore(const std::vector<AppliedOverride>& applied)
{
for (auto it = applied.rbegin(); it != applied.rend(); ++it)
WriteBits(it->address, it->type, it->originalBits);
{
if (!it->originalBytes.empty())
memcpy(it->address, it->originalBytes.data(), it->originalBytes.size());
else
WriteBits(it->address, it->type, it->originalBits);
}
}