remake - v2
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user