working weapon concealment

This commit is contained in:
2026-08-10 16:59:45 +02:00
parent 4e9500f0ea
commit 9a23169b6b
13 changed files with 927 additions and 66 deletions

View File

@@ -53,6 +53,34 @@ static std::vector<std::string> SplitString(const char* value, char separator)
return out;
}
static std::string NormalizeSchemaClassName(std::string typeName)
{
while (!typeName.empty() && (typeName.back() == '*' || typeName.back() == '&' || typeName.back() == ' '))
typeName.pop_back();
while (!typeName.empty() && typeName.front() == ' ')
typeName.erase(typeName.begin());
constexpr const char* classPrefix = "class ";
constexpr const char* structPrefix = "struct ";
if (typeName.rfind(classPrefix, 0) == 0)
typeName.erase(0, strlen(classPrefix));
else if (typeName.rfind(structPrefix, 0) == 0)
typeName.erase(0, strlen(structPrefix));
return typeName;
}
static bool IsPointerSchemaType(const std::string& typeName)
{
for (auto it = typeName.rbegin(); it != typeName.rend(); ++it)
{
if (*it == ' ')
continue;
return *it == '*';
}
return false;
}
void OverrideManager::Clear()
{
for (const auto& rule : m_rules)
@@ -75,7 +103,7 @@ void OverrideManager::Compact()
RebuildIndex();
}
bool OverrideManager::ResolveFieldPath(const char* className, const char* fieldPath, int32_t& offset, int32_t& size, std::string& error) const
bool OverrideManager::ResolveFieldPath(const char* className, const char* fieldPath, int32_t& offset, int32_t& size, std::vector<FieldAddressStep>& addressSteps, std::string& error) const
{
if (!className || !className[0] || !fieldPath || !fieldPath[0])
{
@@ -92,6 +120,7 @@ bool OverrideManager::ResolveFieldPath(const char* className, const char* fieldP
offset = 0;
size = 0;
addressSteps.clear();
std::string currentClass = className;
for (size_t i = 0; i < parts.size(); ++i)
{
@@ -102,8 +131,11 @@ bool OverrideManager::ResolveFieldPath(const char* className, const char* fieldP
return false;
}
offset += field.offset;
size = field.size;
FieldAddressStep step {};
step.offset = field.offset;
step.dereference = i + 1 < parts.size() && IsPointerSchemaType(field.typeName);
addressSteps.push_back(step);
if (i + 1 < parts.size())
{
if (field.typeName.empty())
@@ -111,13 +143,119 @@ bool OverrideManager::ResolveFieldPath(const char* className, const char* fieldP
error = "intermediate field has no schema type name";
return false;
}
currentClass = field.typeName;
currentClass = NormalizeSchemaClassName(field.typeName);
}
}
if (!addressSteps.empty())
offset = addressSteps.front().offset;
return true;
}
static uint8_t* ResolveRuleAddress(const CompiledOverrideRule& rule, void* entityData)
{
auto* address = static_cast<uint8_t*>(entityData);
for (const auto& step : rule.addressSteps)
{
address += step.offset;
if (!step.dereference)
continue;
address = *reinterpret_cast<uint8_t**>(address);
if (!address)
return nullptr;
}
return address;
}
static uint8_t* ResolveAddressSteps(const std::vector<FieldAddressStep>& steps, void* entityData)
{
auto* address = static_cast<uint8_t*>(entityData);
for (const auto& step : steps)
{
address += step.offset;
if (!step.dereference)
continue;
address = *reinterpret_cast<uint8_t**>(address);
if (!address)
return nullptr;
}
return address;
}
static void SaveAndWrite(std::vector<AppliedOverride>& applied, uint8_t* address, const void* value, size_t size)
{
AppliedOverride saved {};
saved.address = address;
saved.original.resize(size);
memcpy(saved.original.data(), address, saved.original.size());
memcpy(address, value, size);
applied.push_back(saved);
}
static void SaveAndWriteIfChanged(std::vector<AppliedOverride>& applied, uint8_t* address, const void* value, size_t size)
{
if (memcmp(address, value, size) == 0)
return;
SaveAndWrite(applied, address, value, size);
}
static void ApplyVectorFirstFromField(const CompiledOverrideRule& rule, void* entityData, std::vector<AppliedOverride>& applied)
{
auto* vectorAddress = ResolveAddressSteps(rule.addressSteps, entityData);
auto* sourceAddress = ResolveAddressSteps(rule.sourceAddressSteps, entityData);
if (!vectorAddress || !sourceAddress)
return;
const uint32_t sourceHandle = *reinterpret_cast<uint32_t*>(sourceAddress);
constexpr uint32_t invalidHandle = 0xffffffff;
const int32_t count = *reinterpret_cast<int32_t*>(vectorAddress);
auto* elements = *reinterpret_cast<uint8_t**>(vectorAddress + 8);
if (!rule.debugLogged)
{
uint32_t h0 = elements && count > 0 ? *reinterpret_cast<uint32_t*>(elements) : invalidHandle;
uint32_t h1 = elements && count > 1 ? *reinterpret_cast<uint32_t*>(elements + 4) : invalidHandle;
uint32_t h2 = elements && count > 2 ? *reinterpret_cast<uint32_t*>(elements + 8) : invalidHandle;
const auto* raw = reinterpret_cast<const uint8_t*>(vectorAddress);
SPMessage("vector apply rule %d recipient=%d entity=%d vec=%p src=%p count=%d elems=%p active=%08x first=[%08x %08x %08x] raw=%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n",
rule.id,
rule.recipientSlot,
rule.entityIndex,
vectorAddress,
sourceAddress,
count,
elements,
sourceHandle,
h0,
h1,
h2,
raw[0], raw[1], raw[2], raw[3], raw[4], raw[5], raw[6], raw[7],
raw[8], raw[9], raw[10], raw[11], raw[12], raw[13], raw[14], raw[15],
raw[16], raw[17], raw[18], raw[19], raw[20], raw[21], raw[22], raw[23]);
rule.debugLogged = true;
}
if (count < 0 || count > 128)
return;
auto* countAddress = reinterpret_cast<uint8_t*>(&*reinterpret_cast<int32_t*>(vectorAddress));
if (sourceHandle == invalidHandle)
{
const int32_t emptyCount = 0;
SaveAndWriteIfChanged(applied, countAddress, &emptyCount, sizeof(emptyCount));
return;
}
if (!elements)
return;
const int32_t activeOnlyCount = 1;
SaveAndWriteIfChanged(applied, countAddress, &activeOnlyCount, sizeof(activeOnlyCount));
SaveAndWriteIfChanged(applied, elements, &sourceHandle, sizeof(sourceHandle));
}
void OverrideManager::RebuildIndex()
{
m_rulesByEntity.clear();
@@ -139,6 +277,17 @@ bool OverrideManager::MarkEntityFieldDirtyByOffset(int entityIndex, int32_t offs
return true;
}
bool OverrideManager::MarkEntityFullDirty(int entityIndex)
{
auto* identity = FindIdentityByIndex(entityIndex);
if (!identity || !identity->m_pInstance)
return false;
NetworkStateChangedData data(true);
identity->m_pInstance->NetworkStateChanged(data);
return true;
}
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)
@@ -163,7 +312,7 @@ int OverrideManager::AddRuleBytes(int recipientSlot, int entityIndex, const char
rule.entityIndex = entityIndex;
rule.className = className ? className : "";
rule.fieldPath = fieldPath ? fieldPath : "";
if (!ResolveFieldPath(rule.className.c_str(), rule.fieldPath.c_str(), rule.offset, rule.size, error))
if (!ResolveFieldPath(rule.className.c_str(), rule.fieldPath.c_str(), rule.offset, rule.size, rule.addressSteps, error))
return 0;
if (rule.size > 0 && valueSize > rule.size)
{
@@ -174,8 +323,72 @@ int OverrideManager::AddRuleBytes(int recipientSlot, int entityIndex, const char
auto* bytes = static_cast<const uint8_t*>(value);
rule.value.assign(bytes, bytes + valueSize);
m_rules.push_back(rule);
if (rule.fieldPath.find("m_hMyWeapons") != std::string::npos)
{
SPMessage("rule %d recipient=%d entity=%d %s::%s size=%d valueSize=%d topOffset=%d steps=%zu\n",
rule.id,
rule.recipientSlot,
rule.entityIndex,
rule.className.c_str(),
rule.fieldPath.c_str(),
rule.size,
valueSize,
rule.offset,
rule.addressSteps.size());
}
RebuildIndex();
MarkEntityFieldDirtyByOffset(entityIndex, rule.offset);
MarkEntityFullDirty(entityIndex);
return rule.id;
}
int OverrideManager::AddRuleVectorFirstFromField(int recipientSlot, int entityIndex, const char* className, const char* vectorFieldPath, const char* sourceFieldPath, 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;
}
CompiledOverrideRule rule;
rule.id = m_nextId++;
rule.kind = OverrideRuleKind::VectorFirstFromField;
rule.recipientSlot = recipientSlot;
rule.entityIndex = entityIndex;
rule.className = className ? className : "";
rule.fieldPath = vectorFieldPath ? vectorFieldPath : "";
rule.sourceFieldPath = sourceFieldPath ? sourceFieldPath : "";
if (!ResolveFieldPath(rule.className.c_str(), rule.fieldPath.c_str(), rule.offset, rule.size, rule.addressSteps, error))
return 0;
int32_t sourceOffset = 0;
if (!ResolveFieldPath(rule.className.c_str(), rule.sourceFieldPath.c_str(), sourceOffset, rule.sourceSize, rule.sourceAddressSteps, error))
return 0;
if (rule.sourceSize > 0 && rule.sourceSize < 4)
{
error = "source field is smaller than a handle";
return 0;
}
m_rules.push_back(rule);
SPMessage("vector-first rule %d recipient=%d entity=%d %s::%s <- %s vectorSize=%d sourceSize=%d topOffset=%d steps=%zu sourceSteps=%zu\n",
rule.id,
rule.recipientSlot,
rule.entityIndex,
rule.className.c_str(),
rule.fieldPath.c_str(),
rule.sourceFieldPath.c_str(),
rule.size,
rule.sourceSize,
rule.offset,
rule.addressSteps.size(),
rule.sourceAddressSteps.size());
RebuildIndex();
MarkEntityFullDirty(entityIndex);
return rule.id;
}
@@ -186,7 +399,7 @@ bool OverrideManager::RemoveRule(int id)
if (rule.id != id)
continue;
rule.enabled = false;
MarkEntityFieldDirtyByOffset(rule.entityIndex, rule.offset);
MarkEntityFullDirty(rule.entityIndex);
RebuildIndex();
return true;
}
@@ -197,7 +410,8 @@ bool OverrideManager::MarkEntityFieldDirty(int entityIndex, const char* classNam
{
int32_t offset = 0;
int32_t size = 0;
if (!ResolveFieldPath(className, fieldPath, offset, size, error))
std::vector<FieldAddressStep> addressSteps;
if (!ResolveFieldPath(className, fieldPath, offset, size, addressSteps, error))
return false;
return MarkEntityFieldDirtyByOffset(entityIndex, offset);
}
@@ -212,19 +426,69 @@ std::vector<AppliedOverride> OverrideManager::ApplyForPackedEntity(CPlayerSlot r
if (found == m_rulesByEntity.end())
return applied;
int effectiveRecipient = recipient.Get();
int uniqueRuleRecipient = -2;
for (size_t ruleIndex : found->second)
{
const auto& rule = m_rules[ruleIndex];
if (!rule.enabled || (rule.recipientSlot != -1 && rule.recipientSlot != recipient.Get()))
if (!rule.enabled || rule.recipientSlot < 0)
continue;
if (uniqueRuleRecipient == -2)
uniqueRuleRecipient = rule.recipientSlot;
else if (uniqueRuleRecipient != rule.recipientSlot)
{
uniqueRuleRecipient = -2;
break;
}
}
if ((effectiveRecipient < 0 || effectiveRecipient == 0 || effectiveRecipient == 1) && uniqueRuleRecipient >= 0)
{
static int fallbackLogs = 0;
if (fallbackLogs < 64)
{
SPMessage("entity=%d unresolved recipient current=%d using unique rule recipient=%d\n",
entityIndex,
effectiveRecipient,
uniqueRuleRecipient);
++fallbackLogs;
}
effectiveRecipient = uniqueRuleRecipient;
}
for (size_t ruleIndex : found->second)
{
const auto& rule = m_rules[ruleIndex];
if (!rule.enabled)
continue;
auto* address = reinterpret_cast<uint8_t*>(entityData) + rule.offset;
AppliedOverride saved {};
saved.address = address;
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 (rule.recipientSlot != -1 && rule.recipientSlot != effectiveRecipient)
{
if (!rule.recipientMismatchLogged)
{
SPMessage("rule %d entity=%d matched packed entity but skipped recipient rule=%d current=%d field=%s::%s\n",
rule.id,
entityIndex,
rule.recipientSlot,
effectiveRecipient,
rule.className.c_str(),
rule.fieldPath.c_str());
rule.recipientMismatchLogged = true;
}
continue;
}
if (rule.kind == OverrideRuleKind::VectorFirstFromField)
{
ApplyVectorFirstFromField(rule, entityData, applied);
continue;
}
auto* address = ResolveRuleAddress(rule, entityData);
if (!address)
continue;
SaveAndWrite(applied, address, rule.value.data(), rule.value.size());
}
return applied;