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 M4A1-S blue for CT viewers.
|
||||
|
||||
Concealed-carry handle spoof:
|
||||
Concealed-carry weapon-list spoof:
|
||||
|
||||
```text
|
||||
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_hLastWeapon`
|
||||
- `CBaseEntity::m_hOwnerEntity` on carried weapon entities
|
||||
- `CBasePlayerPawn::m_pWeaponServices.m_hMyWeapons`
|
||||
- source handle: `CBasePlayerPawn::m_pWeaponServices.m_hActiveWeapon`
|
||||
- `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.
|
||||
|
||||
|
||||
@@ -10,6 +10,11 @@ public sealed class SendProxyConcealedCarryPlugin : BasePlugin
|
||||
{
|
||||
private const string WeaponListField = "m_pWeaponServices.m_hMyWeapons";
|
||||
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 SendProxyNative? _sendProxy;
|
||||
@@ -110,36 +115,8 @@ public sealed class SendProxyConcealedCarryPlugin : BasePlugin
|
||||
if (viewer.Slot == weapon.OwnerSlot)
|
||||
continue;
|
||||
|
||||
desired[new RuleKey(
|
||||
ToEngineSlot(viewer),
|
||||
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 };
|
||||
int recipient = ToEngineSlot(viewer);
|
||||
AddAssociationDisconnectRules(desired, recipient, weapon.EntityIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
byte[] bytes = new byte[2];
|
||||
@@ -227,6 +197,16 @@ public sealed class SendProxyConcealedCarryPlugin : BasePlugin
|
||||
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)
|
||||
{
|
||||
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_packEntityGenericSpoofs = 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 thread_local CPlayerSlot g_currentRecipient(-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;
|
||||
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);
|
||||
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;
|
||||
if (recipient.Get() < 0 && g_lastSnapshotRecipient.Get() >= 0)
|
||||
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);
|
||||
g_packEntityGenericSpoofs += applied.size();
|
||||
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;
|
||||
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;
|
||||
|
||||
@@ -323,19 +300,6 @@ 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();
|
||||
MarkEntityFullDirty(entityIndex);
|
||||
return rule.id;
|
||||
@@ -375,18 +339,6 @@ int OverrideManager::AddRuleVectorFirstFromField(int recipientSlot, int entityIn
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -443,18 +395,7 @@ std::vector<AppliedOverride> OverrideManager::ApplyForPackedEntity(CPlayerSlot r
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -463,20 +404,7 @@ std::vector<AppliedOverride> OverrideManager::ApplyForPackedEntity(CPlayerSlot r
|
||||
continue;
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -41,8 +41,6 @@ struct CompiledOverrideRule
|
||||
std::vector<uint8_t> value;
|
||||
OverrideRuleKind kind {OverrideRuleKind::Bytes};
|
||||
bool enabled {true};
|
||||
mutable bool debugLogged {false};
|
||||
mutable bool recipientMismatchLogged {false};
|
||||
};
|
||||
|
||||
class OverrideManager
|
||||
|
||||
Reference in New Issue
Block a user