diff --git a/README.md b/README.md index 9239961..77e5211 100644 --- a/README.md +++ b/README.md @@ -57,11 +57,10 @@ The C# wrapper is in: examples/CounterStrikeSharp/SendProxyNative.cs ``` -It loads the native addon with `NativeLibrary.Load` and exposes byte-oriented and typed helpers. In a CSS plugin, resolve the native library from the normal addons layout: +It loads the native addon with `NativeLibrary.Load` and exposes byte-oriented and typed helpers. In a CSS plugin, resolve the native library from `Server.GameDirectory`: ```csharp -string addonsDirectory = Path.GetFullPath(Path.Combine(ModuleDirectory, "..", "..", "..")); -string libraryPath = Path.Combine(addonsDirectory, "sendproxy", "bin", "linuxsteamrt64", "sendproxy.so"); +string libraryPath = Path.Combine(Server.GameDirectory, "addons", "sendproxy", "bin", "linuxsteamrt64", "sendproxy.so"); var sendProxy = new SendProxyNative(libraryPath); int ruleId = sendProxy.SetInt32( diff --git a/examples/CounterStrikeSharp/SendProxyWeaponVisibility/SendProxyWeaponVisibilityPlugin.cs b/examples/CounterStrikeSharp/SendProxyWeaponVisibility/SendProxyWeaponVisibilityPlugin.cs index c667bfc..a1996ad 100644 --- a/examples/CounterStrikeSharp/SendProxyWeaponVisibility/SendProxyWeaponVisibilityPlugin.cs +++ b/examples/CounterStrikeSharp/SendProxyWeaponVisibility/SendProxyWeaponVisibilityPlugin.cs @@ -213,8 +213,7 @@ public sealed class SendProxyWeaponVisibilityPlugin : BasePlugin private string GetSendProxyLibraryPath() { - string addonsDirectory = Path.GetFullPath(Path.Combine(ModuleDirectory, "..", "..", "..")); - string libraryPath = Path.Combine(addonsDirectory, "sendproxy", "bin", "linuxsteamrt64", "sendproxy.so"); + string libraryPath = Path.Combine(Server.GameDirectory, "addons", "sendproxy", "bin", "linuxsteamrt64", "sendproxy.so"); if (!File.Exists(libraryPath)) throw new FileNotFoundException("SendProxy Metamod addon was not found. Install it under game/csgo/addons/sendproxy.", libraryPath); return libraryPath; diff --git a/src/detour.cpp b/src/detour.cpp index 82b3833..cfac8ca 100644 --- a/src/detour.cpp +++ b/src/detour.cpp @@ -1,22 +1,16 @@ #include "detour.h" #include "common.h" -#include "entity2/entitysystem.h" -#include "entity2/entityinstance.h" -#include "entityhandle.h" #include "gameconfig.h" #include "networksystem/inetworkserializer.h" #include "overrides.h" #include "playerslot.h" -#include "schema.h" #include extern CGameConfig* g_gameConfig; -extern CGameEntitySystem* g_entitySystem; extern bool g_enabled; extern bool g_armed; -extern CGameEntitySystem* GameEntitySystem(); using SendSnapshotFn = void (*)(void*, void*); using PackEntitiesFn = void (*)(void*, void*, int, void*, void*, void*, void*, void*, void*); @@ -28,17 +22,9 @@ static funchook_t* g_snapshotHook = nullptr; static funchook_t* g_packHook = nullptr; static funchook_t* g_packEntityHook = nullptr; static uint64_t g_snapshotCalls = 0; -static uint64_t g_snapshotApplied = 0; -static uint64_t g_snapshotChanged = 0; static uint64_t g_packCalls = 0; -static uint64_t g_packApplied = 0; -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; static int g_fullUpdateBudget = 0; static thread_local CPlayerSlot g_currentRecipient(-1); @@ -81,21 +67,7 @@ static void Detour_SendSnapshot(void* client, void* snapshot) } } - if (!g_enabled || !g_armed || !client || g_overrides.Empty()) - { - g_sendSnapshot(client, snapshot); - return; - } - - CPlayerSlot slot = SlotFromClient(client); - auto applied = g_overrides.ApplyForRecipient(slot); - if (!applied.empty()) - { - ++g_snapshotApplied; - g_snapshotChanged += applied.size(); - } g_sendSnapshot(client, snapshot); - g_overrides.Restore(applied); } static void Detour_PackEntities(void* self, void* arg1, int arg2, void* arg3, void* arg4, void* arg5, void* arg6, void* arg7, void* arg8) @@ -104,61 +76,6 @@ static void Detour_PackEntities(void* self, void* arg1, int arg2, void* arg3, vo g_packEntities(self, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); } -static CEntityIdentity* PlayerIdentityFromInstance(void* instance) -{ - if (!instance) - return nullptr; - - auto* entity = static_cast(instance); - CEntityIdentity* identity = entity->m_pEntity; - const char* designer = identity ? identity->m_designerName.String() : nullptr; - return designer && !V_stricmp(designer, "player") ? identity : nullptr; -} - -static bool DesignerContains(CEntityIdentity* identity, const char* needle) -{ - const char* designer = identity ? identity->m_designerName.String() : nullptr; - return designer && V_stristr(designer, needle); -} - -static int EntityIndex(CEntityIdentity* identity) -{ - return identity ? identity->m_EHandle.GetEntryIndex() : -1; -} - -static int OwnerSlotForPawn(CEntityIdentity* pawnIdentity) -{ - if (!pawnIdentity) - return -1; - if (!g_entitySystem) - g_entitySystem = GameEntitySystem(); - if (!g_entitySystem) - return -1; - - auto pawnField = schema::FindField("CBasePlayerController", "m_hPawn"); - auto slotField = schema::FindField("CBasePlayerController", "m_nSplitScreenSlot"); - if (!pawnField.found || !slotField.found) - return -1; - - int pawnIndex = EntityIndex(pawnIdentity); - int visited = 0; - for (auto* identity = g_entitySystem->m_EntityList.m_pFirstActiveEntity; identity && visited < 16384; identity = identity->m_pNext, ++visited) - { - if (!identity->m_pInstance || !DesignerContains(identity, "controller")) - continue; - - auto* entity = reinterpret_cast(identity->m_pInstance); - int controlledPawnIndex = reinterpret_cast(entity + pawnField.offset)->GetEntryIndex(); - if (controlledPawnIndex != pawnIndex) - continue; - - int slot = *reinterpret_cast(entity + slotField.offset); - return slot >= 0 && slot < 64 ? slot : -1; - } - - return -1; -} - static void Detour_PackEntity(void* self, void* arg1, int entityIndex, void* entityData, void* arg4, void* arg5, void* arg6, void* arg7, void* arg8, void* arg9) { ++g_packEntityCalls; @@ -172,41 +89,7 @@ static void Detour_PackEntity(void* self, void* arg1, int entityIndex, void* ent 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; - 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; - } - else - { - auto healthField = schema::FindField("CBaseEntity", "m_iHealth"); - if (healthField.found && healthField.offset >= 0) - { - healthAddress = reinterpret_cast(reinterpret_cast(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); } @@ -215,7 +98,7 @@ static bool InitPackEntityDetour(CGameConfig* config) void* packEntityTarget = config->ResolveSignature("CNetworkGameServer_PackEntity"); if (!packEntityTarget) { - SPWarning("CNetworkGameServer_PackEntity signature missing; scoped health hook disabled\n"); + SPWarning("CNetworkGameServer_PackEntity signature missing; override hook disabled\n"); return true; } @@ -257,7 +140,7 @@ bool InitSnapshotDetour(CGameConfig* config) void* packTarget = config->ResolveSignature("CNetworkGameServer_PackEntities_Normal"); if (!packTarget) { - SPWarning("CNetworkGameServer_PackEntities_Normal signature missing; pre-pack health_42 hook disabled\n"); + SPWarning("CNetworkGameServer_PackEntities_Normal signature missing; pack counter hook disabled\n"); return true; } @@ -304,19 +187,11 @@ void ShutdownSnapshotDetour() void DumpDetourStats() { - 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", + SPMessage("hooks: snapshot calls=%llu; pack calls=%llu; pack_entity calls=%llu generic_spoofs=%llu; fullupdate writes=%llu budget=%d\n", static_cast(g_snapshotCalls), - static_cast(g_snapshotApplied), - static_cast(g_snapshotChanged), static_cast(g_packCalls), - static_cast(g_packApplied), - static_cast(g_packChanged), static_cast(g_packEntityCalls), - static_cast(g_packEntitySpoofs), static_cast(g_packEntityGenericSpoofs), - static_cast(g_packEntityScopedWrites), - static_cast(g_packEntitySelfSkips), - static_cast(g_packEntityNoRecipient), static_cast(g_fullUpdateWrites), g_fullUpdateBudget); } diff --git a/src/overrides.cpp b/src/overrides.cpp index b23e05a..a8d5141 100644 --- a/src/overrides.cpp +++ b/src/overrides.cpp @@ -4,206 +4,15 @@ #include "schema.h" #include "entity2/entitysystem.h" #include "entity2/entityinstance.h" -#include "entityhandle.h" -#include #include -#include +#include extern CGameEntitySystem* g_entitySystem; extern CGameEntitySystem* GameEntitySystem(); OverrideManager g_overrides; -static bool ParseType(const char* s, FieldType& out) -{ - if (!V_stricmp(s, "bool")) { out = FieldType::Bool; return true; } - if (!V_stricmp(s, "int8")) { out = FieldType::Int8; return true; } - if (!V_stricmp(s, "uint8")) { out = FieldType::UInt8; return true; } - if (!V_stricmp(s, "int16")) { out = FieldType::Int16; return true; } - if (!V_stricmp(s, "uint16")) { out = FieldType::UInt16; return true; } - if (!V_stricmp(s, "int") || !V_stricmp(s, "int32")) { out = FieldType::Int32; return true; } - if (!V_stricmp(s, "uint") || !V_stricmp(s, "uint32")) { out = FieldType::UInt32; 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, "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; -} - -static uint64_t ParseValue(FieldType type, const char* value) -{ - if (type == FieldType::Float) - { - float f = static_cast(atof(value)); - uint32_t bits; - memcpy(&bits, &f, sizeof(bits)); - return bits; - } - if (type == FieldType::Bool) - return (!V_stricmp(value, "true") || atoi(value) != 0) ? 1 : 0; - return strtoull(value, nullptr, 0); -} - -static uint64_t ReadBits(uint8_t* address, FieldType type) -{ - switch (type) - { - case FieldType::Bool: return *reinterpret_cast(address) ? 1 : 0; - case FieldType::Int8: return static_cast(*reinterpret_cast(address)); - case FieldType::UInt8: return *reinterpret_cast(address); - case FieldType::Int16: return static_cast(*reinterpret_cast(address)); - case FieldType::UInt16: return *reinterpret_cast(address); - case FieldType::Int32: return static_cast(*reinterpret_cast(address)); - case FieldType::UInt32: return *reinterpret_cast(address); - case FieldType::Int64: return static_cast(*reinterpret_cast(address)); - case FieldType::UInt64: return *reinterpret_cast(address); - case FieldType::Float: { - uint32_t bits; - memcpy(&bits, address, sizeof(bits)); - return bits; - } - case FieldType::Bytes: - case FieldType::StringPtr: - return 0; - } - return 0; -} - -static void WriteBits(uint8_t* address, FieldType type, uint64_t bits) -{ - switch (type) - { - case FieldType::Bool: *reinterpret_cast(address) = bits != 0; break; - case FieldType::Int8: *reinterpret_cast(address) = static_cast(bits); break; - case FieldType::UInt8: *reinterpret_cast(address) = static_cast(bits); break; - case FieldType::Int16: *reinterpret_cast(address) = static_cast(bits); break; - case FieldType::UInt16: *reinterpret_cast(address) = static_cast(bits); break; - case FieldType::Int32: *reinterpret_cast(address) = static_cast(bits); break; - case FieldType::UInt32: *reinterpret_cast(address) = static_cast(bits); break; - case FieldType::Int64: *reinterpret_cast(address) = static_cast(bits); break; - case FieldType::UInt64: *reinterpret_cast(address) = bits; break; - case FieldType::Float: { - uint32_t narrowed = static_cast(bits); - memcpy(address, &narrowed, sizeof(narrowed)); - break; - } - case FieldType::Bytes: - case FieldType::StringPtr: - break; - } -} - -static const char* TypeName(FieldType type) -{ - switch (type) - { - case FieldType::Bool: return "bool"; - case FieldType::Int8: return "int8"; - case FieldType::UInt8: return "uint8"; - case FieldType::Int16: return "int16"; - case FieldType::UInt16: return "uint16"; - case FieldType::Int32: return "int32"; - case FieldType::UInt32: return "uint32"; - 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 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 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(value); - return true; -} - -static bool ParseBytes(const char* value, std::vector& 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(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 -static void AppendValueBytes(std::vector& 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; - return designer && V_stristr(designer, needle); -} - -static bool DesignerEquals(CEntityIdentity* identity, const char* value) -{ - const char* designer = identity ? identity->m_designerName.String() : nullptr; - return designer && !V_stricmp(designer, value); -} - static int EntityIndex(CEntityIdentity* identity) { return identity ? identity->m_EHandle.GetEntryIndex() : -1; @@ -225,106 +34,48 @@ static CEntityIdentity* FindIdentityByIndex(int index) return nullptr; } -static CEntityIdentity* FindControllerBySlot(CPlayerSlot slot) +static std::vector SplitString(const char* value, char separator) { - if (!g_entitySystem) - g_entitySystem = GameEntitySystem(); - if (!g_entitySystem) - return nullptr; + std::vector out; + if (!value) + return out; - auto slotField = schema::FindField("CBasePlayerController", "m_nSplitScreenSlot"); - if (!slotField.found) - return nullptr; - - int visited = 0; - for (auto* identity = g_entitySystem->m_EntityList.m_pFirstActiveEntity; identity && visited < 16384; identity = identity->m_pNext, ++visited) + const char* start = value; + for (const char* p = value; ; ++p) { - if (!identity->m_pInstance || !DesignerContains(identity, "controller")) + if (*p != separator && *p != '\0') continue; - uintptr_t entity = reinterpret_cast(identity->m_pInstance); - int controllerSlot = *reinterpret_cast(entity + slotField.offset); - if (controllerSlot == slot.Get()) - return identity; + out.emplace_back(start, p - start); + if (*p == '\0') + break; + start = p + 1; } - return nullptr; -} - -static uint8_t* FieldAddress(uintptr_t entity, const char* className, const char* fieldName) -{ - auto field = schema::FindField(className, fieldName); - if (!field.found) - return nullptr; - return reinterpret_cast(entity + field.offset); -} - -static bool RequiredField(const char* className, const char* fieldName, std::string& error) -{ - auto field = schema::FindField(className, fieldName); - if (!field.found) - { - error = std::string("schema field not found: ") + className + "::" + fieldName; - return false; - } - return true; -} - -static bool ValidatePath(const OverrideRule& rule, std::string& error) -{ - for (const auto& segment : rule.path) - { - auto field = schema::FindField(segment.className.c_str(), segment.fieldName.c_str()); - if (!field.found) - { - error = "schema field not found"; - return false; - } - } - return true; + return out; } void OverrideManager::Clear() { - for (const auto& rule : m_compiledRules) + for (const auto& rule : m_rules) { if (rule.enabled) MarkEntityFieldDirtyByOffset(rule.entityIndex, rule.offset); } + m_rules.clear(); - m_compiledRules.clear(); - m_compiledRulesByEntity.clear(); + m_rulesByEntity.clear(); } -bool OverrideManager::AddFromTokens(int argc, const char** argv, std::string& error) +void OverrideManager::Compact() { - if (argc < 7) - { - error = "usage: sp_set "; - return false; - } - - OverrideRule rule; - rule.id = m_nextId++; - rule.recipientSlot = !V_stricmp(argv[1], "all") ? -1 : atoi(argv[1]); - rule.target = argv[2]; - rule.className = argv[3]; - rule.fieldName = argv[4]; - rule.path.push_back({rule.className, rule.fieldName}); - if (!ParseType(argv[5], rule.type)) - { - error = "unknown type"; - return false; - } - rule.bits = ParseValue(rule.type, argv[6]); - - if (!ValidatePath(rule, error)) - return false; - - m_rules.push_back(rule); - SPMessage("rule #%d: recipient=%d target=%s %s::%s type=%s\n", rule.id, rule.recipientSlot, rule.target.c_str(), rule.className.c_str(), rule.fieldName.c_str(), TypeName(rule.type)); - return true; + 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::string& typeName, std::string& error) const +bool OverrideManager::ResolveFieldPath(const char* className, const char* fieldPath, int32_t& offset, int32_t& size, std::string& error) const { if (!className || !className[0] || !fieldPath || !fieldPath[0]) { @@ -353,7 +104,6 @@ bool OverrideManager::ResolveFieldPath(const char* className, const char* fieldP offset += field.offset; size = field.size; - typeName = field.typeName; if (i + 1 < parts.size()) { if (field.typeName.empty()) @@ -368,71 +118,13 @@ bool OverrideManager::ResolveFieldPath(const char* className, const char* fieldP return true; } -bool OverrideManager::CompileValue(FieldType type, int32_t fieldSize, const char* value, std::vector& out, std::string& stringValue, std::string& error) const +void OverrideManager::RebuildIndex() { - out.clear(); - stringValue.clear(); - - switch (type) + m_rulesByEntity.clear(); + for (size_t i = 0; i < m_rules.size(); ++i) { - case FieldType::Bool: { - bool b = !V_stricmp(value, "true") || atoi(value) != 0; - AppendValueBytes(out, b); - return true; - } - case FieldType::Int8: AppendValueBytes(out, static_cast(strtol(value, nullptr, 0))); return true; - case FieldType::UInt8: AppendValueBytes(out, static_cast(strtoul(value, nullptr, 0))); return true; - case FieldType::Int16: AppendValueBytes(out, static_cast(strtol(value, nullptr, 0))); return true; - case FieldType::UInt16: AppendValueBytes(out, static_cast(strtoul(value, nullptr, 0))); return true; - case FieldType::Int32: AppendValueBytes(out, static_cast(strtol(value, nullptr, 0))); return true; - case FieldType::UInt32: AppendValueBytes(out, static_cast(strtoul(value, nullptr, 0))); return true; - case FieldType::Int64: AppendValueBytes(out, static_cast(strtoll(value, nullptr, 0))); return true; - case FieldType::UInt64: AppendValueBytes(out, static_cast(strtoull(value, nullptr, 0))); return true; - case FieldType::Float: AppendValueBytes(out, static_cast(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(out.size()) > fieldSize) - { - error = "bytes value is larger than schema field"; - return false; - } - return true; - } - case FieldType::StringPtr: { - if (fieldSize > 0 && fieldSize != static_cast(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); + if (m_rules[i].enabled) + m_rulesByEntity[m_rules[i].entityIndex].push_back(i); } } @@ -447,7 +139,7 @@ bool OverrideManager::MarkEntityFieldDirtyByOffset(int entityIndex, int32_t offs return true; } -int OverrideManager::AddCompiledRule(int recipientSlot, int entityIndex, const char* className, const char* fieldPath, const char* type, const char* value, std::string& error) +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) { @@ -459,11 +151,9 @@ int OverrideManager::AddCompiledRule(int recipientSlot, int entityIndex, const c error = "entity index must be >= 0"; return 0; } - - FieldType fieldType {}; - if (!ParseType(type, fieldType)) + if (!value || valueSize <= 0) { - error = "unknown type"; + error = "value bytes are required"; return 0; } @@ -473,57 +163,33 @@ int OverrideManager::AddCompiledRule(int recipientSlot, int entityIndex, const c 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)) + if (!ResolveFieldPath(rule.className.c_str(), rule.fieldPath.c_str(), rule.offset, rule.size, 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(rule.value.size()) > rule.size) + if (rule.size > 0 && valueSize > rule.size) { - error = "value type is larger than schema field"; + error = "value is larger than schema field"; return 0; } - m_compiledRules.push_back(rule); - RebuildCompiledIndex(); + auto* bytes = static_cast(value); + rule.value.assign(bytes, bytes + valueSize); + m_rules.push_back(rule); + RebuildIndex(); 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; + MarkEntityFieldDirtyByOffset(rule.entityIndex, rule.offset); + RebuildIndex(); 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; } @@ -531,374 +197,41 @@ bool OverrideManager::MarkEntityFieldDirty(int entityIndex, const char* classNam { int32_t offset = 0; int32_t size = 0; - std::string typeName; - if (!ResolveFieldPath(className, fieldPath, offset, size, typeName, error)) + if (!ResolveFieldPath(className, fieldPath, offset, size, error)) return false; return MarkEntityFieldDirtyByOffset(entityIndex, offset); } -bool OverrideManager::AddWeaponItemDefRule(int recipientSlot, const char* target, uint16_t itemDefinitionIndex, std::string& error) -{ - OverrideRule rule; - rule.id = m_nextId++; - rule.recipientSlot = recipientSlot; - rule.target = target; - rule.className = "CEconEntity"; - rule.fieldName = "m_AttributeManager.m_Item.m_iItemDefinitionIndex"; - rule.path.push_back({"CEconEntity", "m_AttributeManager"}); - rule.path.push_back({"CAttributeContainer", "m_Item"}); - rule.path.push_back({"CEconItemView", "m_iItemDefinitionIndex"}); - rule.type = FieldType::UInt16; - rule.bits = itemDefinitionIndex; - - if (!ValidatePath(rule, error)) - return false; - - m_rules.push_back(rule); - SPMessage("rule #%d: recipient=%d target=%s weapon item_definition_index=%u\n", rule.id, rule.recipientSlot, rule.target.c_str(), itemDefinitionIndex); - return true; -} - -bool OverrideManager::SetHealthMoneyTest(bool enabled, std::string& error) -{ - if (enabled) - { - if (!RequiredField("CCSPlayerController", "m_pInGameMoneyServices", error) || - !RequiredField("CCSPlayerController_InGameMoneyServices", "m_iAccount", error) || - !RequiredField("CBasePlayerController", "m_hPawn", error) || - !RequiredField("CBasePlayerController", "m_nSplitScreenSlot", error) || - !RequiredField("CBaseEntity", "m_iHealth", error) || - !RequiredField("CBaseEntity", "m_iTeamNum", error)) - { - return false; - } - } - - m_healthMoneyTest = enabled; - SPMessage("health_money_test=%d\n", m_healthMoneyTest ? 1 : 0); - return true; -} - -bool OverrideManager::SetHealth42Test(bool enabled, std::string& error) -{ - if (enabled && !RequiredField("CBaseEntity", "m_iHealth", error)) - return false; - - m_health42Test = enabled; - SPMessage("health_42_test=%d\n", m_health42Test ? 1 : 0); - return true; -} - -bool OverrideManager::SetRecipientHealthValue(int recipientSlot, int value, std::string& error) -{ - if (recipientSlot < -1 || recipientSlot >= static_cast(m_recipientHealthSet.size())) - { - error = "recipient slot must be all/-1 or 0..63"; - return false; - } - - if (!RequiredField("CBaseEntity", "m_iHealth", error)) - return false; - - if (recipientSlot == -1) - { - m_defaultHealth42Value = value; - SPMessage("health_42 default recipient value=%d\n", value); - return true; - } - - m_recipientHealthSet[recipientSlot] = true; - m_recipientHealthValue[recipientSlot] = value; - SPMessage("health_42 recipient slot %d value=%d\n", recipientSlot, value); - return true; -} - -void OverrideManager::ClearRecipientHealthValues() -{ - m_defaultHealth42Value = 42; - m_recipientHealthSet.fill(false); - m_recipientHealthValue.fill(0); - SPMessage("health_42 recipient values cleared\n"); -} - -bool OverrideManager::TryGetHealth42Value(CPlayerSlot recipient, int& value) const -{ - if (!m_health42Test) - return false; - - int slot = recipient.Get(); - if (slot >= 0 && slot < static_cast(m_recipientHealthSet.size()) && m_recipientHealthSet[slot]) - { - value = m_recipientHealthValue[slot]; - return true; - } - - value = m_defaultHealth42Value; - return true; -} - -void OverrideManager::Dump() const -{ - SPMessage("%zu static rules loaded; %zu dynamic rules loaded; trace=%d\n", m_rules.size(), DynamicRuleCount(), m_trace ? 1 : 0); - if (m_healthMoneyTest) - SPMessage("dynamic: teammate CBaseEntity::m_iHealth = recipient CCSPlayerController_InGameMoneyServices::m_iAccount\n"); - if (m_health42Test) - { - SPMessage("dynamic: all non-own player CBaseEntity::m_iHealth = per-recipient value, default=%d\n", m_defaultHealth42Value); - for (size_t i = 0; i < m_recipientHealthSet.size(); ++i) - { - if (m_recipientHealthSet[i]) - SPMessage("dynamic: recipient slot %zu health=%d\n", i, m_recipientHealthValue[i]); - } - } - 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 -{ - if (!g_entitySystem) - g_entitySystem = GameEntitySystem(); - if (!g_entitySystem) - return 0; - - if (!V_stricmp(rule.target.c_str(), "entity")) - return 0; - - int visited = 0; - for (auto* identity = g_entitySystem->m_EntityList.m_pFirstActiveEntity; identity && visited < 16384; identity = identity->m_pNext, ++visited) - { - auto* entity = identity->m_pInstance; - if (!entity) - continue; - const char* designer = identity->m_designerName.String(); - if (!designer) - continue; - if (!V_stricmp(rule.target.c_str(), "all") || V_stristr(designer, rule.target.c_str())) - return reinterpret_cast(entity); - } - return 0; -} - -uint8_t* OverrideManager::ResolveAddress(uintptr_t entity, const OverrideRule& rule) const -{ - uintptr_t address = entity; - for (const auto& segment : rule.path) - { - auto field = schema::FindField(segment.className.c_str(), segment.fieldName.c_str()); - if (!field.found) - return nullptr; - address += field.offset; - } - return reinterpret_cast(address); -} - -void OverrideManager::ApplyHealthMoneyTest(CPlayerSlot slot, std::vector& applied) -{ - if (!m_healthMoneyTest) - return; - if (!g_entitySystem) - g_entitySystem = GameEntitySystem(); - if (!g_entitySystem) - return; - - auto* recipientIdentity = FindControllerBySlot(slot); - if (!recipientIdentity || !DesignerContains(recipientIdentity, "controller") || !recipientIdentity->m_pInstance) - { - if (m_trace) - SPMessage("health_money_test recipient=%d no controller\n", slot.Get()); - return; - } - - uintptr_t recipient = reinterpret_cast(recipientIdentity->m_pInstance); - auto* moneyServicesAddress = FieldAddress(recipient, "CCSPlayerController", "m_pInGameMoneyServices"); - auto* teamAddress = FieldAddress(recipient, "CBaseEntity", "m_iTeamNum"); - auto* pawnHandleAddress = FieldAddress(recipient, "CBasePlayerController", "m_hPawn"); - if (!moneyServicesAddress || !teamAddress || !pawnHandleAddress) - { - if (m_trace) - SPMessage("health_money_test recipient=%d missing addresses\n", slot.Get()); - return; - } - - uintptr_t moneyServices = *reinterpret_cast(moneyServicesAddress); - if (!moneyServices) - { - if (m_trace) - SPMessage("health_money_test recipient=%d no money services\n", slot.Get()); - return; - } - auto* moneyAddress = FieldAddress(moneyServices, "CCSPlayerController_InGameMoneyServices", "m_iAccount"); - if (!moneyAddress) - return; - - int money = static_cast(ReadBits(moneyAddress, FieldType::Int32)); - int team = static_cast(*teamAddress); - if (team < 2) - { - if (m_trace) - SPMessage("health_money_test recipient=%d invalid team=%d\n", slot.Get(), team); - return; - } - - int ownPawnIndex = reinterpret_cast(pawnHandleAddress)->GetEntryIndex(); - int changed = 0; - int visited = 0; - for (auto* identity = g_entitySystem->m_EntityList.m_pFirstActiveEntity; identity && visited < 16384; identity = identity->m_pNext, ++visited) - { - if (!identity->m_pInstance || EntityIndex(identity) == ownPawnIndex) - continue; - if (!DesignerContains(identity, "player") || DesignerContains(identity, "controller")) - continue; - - uintptr_t entity = reinterpret_cast(identity->m_pInstance); - auto* targetTeamAddress = FieldAddress(entity, "CBaseEntity", "m_iTeamNum"); - auto* healthAddress = FieldAddress(entity, "CBaseEntity", "m_iHealth"); - if (!targetTeamAddress || !healthAddress) - continue; - if (static_cast(*targetTeamAddress) != team) - continue; - - AppliedOverride saved {healthAddress, FieldType::Int32, ReadBits(healthAddress, FieldType::Int32)}; - WriteBits(healthAddress, FieldType::Int32, static_cast(money)); - applied.push_back(saved); - ++changed; - } - - if (m_trace) - SPMessage("health_money_test recipient=%d money=%d changed=%d\n", slot.Get(), money, changed); -} - -std::vector OverrideManager::ApplyGlobalHealth42() -{ - std::vector applied; - if (!m_health42Test) - return applied; - if (!g_entitySystem) - g_entitySystem = GameEntitySystem(); - if (!g_entitySystem) - return applied; - - auto healthField = schema::FindField("CBaseEntity", "m_iHealth"); - if (!healthField.found) - return applied; - - applied.reserve(16); - int visited = 0; - for (auto* identity = g_entitySystem->m_EntityList.m_pFirstActiveEntity; identity && visited < 16384; identity = identity->m_pNext, ++visited) - { - if (!identity->m_pInstance) - continue; - if (!DesignerEquals(identity, "player")) - continue; - - auto* healthAddress = reinterpret_cast(identity->m_pInstance) + healthField.offset; - - AppliedOverride saved {healthAddress, FieldType::Int32, static_cast(*reinterpret_cast(healthAddress))}; - *reinterpret_cast(healthAddress) = 42; - applied.push_back(saved); - } - - return applied; -} - -int OverrideManager::MarkPlayerHealthDirty() -{ - if (!g_entitySystem) - g_entitySystem = GameEntitySystem(); - if (!g_entitySystem) - return 0; - - auto healthField = schema::FindField("CBaseEntity", "m_iHealth"); - if (!healthField.found) - return 0; - - int changed = 0; - int visited = 0; - for (auto* identity = g_entitySystem->m_EntityList.m_pFirstActiveEntity; identity && visited < 16384; identity = identity->m_pNext, ++visited) - { - if (!identity->m_pInstance || !DesignerEquals(identity, "player")) - continue; - - NetworkStateChangedData data(static_cast(healthField.offset)); - identity->m_pInstance->NetworkStateChanged(data); - ++changed; - } - - if (m_trace) - SPMessage("marked player health dirty changed=%d\n", changed); - return changed; -} - -std::vector OverrideManager::ApplyForRecipient(CPlayerSlot slot) -{ - std::vector applied; - ApplyHealthMoneyTest(slot, applied); - for (const auto& rule : m_rules) - { - if (!rule.enabled || (rule.recipientSlot != -1 && rule.recipientSlot != slot.Get())) - continue; - uintptr_t entity = FindFirstEntityForTarget(rule); - if (!entity) - continue; - auto* address = ResolveAddress(entity, rule); - if (!address) - continue; - AppliedOverride saved {address, rule.type, ReadBits(address, rule.type)}; - WriteBits(address, rule.type, rule.bits); - applied.push_back(saved); - if (m_trace) - SPMessage("applied #%d for recipient %d at %p\n", rule.id, slot.Get(), address); - } - return applied; -} - std::vector OverrideManager::ApplyForPackedEntity(CPlayerSlot recipient, int entityIndex, void* entityData) { std::vector applied; - if (!entityData || m_compiledRulesByEntity.empty()) + if (!entityData || m_rulesByEntity.empty()) return applied; - auto found = m_compiledRulesByEntity.find(entityIndex); - if (found == m_compiledRulesByEntity.end()) + auto found = m_rulesByEntity.find(entityIndex); + if (found == m_rulesByEntity.end()) return applied; for (size_t ruleIndex : found->second) { - const auto& rule = m_compiledRules[ruleIndex]; + const auto& rule = m_rules[ruleIndex]; if (!rule.enabled || (rule.recipientSlot != -1 && rule.recipientSlot != recipient.Get())) continue; - if (rule.value.empty()) - continue; auto* address = reinterpret_cast(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()); + saved.original.resize(rule.value.size()); + memcpy(saved.original.data(), address, saved.original.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& 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); - } + memcpy(it->address, it->original.data(), it->original.size()); } diff --git a/src/overrides.h b/src/overrides.h index fc1fdc7..07eac99 100644 --- a/src/overrides.h +++ b/src/overrides.h @@ -3,52 +3,14 @@ #include "playerslot.h" #include -#include #include #include #include -enum class FieldType -{ - Bool, - Int8, - UInt8, - Int16, - UInt16, - Int32, - UInt32, - Int64, - UInt64, - Float, - Bytes, - StringPtr, -}; - -struct FieldPathSegment -{ - std::string className; - std::string fieldName; -}; - -struct OverrideRule -{ - int id {}; - int recipientSlot {-1}; - std::string target; - std::string className; - std::string fieldName; - std::vector path; - FieldType type {FieldType::Int32}; - uint64_t bits {}; - bool enabled {true}; -}; - struct AppliedOverride { uint8_t* address {}; - FieldType type {}; - uint64_t originalBits {}; - std::vector originalBytes; + std::vector original; }; struct CompiledOverrideRule @@ -60,9 +22,7 @@ struct CompiledOverrideRule std::string fieldPath; int32_t offset {}; int32_t size {}; - FieldType type {FieldType::Int32}; std::vector value; - std::string stringValue; bool enabled {true}; }; @@ -70,52 +30,24 @@ 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); + void Compact(); + int AddRuleBytes(int recipientSlot, int entityIndex, const char* className, const char* fieldPath, const void* value, int valueSize, 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); - bool SetRecipientHealthValue(int recipientSlot, int value, std::string& error); - void ClearRecipientHealthValues(); - void Dump() const; - void SetTrace(bool value) { m_trace = value; } - bool Trace() const { return m_trace; } - 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_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; } + bool Empty() const { return m_rules.empty(); } + bool HasPackedOverrides() const { return !m_rulesByEntity.empty(); } - std::vector ApplyForRecipient(CPlayerSlot slot); std::vector ApplyForPackedEntity(CPlayerSlot recipient, int entityIndex, void* entityData); - std::vector ApplyGlobalHealth42(); - int MarkPlayerHealthDirty(); void Restore(const std::vector& applied); private: - bool EntityMatches(uintptr_t entity, const OverrideRule& rule) const; - uintptr_t FindFirstEntityForTarget(const OverrideRule& rule) const; - uint8_t* ResolveAddress(uintptr_t entity, const OverrideRule& rule) const; - void ApplyHealthMoneyTest(CPlayerSlot slot, std::vector& 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& out, std::string& stringValue, std::string& error) const; - void RebuildCompiledIndex(); + bool ResolveFieldPath(const char* className, const char* fieldPath, int32_t& offset, int32_t& size, std::string& error) const; + void RebuildIndex(); bool MarkEntityFieldDirtyByOffset(int entityIndex, int32_t offset); - std::vector m_rules; - std::vector m_compiledRules; - std::unordered_map> m_compiledRulesByEntity; + std::vector m_rules; + std::unordered_map> m_rulesByEntity; int m_nextId {1}; - bool m_trace {false}; - bool m_healthMoneyTest {false}; - bool m_health42Test {false}; - int m_defaultHealth42Value {42}; - std::array m_recipientHealthSet {}; - std::array m_recipientHealthValue {}; }; extern OverrideManager g_overrides; diff --git a/src/plugin.cpp b/src/plugin.cpp index 02f2941..b12144d 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -59,10 +59,10 @@ CGameEntitySystem* GameEntitySystem() return g_entitySystem; } -extern "C" __attribute__((visibility("default"))) int SendProxy_SetOverride(int recipientSlot, int entityIndex, const char* className, const char* fieldPath, const char* type, const char* value) +extern "C" __attribute__((visibility("default"))) int SendProxy_SetOverride(int recipientSlot, int entityIndex, const char* className, const char* fieldPath, const void* value, int valueSize) { std::string error; - int id = g_overrides.AddCompiledRule(recipientSlot, entityIndex, className, fieldPath, type, value, error); + int id = g_overrides.AddRuleBytes(recipientSlot, entityIndex, className, fieldPath, value, valueSize, error); if (!id) { SPWarning("SendProxy_SetOverride failed: %s\n", error.c_str()); @@ -151,6 +151,7 @@ bool SendProxyPlugin::Unload(char*, size_t) void SendProxyPlugin::OnLevelInit(char const* map, char const*, char const*, char const*, bool, bool) { + g_overrides.Compact(); g_armed = false; SPMessage("level init %s\n", map ? map : ""); }