remake - v2
This commit is contained in:
@@ -36,6 +36,7 @@ static uint64_t g_packChanged = 0;
|
||||
static uint64_t g_packEntityCalls = 0;
|
||||
static uint64_t g_packEntitySpoofs = 0;
|
||||
static uint64_t g_packEntityScopedWrites = 0;
|
||||
static uint64_t g_packEntityGenericSpoofs = 0;
|
||||
static uint64_t g_packEntitySelfSkips = 0;
|
||||
static uint64_t g_packEntityNoRecipient = 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;
|
||||
|
||||
if (!g_enabled || !g_armed || !entityData)
|
||||
{
|
||||
g_packEntity(self, arg1, entityIndex, entityData, arg4, arg5, arg6, arg7, arg8, arg9);
|
||||
return;
|
||||
}
|
||||
|
||||
CEntityIdentity* identity = PlayerIdentityFromInstance(entityData);
|
||||
if (!identity)
|
||||
if (!g_enabled || !g_armed || !entityData || !g_overrides.HasPackedOverrides())
|
||||
{
|
||||
g_packEntity(self, arg1, entityIndex, entityData, arg4, arg5, arg6, arg7, arg8, arg9);
|
||||
return;
|
||||
}
|
||||
|
||||
CPlayerSlot recipient = g_currentRecipient;
|
||||
int spoofHealth = 42;
|
||||
if (!g_overrides.TryGetHealth42Value(recipient, spoofHealth))
|
||||
{
|
||||
g_packEntity(self, arg1, entityIndex, entityData, arg4, arg5, arg6, arg7, arg8, arg9);
|
||||
return;
|
||||
}
|
||||
auto applied = g_overrides.ApplyForPackedEntity(recipient, entityIndex, entityData);
|
||||
g_packEntityGenericSpoofs += applied.size();
|
||||
|
||||
bool spoofedHealth = false;
|
||||
int savedHealth = 0;
|
||||
int* healthAddress = nullptr;
|
||||
int spoofHealth = 42;
|
||||
if (g_overrides.TryGetHealth42Value(recipient, spoofHealth))
|
||||
{
|
||||
CEntityIdentity* identity = PlayerIdentityFromInstance(entityData);
|
||||
if (identity)
|
||||
{
|
||||
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)
|
||||
else
|
||||
{
|
||||
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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g_packEntity(self, arg1, entityIndex, entityData, arg4, arg5, arg6, arg7, arg8, arg9);
|
||||
if (spoofedHealth)
|
||||
*healthAddress = savedHealth;
|
||||
g_overrides.Restore(applied);
|
||||
}
|
||||
|
||||
static bool InitPackEntityDetour(CGameConfig* config)
|
||||
@@ -303,7 +304,7 @@ void ShutdownSnapshotDetour()
|
||||
|
||||
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_snapshotApplied),
|
||||
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_packEntityCalls),
|
||||
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_packEntitySelfSkips),
|
||||
static_cast<unsigned long long>(g_packEntityNoRecipient),
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
if (!it->originalBytes.empty())
|
||||
memcpy(it->address, it->originalBytes.data(), it->originalBytes.size());
|
||||
else
|
||||
WriteBits(it->address, it->type, it->originalBits);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
enum class FieldType
|
||||
@@ -19,6 +20,8 @@ enum class FieldType
|
||||
Int64,
|
||||
UInt64,
|
||||
Float,
|
||||
Bytes,
|
||||
StringPtr,
|
||||
};
|
||||
|
||||
struct FieldPathSegment
|
||||
@@ -45,6 +48,22 @@ struct AppliedOverride
|
||||
uint8_t* address {};
|
||||
FieldType type {};
|
||||
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
|
||||
@@ -52,6 +71,10 @@ class OverrideManager
|
||||
public:
|
||||
void Clear();
|
||||
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 SetHealthMoneyTest(bool enabled, std::string& error);
|
||||
bool SetHealth42Test(bool enabled, std::string& error);
|
||||
@@ -60,13 +83,15 @@ public:
|
||||
void Dump() const;
|
||||
void SetTrace(bool value) { m_trace = value; }
|
||||
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); }
|
||||
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 TryGetHealth42Value(CPlayerSlot recipient, int& value) const;
|
||||
bool HasPackedOverrides() const { return !m_compiledRules.empty() || m_health42Test; }
|
||||
|
||||
std::vector<AppliedOverride> ApplyForRecipient(CPlayerSlot slot);
|
||||
std::vector<AppliedOverride> ApplyForPackedEntity(CPlayerSlot recipient, int entityIndex, void* entityData);
|
||||
std::vector<AppliedOverride> ApplyGlobalHealth42();
|
||||
int MarkPlayerHealthDirty();
|
||||
void Restore(const std::vector<AppliedOverride>& applied);
|
||||
@@ -76,8 +101,14 @@ private:
|
||||
uintptr_t FindFirstEntityForTarget(const OverrideRule& rule) const;
|
||||
uint8_t* ResolveAddress(uintptr_t entity, const OverrideRule& rule) const;
|
||||
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<CompiledOverrideRule> m_compiledRules;
|
||||
std::unordered_map<int, std::vector<size_t>> m_compiledRulesByEntity;
|
||||
int m_nextId {1};
|
||||
bool m_trace {false};
|
||||
bool m_healthMoneyTest {false};
|
||||
|
||||
@@ -178,6 +178,63 @@ CON_COMMAND_F(sp_clear, "Clear SendProxy rules", FCVAR_NONE)
|
||||
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)
|
||||
{
|
||||
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());
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (args.ArgC() < 3)
|
||||
|
||||
@@ -55,6 +55,14 @@ SchemaField schema::FindField(const char* className, const char* fieldName)
|
||||
continue;
|
||||
result.offset = field.m_nSingleInheritanceOffset;
|
||||
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;
|
||||
g_cache[key] = result;
|
||||
return result;
|
||||
@@ -87,7 +95,12 @@ void schema::DumpFields(const char* className, const char* contains)
|
||||
const auto& field = info->m_pFields[i];
|
||||
if (contains && contains[0] && !V_stristr(field.m_pszName, contains))
|
||||
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;
|
||||
}
|
||||
SPMessage("%s fields printed=%d filter=%s\n", className, printed, contains && contains[0] ? contains : "<none>");
|
||||
|
||||
@@ -8,8 +8,10 @@ class ISchemaSystem;
|
||||
struct SchemaField
|
||||
{
|
||||
int32_t offset {};
|
||||
int32_t size {};
|
||||
bool networked {};
|
||||
bool found {};
|
||||
std::string typeName;
|
||||
};
|
||||
|
||||
namespace schema
|
||||
|
||||
Reference in New Issue
Block a user