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

@@ -36,6 +36,7 @@ static uint64_t g_packChanged = 0;
static uint64_t g_packEntityCalls = 0; static uint64_t g_packEntityCalls = 0;
static uint64_t g_packEntitySpoofs = 0; static uint64_t g_packEntitySpoofs = 0;
static uint64_t g_packEntityScopedWrites = 0; static uint64_t g_packEntityScopedWrites = 0;
static uint64_t g_packEntityGenericSpoofs = 0;
static uint64_t g_packEntitySelfSkips = 0; static uint64_t g_packEntitySelfSkips = 0;
static uint64_t g_packEntityNoRecipient = 0; static uint64_t g_packEntityNoRecipient = 0;
static uint64_t g_fullUpdateWrites = 0; static uint64_t g_fullUpdateWrites = 0;
@@ -162,51 +163,51 @@ static void Detour_PackEntity(void* self, void* arg1, int entityIndex, void* ent
{ {
++g_packEntityCalls; ++g_packEntityCalls;
if (!g_enabled || !g_armed || !entityData) if (!g_enabled || !g_armed || !entityData || !g_overrides.HasPackedOverrides())
{
g_packEntity(self, arg1, entityIndex, entityData, arg4, arg5, arg6, arg7, arg8, arg9);
return;
}
CEntityIdentity* identity = PlayerIdentityFromInstance(entityData);
if (!identity)
{ {
g_packEntity(self, arg1, entityIndex, entityData, arg4, arg5, arg6, arg7, arg8, arg9); g_packEntity(self, arg1, entityIndex, entityData, arg4, arg5, arg6, arg7, arg8, arg9);
return; return;
} }
CPlayerSlot recipient = g_currentRecipient; CPlayerSlot recipient = g_currentRecipient;
auto applied = g_overrides.ApplyForPackedEntity(recipient, entityIndex, entityData);
g_packEntityGenericSpoofs += applied.size();
bool spoofedHealth = false;
int savedHealth = 0;
int* healthAddress = nullptr;
int spoofHealth = 42; int spoofHealth = 42;
if (!g_overrides.TryGetHealth42Value(recipient, spoofHealth)) if (g_overrides.TryGetHealth42Value(recipient, spoofHealth))
{ {
g_packEntity(self, arg1, entityIndex, entityData, arg4, arg5, arg6, arg7, arg8, arg9); CEntityIdentity* identity = PlayerIdentityFromInstance(entityData);
return; if (identity)
{
if (recipient.Get() < 0)
++g_packEntityNoRecipient;
else if (OwnerSlotForPawn(identity) == recipient.Get())
{
++g_packEntitySelfSkips;
}
else
{
auto healthField = schema::FindField("CBaseEntity", "m_iHealth");
if (healthField.found && healthField.offset >= 0)
{
healthAddress = reinterpret_cast<int*>(reinterpret_cast<uint8_t*>(entityData) + healthField.offset);
savedHealth = *healthAddress;
*healthAddress = spoofHealth;
spoofedHealth = true;
++g_packEntitySpoofs;
++g_packEntityScopedWrites;
}
}
}
} }
if (recipient.Get() < 0)
++g_packEntityNoRecipient;
else if (OwnerSlotForPawn(identity) == recipient.Get())
{
++g_packEntitySelfSkips;
g_packEntity(self, arg1, entityIndex, entityData, arg4, arg5, arg6, arg7, arg8, arg9);
return;
}
auto healthField = schema::FindField("CBaseEntity", "m_iHealth");
if (!healthField.found || healthField.offset < 0)
{
g_packEntity(self, arg1, entityIndex, entityData, arg4, arg5, arg6, arg7, arg8, arg9);
return;
}
auto* healthAddress = reinterpret_cast<int*>(reinterpret_cast<uint8_t*>(entityData) + healthField.offset);
int savedHealth = *healthAddress;
*healthAddress = spoofHealth;
++g_packEntitySpoofs;
++g_packEntityScopedWrites;
g_packEntity(self, arg1, entityIndex, entityData, arg4, arg5, arg6, arg7, arg8, arg9); g_packEntity(self, arg1, entityIndex, entityData, arg4, arg5, arg6, arg7, arg8, arg9);
*healthAddress = savedHealth; if (spoofedHealth)
*healthAddress = savedHealth;
g_overrides.Restore(applied);
} }
static bool InitPackEntityDetour(CGameConfig* config) static bool InitPackEntityDetour(CGameConfig* config)
@@ -303,7 +304,7 @@ void ShutdownSnapshotDetour()
void DumpDetourStats() void DumpDetourStats()
{ {
SPMessage("hooks: snapshot calls=%llu applied=%llu changed=%llu; pack calls=%llu applied=%llu changed=%llu; pack_entity calls=%llu spoofs=%llu scoped_writes=%llu self_skips=%llu no_recipient=%llu; fullupdate writes=%llu budget=%d\n", SPMessage("hooks: snapshot calls=%llu applied=%llu changed=%llu; pack calls=%llu applied=%llu changed=%llu; pack_entity calls=%llu health_spoofs=%llu generic_spoofs=%llu scoped_writes=%llu self_skips=%llu no_recipient=%llu; fullupdate writes=%llu budget=%d\n",
static_cast<unsigned long long>(g_snapshotCalls), static_cast<unsigned long long>(g_snapshotCalls),
static_cast<unsigned long long>(g_snapshotApplied), static_cast<unsigned long long>(g_snapshotApplied),
static_cast<unsigned long long>(g_snapshotChanged), static_cast<unsigned long long>(g_snapshotChanged),
@@ -312,6 +313,7 @@ void DumpDetourStats()
static_cast<unsigned long long>(g_packChanged), static_cast<unsigned long long>(g_packChanged),
static_cast<unsigned long long>(g_packEntityCalls), static_cast<unsigned long long>(g_packEntityCalls),
static_cast<unsigned long long>(g_packEntitySpoofs), static_cast<unsigned long long>(g_packEntitySpoofs),
static_cast<unsigned long long>(g_packEntityGenericSpoofs),
static_cast<unsigned long long>(g_packEntityScopedWrites), static_cast<unsigned long long>(g_packEntityScopedWrites),
static_cast<unsigned long long>(g_packEntitySelfSkips), static_cast<unsigned long long>(g_packEntitySelfSkips),
static_cast<unsigned long long>(g_packEntityNoRecipient), static_cast<unsigned long long>(g_packEntityNoRecipient),

View File

@@ -8,6 +8,7 @@
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <sstream>
extern CGameEntitySystem* g_entitySystem; extern CGameEntitySystem* g_entitySystem;
extern CGameEntitySystem* GameEntitySystem(); 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, "int64")) { out = FieldType::Int64; return true; }
if (!V_stricmp(s, "uint64")) { out = FieldType::UInt64; 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, "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; return false;
} }
@@ -61,6 +66,9 @@ static uint64_t ReadBits(uint8_t* address, FieldType type)
memcpy(&bits, address, sizeof(bits)); memcpy(&bits, address, sizeof(bits));
return bits; return bits;
} }
case FieldType::Bytes:
case FieldType::StringPtr:
return 0;
} }
return 0; return 0;
} }
@@ -83,6 +91,9 @@ static void WriteBits(uint8_t* address, FieldType type, uint64_t bits)
memcpy(address, &narrowed, sizeof(narrowed)); memcpy(address, &narrowed, sizeof(narrowed));
break; 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::Int64: return "int64";
case FieldType::UInt64: return "uint64"; case FieldType::UInt64: return "uint64";
case FieldType::Float: return "float"; case FieldType::Float: return "float";
case FieldType::Bytes: return "bytes";
case FieldType::StringPtr: return "string";
} }
return "unknown"; 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) static bool DesignerContains(CEntityIdentity* identity, const char* needle)
{ {
const char* designer = identity ? identity->m_designerName.String() : nullptr; 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() void OverrideManager::Clear()
{ {
for (const auto& rule : m_compiledRules)
{
if (rule.enabled)
MarkEntityFieldDirtyByOffset(rule.entityIndex, rule.offset);
}
m_rules.clear(); m_rules.clear();
m_compiledRules.clear();
m_compiledRulesByEntity.clear();
} }
bool OverrideManager::AddFromTokens(int argc, const char** argv, std::string& error) 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; 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) bool OverrideManager::AddWeaponItemDefRule(int recipientSlot, const char* target, uint16_t itemDefinitionIndex, std::string& error)
{ {
OverrideRule rule; OverrideRule rule;
@@ -345,6 +653,12 @@ void OverrideManager::Dump() const
} }
for (const auto& rule : m_rules) 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)); 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 uintptr_t OverrideManager::FindFirstEntityForTarget(const OverrideRule& rule) const
@@ -545,8 +859,46 @@ std::vector<AppliedOverride> OverrideManager::ApplyForRecipient(CPlayerSlot slot
return applied; 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) void OverrideManager::Restore(const std::vector<AppliedOverride>& applied)
{ {
for (auto it = applied.rbegin(); it != applied.rend(); ++it) 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);
}
} }

View File

@@ -5,6 +5,7 @@
#include <cstdint> #include <cstdint>
#include <array> #include <array>
#include <string> #include <string>
#include <unordered_map>
#include <vector> #include <vector>
enum class FieldType enum class FieldType
@@ -19,6 +20,8 @@ enum class FieldType
Int64, Int64,
UInt64, UInt64,
Float, Float,
Bytes,
StringPtr,
}; };
struct FieldPathSegment struct FieldPathSegment
@@ -45,6 +48,22 @@ struct AppliedOverride
uint8_t* address {}; uint8_t* address {};
FieldType type {}; FieldType type {};
uint64_t originalBits {}; uint64_t originalBits {};
std::vector<uint8_t> originalBytes;
};
struct CompiledOverrideRule
{
int id {};
int recipientSlot {-1};
int entityIndex {-1};
std::string className;
std::string fieldPath;
int32_t offset {};
int32_t size {};
FieldType type {FieldType::Int32};
std::vector<uint8_t> value;
std::string stringValue;
bool enabled {true};
}; };
class OverrideManager class OverrideManager
@@ -52,6 +71,10 @@ class OverrideManager
public: public:
void Clear(); void Clear();
bool AddFromTokens(int argc, const char** argv, std::string& error); bool AddFromTokens(int argc, const char** argv, std::string& error);
int AddCompiledRule(int recipientSlot, int entityIndex, const char* className, const char* fieldPath, const char* type, const char* value, std::string& error);
bool RemoveRule(int id);
bool MarkRuleDirty(int id);
bool MarkEntityFieldDirty(int entityIndex, const char* className, const char* fieldPath, std::string& error);
bool AddWeaponItemDefRule(int recipientSlot, const char* target, uint16_t itemDefinitionIndex, std::string& error); bool AddWeaponItemDefRule(int recipientSlot, const char* target, uint16_t itemDefinitionIndex, std::string& error);
bool SetHealthMoneyTest(bool enabled, std::string& error); bool SetHealthMoneyTest(bool enabled, std::string& error);
bool SetHealth42Test(bool enabled, std::string& error); bool SetHealth42Test(bool enabled, std::string& error);
@@ -60,13 +83,15 @@ public:
void Dump() const; void Dump() const;
void SetTrace(bool value) { m_trace = value; } void SetTrace(bool value) { m_trace = value; }
bool Trace() const { return m_trace; } bool Trace() const { return m_trace; }
size_t RuleCount() const { return m_rules.size(); } size_t RuleCount() const { return m_rules.size() + m_compiledRules.size(); }
size_t DynamicRuleCount() const { return (m_healthMoneyTest ? 1 : 0) + (m_health42Test ? 1 : 0); } size_t DynamicRuleCount() const { return (m_healthMoneyTest ? 1 : 0) + (m_health42Test ? 1 : 0); }
bool Empty() const { return m_rules.empty() && !m_healthMoneyTest && !m_health42Test; } bool Empty() const { return m_rules.empty() && m_compiledRules.empty() && !m_healthMoneyTest && !m_health42Test; }
bool Health42Test() const { return m_health42Test; } bool Health42Test() const { return m_health42Test; }
bool TryGetHealth42Value(CPlayerSlot recipient, int& value) const; bool TryGetHealth42Value(CPlayerSlot recipient, int& value) const;
bool HasPackedOverrides() const { return !m_compiledRules.empty() || m_health42Test; }
std::vector<AppliedOverride> ApplyForRecipient(CPlayerSlot slot); std::vector<AppliedOverride> ApplyForRecipient(CPlayerSlot slot);
std::vector<AppliedOverride> ApplyForPackedEntity(CPlayerSlot recipient, int entityIndex, void* entityData);
std::vector<AppliedOverride> ApplyGlobalHealth42(); std::vector<AppliedOverride> ApplyGlobalHealth42();
int MarkPlayerHealthDirty(); int MarkPlayerHealthDirty();
void Restore(const std::vector<AppliedOverride>& applied); void Restore(const std::vector<AppliedOverride>& applied);
@@ -76,8 +101,14 @@ private:
uintptr_t FindFirstEntityForTarget(const OverrideRule& rule) const; uintptr_t FindFirstEntityForTarget(const OverrideRule& rule) const;
uint8_t* ResolveAddress(uintptr_t entity, const OverrideRule& rule) const; uint8_t* ResolveAddress(uintptr_t entity, const OverrideRule& rule) const;
void ApplyHealthMoneyTest(CPlayerSlot slot, std::vector<AppliedOverride>& applied); void ApplyHealthMoneyTest(CPlayerSlot slot, std::vector<AppliedOverride>& applied);
bool ResolveFieldPath(const char* className, const char* fieldPath, int32_t& offset, int32_t& size, std::string& typeName, std::string& error) const;
bool CompileValue(FieldType type, int32_t fieldSize, const char* value, std::vector<uint8_t>& out, std::string& stringValue, std::string& error) const;
void RebuildCompiledIndex();
bool MarkEntityFieldDirtyByOffset(int entityIndex, int32_t offset);
std::vector<OverrideRule> m_rules; std::vector<OverrideRule> m_rules;
std::vector<CompiledOverrideRule> m_compiledRules;
std::unordered_map<int, std::vector<size_t>> m_compiledRulesByEntity;
int m_nextId {1}; int m_nextId {1};
bool m_trace {false}; bool m_trace {false};
bool m_healthMoneyTest {false}; bool m_healthMoneyTest {false};

View File

@@ -178,6 +178,63 @@ CON_COMMAND_F(sp_clear, "Clear SendProxy rules", FCVAR_NONE)
SPMessage("rules cleared\n"); SPMessage("rules cleared\n");
} }
CON_COMMAND_F(sp_set_exact, "sp_set_exact <recipient|all> <entity_index> <ClassName> <field.path> <type> <value>", FCVAR_NONE)
{
if (args.ArgC() < 7)
{
SPWarning("usage: sp_set_exact <recipient|all> <entity_index> <ClassName> <field.path> <type> <value>\n");
return;
}
int recipientSlot = !V_stricmp(args[1], "all") ? -1 : atoi(args[1]);
int entityIndex = atoi(args[2]);
std::string error;
int id = g_overrides.AddCompiledRule(recipientSlot, entityIndex, args[3], args[4], args[5], args[6], error);
if (!id)
{
SPWarning("%s\n", error.c_str());
return;
}
g_enabled = true;
g_armed = true;
SPMessage("enabled=1 armed=1 rule_id=%d\n", id);
}
CON_COMMAND_F(sp_unset, "sp_unset <rule_id>", FCVAR_NONE)
{
if (args.ArgC() < 2)
{
SPWarning("usage: sp_unset <rule_id>\n");
return;
}
int id = atoi(args[1]);
if (!g_overrides.RemoveRule(id))
{
SPWarning("rule %d not found\n", id);
return;
}
SPMessage("rule %d removed\n", id);
}
CON_COMMAND_F(sp_dirty, "sp_dirty <entity_index> <ClassName> <field.path>", FCVAR_NONE)
{
if (args.ArgC() < 4)
{
SPWarning("usage: sp_dirty <entity_index> <ClassName> <field.path>\n");
return;
}
std::string error;
if (!g_overrides.MarkEntityFieldDirty(atoi(args[1]), args[2], args[3], error))
{
SPWarning("%s\n", error.empty() ? "failed to mark field dirty" : error.c_str());
return;
}
SPMessage("marked entity %d %s::%s dirty\n", atoi(args[1]), args[2], args[3]);
}
CON_COMMAND_F(sp_set, "sp_set <recipient|all> <entity|bot|all> <ClassName> <field> <type> <value>", FCVAR_NONE) CON_COMMAND_F(sp_set, "sp_set <recipient|all> <entity|bot|all> <ClassName> <field> <type> <value>", FCVAR_NONE)
{ {
const char* argv[8] {}; const char* argv[8] {};
@@ -188,6 +245,43 @@ CON_COMMAND_F(sp_set, "sp_set <recipient|all> <entity|bot|all> <ClassName> <fiel
SPWarning("%s\n", error.c_str()); SPWarning("%s\n", error.c_str());
} }
extern "C" __attribute__((visibility("default"))) int SendProxy_SetOverride(int recipientSlot, int entityIndex, const char* className, const char* fieldPath, const char* type, const char* value)
{
std::string error;
int id = g_overrides.AddCompiledRule(recipientSlot, entityIndex, className, fieldPath, type, value, error);
if (!id)
{
SPWarning("SendProxy_SetOverride failed: %s\n", error.c_str());
return 0;
}
g_enabled = true;
g_armed = true;
return id;
}
extern "C" __attribute__((visibility("default"))) bool SendProxy_RemoveOverride(int ruleId)
{
return g_overrides.RemoveRule(ruleId);
}
extern "C" __attribute__((visibility("default"))) void SendProxy_ClearOverrides()
{
g_overrides.Clear();
}
extern "C" __attribute__((visibility("default"))) bool SendProxy_MarkDirty(int entityIndex, const char* className, const char* fieldPath)
{
std::string error;
if (!g_overrides.MarkEntityFieldDirty(entityIndex, className, fieldPath, error))
{
if (!error.empty())
SPWarning("SendProxy_MarkDirty failed: %s\n", error.c_str());
return false;
}
return true;
}
CON_COMMAND_F(sp_weapon_test, "sp_weapon_test <weapon_entity_substring|all> <item_definition_index>", FCVAR_NONE) CON_COMMAND_F(sp_weapon_test, "sp_weapon_test <weapon_entity_substring|all> <item_definition_index>", FCVAR_NONE)
{ {
if (args.ArgC() < 3) if (args.ArgC() < 3)

View File

@@ -55,6 +55,14 @@ SchemaField schema::FindField(const char* className, const char* fieldName)
continue; continue;
result.offset = field.m_nSingleInheritanceOffset; result.offset = field.m_nSingleInheritanceOffset;
result.networked = IsNetworked(field); result.networked = IsNetworked(field);
if (field.m_pType)
{
int size = 0;
uint8 alignment = 0;
if (field.m_pType->GetSizeAndAlignment(size, alignment))
result.size = size;
result.typeName = field.m_pType->m_sTypeName.String();
}
result.found = true; result.found = true;
g_cache[key] = result; g_cache[key] = result;
return result; return result;
@@ -87,7 +95,12 @@ void schema::DumpFields(const char* className, const char* contains)
const auto& field = info->m_pFields[i]; const auto& field = info->m_pFields[i];
if (contains && contains[0] && !V_stristr(field.m_pszName, contains)) if (contains && contains[0] && !V_stristr(field.m_pszName, contains))
continue; continue;
SPMessage("%s::%s offset=%d networked=%d\n", className, field.m_pszName, field.m_nSingleInheritanceOffset, IsNetworked(field) ? 1 : 0); const char* typeName = field.m_pType ? field.m_pType->m_sTypeName.String() : "";
int size = 0;
uint8 alignment = 0;
if (field.m_pType)
field.m_pType->GetSizeAndAlignment(size, alignment);
SPMessage("%s::%s offset=%d size=%d type=%s networked=%d\n", className, field.m_pszName, field.m_nSingleInheritanceOffset, size, typeName ? typeName : "", IsNetworked(field) ? 1 : 0);
++printed; ++printed;
} }
SPMessage("%s fields printed=%d filter=%s\n", className, printed, contains && contains[0] ? contains : "<none>"); SPMessage("%s fields printed=%d filter=%s\n", className, printed, contains && contains[0] ? contains : "<none>");

View File

@@ -8,8 +8,10 @@ class ISchemaSystem;
struct SchemaField struct SchemaField
{ {
int32_t offset {}; int32_t offset {};
int32_t size {};
bool networked {}; bool networked {};
bool found {}; bool found {};
std::string typeName;
}; };
namespace schema namespace schema