clean up debugging outputs
This commit is contained in:
16
README.md
16
README.md
@@ -97,19 +97,23 @@ It demonstrates:
|
|||||||
- Coloring dropped AK-47s red for Terrorist viewers.
|
- Coloring dropped AK-47s red for Terrorist viewers.
|
||||||
- Coloring dropped M4A1-S blue for CT viewers.
|
- Coloring dropped M4A1-S blue for CT viewers.
|
||||||
|
|
||||||
Concealed-carry handle spoof:
|
Concealed-carry weapon-list spoof:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
examples/CounterStrikeSharp/SendProxyConcealedCarry/
|
examples/CounterStrikeSharp/SendProxyConcealedCarry/
|
||||||
```
|
```
|
||||||
|
|
||||||
It hides owned primary weapons, secondary weapons, and grenades from every recipient except the owning player by spoofing weapon-related handles to `0xffffffff`:
|
It hides a player's non-active carried weapons from every recipient except the owning player by spoofing the player/weapon association:
|
||||||
|
|
||||||
- `CBasePlayerPawn::m_pWeaponServices.m_hActiveWeapon`
|
- `CBasePlayerPawn::m_pWeaponServices.m_hMyWeapons`
|
||||||
- `CBasePlayerPawn::m_pWeaponServices.m_hLastWeapon`
|
- source handle: `CBasePlayerPawn::m_pWeaponServices.m_hActiveWeapon`
|
||||||
- `CBaseEntity::m_hOwnerEntity` on carried weapon entities
|
- `CBaseEntity::m_hOwnerEntity = INVALID_EHANDLE` on non-active carried weapon entities
|
||||||
|
- `CBaseEntity::m_CBodyComponent.m_pSceneNode.m_hParent = INVALID_EHANDLE`
|
||||||
|
- `CBaseEntity::m_CBodyComponent.m_pSceneNode.m_hierarchyAttachName = 0`
|
||||||
|
- `CCSWeaponBase::m_hPrevOwner = INVALID_EHANDLE`
|
||||||
|
- `CBaseCSGrenade::m_bIsHeldByPlayer = false` for non-active grenades
|
||||||
|
|
||||||
It intentionally does not set `EF_NODRAW` and does not remove or drop the real weapons.
|
This corresponds to CounterStrikeSharp's `player.PlayerPawn.Value.WeaponServices?.MyWeapons`, plus the weapon entity relationship fields that attach carried weapons to the owning pawn. During packing for non-owner recipients, native code temporarily masks non-active `m_hMyWeapons` elements to `INVALID_EHANDLE`; the CSS sample also spoofs non-active carried weapon entities as detached/unowned. It intentionally does not set `EF_NODRAW`, use render mode tricks, or remove/drop the real weapons.
|
||||||
|
|
||||||
This sample periodically reconciles desired rules and diffs them against active rule IDs. For production, prefer event-driven updates on player connect/disconnect/team changes/spawn/death and weapon pickup/drop/create/delete.
|
This sample periodically reconciles desired rules and diffs them against active rule IDs. For production, prefer event-driven updates on player connect/disconnect/team changes/spawn/death and weapon pickup/drop/create/delete.
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ public sealed class SendProxyConcealedCarryPlugin : BasePlugin
|
|||||||
{
|
{
|
||||||
private const string WeaponListField = "m_pWeaponServices.m_hMyWeapons";
|
private const string WeaponListField = "m_pWeaponServices.m_hMyWeapons";
|
||||||
private const string ActiveWeaponField = "m_pWeaponServices.m_hActiveWeapon";
|
private const string ActiveWeaponField = "m_pWeaponServices.m_hActiveWeapon";
|
||||||
|
private const string EntityClass = "CBaseEntity";
|
||||||
|
private static readonly byte[] InvalidHandle = UInt32Bytes(0xffffffff);
|
||||||
|
private static readonly byte[] EmptyToken = UInt32Bytes(0);
|
||||||
|
private static readonly byte[] NoAttachment = Int16Bytes(-1);
|
||||||
|
private static readonly byte[] FalseByte = [0];
|
||||||
|
|
||||||
private readonly Dictionary<RuleKey, int> _activeRules = new();
|
private readonly Dictionary<RuleKey, int> _activeRules = new();
|
||||||
private SendProxyNative? _sendProxy;
|
private SendProxyNative? _sendProxy;
|
||||||
@@ -110,36 +115,8 @@ public sealed class SendProxyConcealedCarryPlugin : BasePlugin
|
|||||||
if (viewer.Slot == weapon.OwnerSlot)
|
if (viewer.Slot == weapon.OwnerSlot)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
desired[new RuleKey(
|
int recipient = ToEngineSlot(viewer);
|
||||||
ToEngineSlot(viewer),
|
AddAssociationDisconnectRules(desired, recipient, weapon.EntityIndex);
|
||||||
weapon.EntityIndex,
|
|
||||||
"CBaseEntity",
|
|
||||||
"m_hOwnerEntity")] = UInt32Bytes(0xffffffff);
|
|
||||||
desired[new RuleKey(
|
|
||||||
ToEngineSlot(viewer),
|
|
||||||
weapon.EntityIndex,
|
|
||||||
"CBaseEntity",
|
|
||||||
"m_CBodyComponent.m_pSceneNode.m_hParent.m_hOwner")] = UInt32Bytes(0xffffffff);
|
|
||||||
desired[new RuleKey(
|
|
||||||
ToEngineSlot(viewer),
|
|
||||||
weapon.EntityIndex,
|
|
||||||
"CBaseEntity",
|
|
||||||
"m_CBodyComponent.m_pSceneNode.m_hParent.m_name")] = UInt32Bytes(0);
|
|
||||||
desired[new RuleKey(
|
|
||||||
ToEngineSlot(viewer),
|
|
||||||
weapon.EntityIndex,
|
|
||||||
"CBaseEntity",
|
|
||||||
"m_CBodyComponent.m_pSceneNode.m_nParentAttachmentOrBone")] = Int16Bytes(-1);
|
|
||||||
desired[new RuleKey(
|
|
||||||
ToEngineSlot(viewer),
|
|
||||||
weapon.EntityIndex,
|
|
||||||
"CBaseEntity",
|
|
||||||
"m_CBodyComponent.m_pSceneNode.m_hierarchyAttachName")] = UInt32Bytes(0);
|
|
||||||
desired[new RuleKey(
|
|
||||||
ToEngineSlot(viewer),
|
|
||||||
weapon.EntityIndex,
|
|
||||||
"CBaseEntity",
|
|
||||||
"m_CBodyComponent.m_pSceneNode.m_bForceParentToBeNetworked")] = new byte[] { 0 };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,13 +183,6 @@ public sealed class SendProxyConcealedCarryPlugin : BasePlugin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] UInt16Bytes(ushort value)
|
|
||||||
{
|
|
||||||
byte[] bytes = new byte[2];
|
|
||||||
BitConverter.TryWriteBytes(bytes, value);
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] Int16Bytes(short value)
|
private static byte[] Int16Bytes(short value)
|
||||||
{
|
{
|
||||||
byte[] bytes = new byte[2];
|
byte[] bytes = new byte[2];
|
||||||
@@ -227,6 +197,16 @@ public sealed class SendProxyConcealedCarryPlugin : BasePlugin
|
|||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void AddAssociationDisconnectRules(Dictionary<RuleKey, byte[]> desired, int recipientSlot, int weaponEntityIndex)
|
||||||
|
{
|
||||||
|
desired[new RuleKey(recipientSlot, weaponEntityIndex, EntityClass, "m_hOwnerEntity")] = InvalidHandle;
|
||||||
|
desired[new RuleKey(recipientSlot, weaponEntityIndex, EntityClass, "m_CBodyComponent.m_pSceneNode.m_hParent.m_hOwner")] = InvalidHandle;
|
||||||
|
desired[new RuleKey(recipientSlot, weaponEntityIndex, EntityClass, "m_CBodyComponent.m_pSceneNode.m_hParent.m_name")] = EmptyToken;
|
||||||
|
desired[new RuleKey(recipientSlot, weaponEntityIndex, EntityClass, "m_CBodyComponent.m_pSceneNode.m_nParentAttachmentOrBone")] = NoAttachment;
|
||||||
|
desired[new RuleKey(recipientSlot, weaponEntityIndex, EntityClass, "m_CBodyComponent.m_pSceneNode.m_hierarchyAttachName")] = EmptyToken;
|
||||||
|
desired[new RuleKey(recipientSlot, weaponEntityIndex, EntityClass, "m_CBodyComponent.m_pSceneNode.m_bForceParentToBeNetworked")] = FalseByte;
|
||||||
|
}
|
||||||
|
|
||||||
private static int ToEngineSlot(CCSPlayerController player)
|
private static int ToEngineSlot(CCSPlayerController player)
|
||||||
{
|
{
|
||||||
return Math.Max(0, player.Slot - 1);
|
return Math.Max(0, player.Slot - 1);
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ static uint64_t g_packCalls = 0;
|
|||||||
static uint64_t g_packEntityCalls = 0;
|
static uint64_t g_packEntityCalls = 0;
|
||||||
static uint64_t g_packEntityGenericSpoofs = 0;
|
static uint64_t g_packEntityGenericSpoofs = 0;
|
||||||
static uint64_t g_fullUpdateWrites = 0;
|
static uint64_t g_fullUpdateWrites = 0;
|
||||||
static uint64_t g_packEntitiesDebugLogs = 0;
|
|
||||||
static uint64_t g_packEntityRuleDebugLogs = 0;
|
|
||||||
static int g_fullUpdateBudget = 0;
|
static int g_fullUpdateBudget = 0;
|
||||||
static thread_local CPlayerSlot g_currentRecipient(-1);
|
static thread_local CPlayerSlot g_currentRecipient(-1);
|
||||||
static thread_local CPlayerSlot g_lastSnapshotRecipient(-1);
|
static thread_local CPlayerSlot g_lastSnapshotRecipient(-1);
|
||||||
@@ -79,20 +77,6 @@ static void Detour_PackEntities(void* self, void* arg1, int arg2, void* arg3, vo
|
|||||||
{
|
{
|
||||||
++g_packCalls;
|
++g_packCalls;
|
||||||
CPlayerSlot recipient((arg2 >= 0 && arg2 < 64) ? arg2 : -1);
|
CPlayerSlot recipient((arg2 >= 0 && arg2 < 64) ? arg2 : -1);
|
||||||
if (g_packEntitiesDebugLogs < 16)
|
|
||||||
{
|
|
||||||
SPMessage("pack_entities call %llu self=%p arg1=%p arg2=%d recipient=%d args=[%p %p %p %p]\n",
|
|
||||||
static_cast<unsigned long long>(g_packCalls),
|
|
||||||
self,
|
|
||||||
arg1,
|
|
||||||
arg2,
|
|
||||||
recipient.Get(),
|
|
||||||
arg3,
|
|
||||||
arg4,
|
|
||||||
arg5,
|
|
||||||
arg6);
|
|
||||||
++g_packEntitiesDebugLogs;
|
|
||||||
}
|
|
||||||
ScopedRecipient scopedRecipient(recipient);
|
ScopedRecipient scopedRecipient(recipient);
|
||||||
g_packEntities(self, arg1, arg2, arg3, arg4, arg5, arg6);
|
g_packEntities(self, arg1, arg2, arg3, arg4, arg5, arg6);
|
||||||
}
|
}
|
||||||
@@ -110,24 +94,6 @@ static void Detour_PackEntity(void* self, void* arg1, int entityIndex, void* ent
|
|||||||
CPlayerSlot recipient = g_currentRecipient;
|
CPlayerSlot recipient = g_currentRecipient;
|
||||||
if (recipient.Get() < 0 && g_lastSnapshotRecipient.Get() >= 0)
|
if (recipient.Get() < 0 && g_lastSnapshotRecipient.Get() >= 0)
|
||||||
recipient = g_lastSnapshotRecipient;
|
recipient = g_lastSnapshotRecipient;
|
||||||
if (g_packEntityRuleDebugLogs < 64 && g_overrides.HasRulesForEntity(entityIndex))
|
|
||||||
{
|
|
||||||
SPMessage("pack_entity ruled call %llu entity=%d current=%d lastSnapshot=%d args self=%p arg1=%p data=%p arg4=%p arg5=%p arg6=%p arg7=%p arg8=%p arg9=%p\n",
|
|
||||||
static_cast<unsigned long long>(g_packEntityCalls),
|
|
||||||
entityIndex,
|
|
||||||
g_currentRecipient.Get(),
|
|
||||||
g_lastSnapshotRecipient.Get(),
|
|
||||||
self,
|
|
||||||
arg1,
|
|
||||||
entityData,
|
|
||||||
arg4,
|
|
||||||
arg5,
|
|
||||||
arg6,
|
|
||||||
arg7,
|
|
||||||
arg8,
|
|
||||||
arg9);
|
|
||||||
++g_packEntityRuleDebugLogs;
|
|
||||||
}
|
|
||||||
auto applied = g_overrides.ApplyForPackedEntity(recipient, entityIndex, entityData);
|
auto applied = g_overrides.ApplyForPackedEntity(recipient, entityIndex, entityData);
|
||||||
g_packEntityGenericSpoofs += applied.size();
|
g_packEntityGenericSpoofs += applied.size();
|
||||||
g_packEntity(self, arg1, entityIndex, entityData, arg4, arg5, arg6, arg7, arg8, arg9);
|
g_packEntity(self, arg1, entityIndex, entityData, arg4, arg5, arg6, arg7, arg8, arg9);
|
||||||
|
|||||||
@@ -214,29 +214,6 @@ static void ApplyVectorFirstFromField(const CompiledOverrideRule& rule, void* en
|
|||||||
constexpr uint32_t invalidHandle = 0xffffffff;
|
constexpr uint32_t invalidHandle = 0xffffffff;
|
||||||
const int32_t count = *reinterpret_cast<int32_t*>(vectorAddress);
|
const int32_t count = *reinterpret_cast<int32_t*>(vectorAddress);
|
||||||
auto* elements = *reinterpret_cast<uint8_t**>(vectorAddress + 8);
|
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)
|
if (count < 0 || count > 128)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -323,19 +300,6 @@ int OverrideManager::AddRuleBytes(int recipientSlot, int entityIndex, const char
|
|||||||
auto* bytes = static_cast<const uint8_t*>(value);
|
auto* bytes = static_cast<const uint8_t*>(value);
|
||||||
rule.value.assign(bytes, bytes + valueSize);
|
rule.value.assign(bytes, bytes + valueSize);
|
||||||
m_rules.push_back(rule);
|
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();
|
RebuildIndex();
|
||||||
MarkEntityFullDirty(entityIndex);
|
MarkEntityFullDirty(entityIndex);
|
||||||
return rule.id;
|
return rule.id;
|
||||||
@@ -375,18 +339,6 @@ int OverrideManager::AddRuleVectorFirstFromField(int recipientSlot, int entityIn
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_rules.push_back(rule);
|
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();
|
RebuildIndex();
|
||||||
MarkEntityFullDirty(entityIndex);
|
MarkEntityFullDirty(entityIndex);
|
||||||
return rule.id;
|
return rule.id;
|
||||||
@@ -443,18 +395,7 @@ std::vector<AppliedOverride> OverrideManager::ApplyForPackedEntity(CPlayerSlot r
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((effectiveRecipient < 0 || effectiveRecipient == 0 || effectiveRecipient == 1) && uniqueRuleRecipient >= 0)
|
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;
|
effectiveRecipient = uniqueRuleRecipient;
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t ruleIndex : found->second)
|
for (size_t ruleIndex : found->second)
|
||||||
{
|
{
|
||||||
@@ -463,20 +404,7 @@ std::vector<AppliedOverride> OverrideManager::ApplyForPackedEntity(CPlayerSlot r
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (rule.recipientSlot != -1 && rule.recipientSlot != effectiveRecipient)
|
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;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
if (rule.kind == OverrideRuleKind::VectorFirstFromField)
|
if (rule.kind == OverrideRuleKind::VectorFirstFromField)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -41,8 +41,6 @@ struct CompiledOverrideRule
|
|||||||
std::vector<uint8_t> value;
|
std::vector<uint8_t> value;
|
||||||
OverrideRuleKind kind {OverrideRuleKind::Bytes};
|
OverrideRuleKind kind {OverrideRuleKind::Bytes};
|
||||||
bool enabled {true};
|
bool enabled {true};
|
||||||
mutable bool debugLogged {false};
|
|
||||||
mutable bool recipientMismatchLogged {false};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class OverrideManager
|
class OverrideManager
|
||||||
|
|||||||
Reference in New Issue
Block a user