working weapon concealment
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.369" />
|
||||
<Compile Include="..\SendProxyNative.cs" Link="SendProxyNative.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,248 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Timers;
|
||||
using SendProxyInterop;
|
||||
using Timer = CounterStrikeSharp.API.Modules.Timers.Timer;
|
||||
|
||||
namespace SendProxyConcealedCarry;
|
||||
|
||||
public sealed class SendProxyConcealedCarryPlugin : BasePlugin
|
||||
{
|
||||
private const string WeaponListField = "m_pWeaponServices.m_hMyWeapons";
|
||||
private const string ActiveWeaponField = "m_pWeaponServices.m_hActiveWeapon";
|
||||
|
||||
private readonly Dictionary<RuleKey, int> _activeRules = new();
|
||||
private SendProxyNative? _sendProxy;
|
||||
private Timer? _syncTimer;
|
||||
|
||||
public override string ModuleName => "SendProxy Concealed Carry";
|
||||
public override string ModuleVersion => "0.1.0";
|
||||
public override string ModuleAuthor => "OpenAI Codex";
|
||||
|
||||
public override void Load(bool hotReload)
|
||||
{
|
||||
_sendProxy = SendProxyNative.FromGameDirectory(Server.GameDirectory);
|
||||
_syncTimer = AddTimer(0.25f, SyncRules, TimerFlags.REPEAT);
|
||||
RegisterListener<Listeners.OnEntityCreated>(_ => Server.NextFrame(SyncRules));
|
||||
RegisterListener<Listeners.OnEntityDeleted>(_ => Server.NextFrame(SyncRules));
|
||||
Server.NextFrame(SyncRules);
|
||||
}
|
||||
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
_syncTimer?.Kill();
|
||||
ClearRules();
|
||||
_sendProxy?.Dispose();
|
||||
_sendProxy = null;
|
||||
}
|
||||
|
||||
private void SyncRules()
|
||||
{
|
||||
if (_sendProxy == null)
|
||||
return;
|
||||
|
||||
Dictionary<RuleKey, byte[]> desired;
|
||||
try
|
||||
{
|
||||
var viewers = Utilities.GetPlayers()
|
||||
.Where(player => player.IsValid && !player.IsBot && !player.IsHLTV)
|
||||
.ToArray();
|
||||
|
||||
var targets = Utilities.GetPlayers()
|
||||
.Where(player => player.IsValid && !player.IsHLTV)
|
||||
.ToArray();
|
||||
|
||||
var targetStatesByPawnIndex = targets
|
||||
.Select(player => new
|
||||
{
|
||||
player.Slot,
|
||||
Pawn = player.PlayerPawn.Value,
|
||||
})
|
||||
.Where(entry => entry.Pawn != null && entry.Pawn.IsValid)
|
||||
.GroupBy(entry => (int)entry.Pawn!.Index)
|
||||
.ToDictionary(
|
||||
group => group.Key,
|
||||
group =>
|
||||
{
|
||||
var first = group.First();
|
||||
var activeWeapon = first.Pawn!.WeaponServices?.ActiveWeapon.Value;
|
||||
int activeWeaponIndex = activeWeapon != null && activeWeapon.IsValid ? (int)activeWeapon.Index : -1;
|
||||
return new TargetState(first.Slot, activeWeaponIndex);
|
||||
});
|
||||
|
||||
desired = BuildDesiredRules(viewers, targetStatesByPawnIndex);
|
||||
}
|
||||
catch (NativeException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ReconcileRules(desired);
|
||||
}
|
||||
|
||||
private Dictionary<RuleKey, byte[]> BuildDesiredRules(
|
||||
IReadOnlyCollection<CCSPlayerController> viewers,
|
||||
IReadOnlyDictionary<int, TargetState> targetStatesByPawnIndex)
|
||||
{
|
||||
var desired = new Dictionary<RuleKey, byte[]>();
|
||||
foreach (var (pawnIndex, target) in targetStatesByPawnIndex)
|
||||
{
|
||||
foreach (var viewer in viewers)
|
||||
{
|
||||
if (viewer.Slot == target.OwnerSlot)
|
||||
continue;
|
||||
|
||||
desired[new RuleKey(
|
||||
ToEngineSlot(viewer),
|
||||
pawnIndex,
|
||||
"CBasePlayerPawn",
|
||||
WeaponListField)] = Array.Empty<byte>();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var weapon in EnumerateConcealableCarriedWeapons(targetStatesByPawnIndex))
|
||||
{
|
||||
if (weapon.EntityIndex == weapon.ActiveWeaponIndex)
|
||||
continue;
|
||||
|
||||
foreach (var viewer in viewers)
|
||||
{
|
||||
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 };
|
||||
}
|
||||
}
|
||||
|
||||
return desired;
|
||||
}
|
||||
|
||||
private void ReconcileRules(Dictionary<RuleKey, byte[]> desired)
|
||||
{
|
||||
if (_sendProxy == null)
|
||||
return;
|
||||
|
||||
foreach (var key in _activeRules.Keys.Except(desired.Keys).ToArray())
|
||||
{
|
||||
_sendProxy.RemoveOverride(_activeRules[key]);
|
||||
_activeRules.Remove(key);
|
||||
}
|
||||
|
||||
foreach (var (key, value) in desired)
|
||||
{
|
||||
if (_activeRules.ContainsKey(key))
|
||||
continue;
|
||||
|
||||
int ruleId = value.Length == 0
|
||||
? _sendProxy.SetVectorFirstFromField(
|
||||
key.RecipientSlot,
|
||||
key.EntityIndex,
|
||||
key.ClassName,
|
||||
key.FieldPath,
|
||||
ActiveWeaponField)
|
||||
: _sendProxy.SetBytes(
|
||||
key.RecipientSlot,
|
||||
key.EntityIndex,
|
||||
key.ClassName,
|
||||
key.FieldPath,
|
||||
value);
|
||||
|
||||
if (ruleId != 0)
|
||||
_activeRules[key] = ruleId;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<ConcealableWeapon> EnumerateConcealableCarriedWeapons(IReadOnlyDictionary<int, TargetState> targetStatesByPawnIndex)
|
||||
{
|
||||
foreach (var entity in Utilities.GetAllEntities())
|
||||
{
|
||||
if (!entity.IsValid || !entity.DesignerName.StartsWith("weapon_", StringComparison.Ordinal))
|
||||
continue;
|
||||
|
||||
CBasePlayerWeapon weapon = entity.As<CBasePlayerWeapon>();
|
||||
if (!weapon.IsValid)
|
||||
continue;
|
||||
|
||||
var owner = weapon.OwnerEntity.Value;
|
||||
if (owner == null || !owner.IsValid)
|
||||
continue;
|
||||
|
||||
if (!targetStatesByPawnIndex.TryGetValue((int)owner.Index, out var target))
|
||||
continue;
|
||||
|
||||
yield return new ConcealableWeapon(
|
||||
(int)weapon.Index,
|
||||
target.OwnerSlot,
|
||||
target.ActiveWeaponIndex);
|
||||
}
|
||||
}
|
||||
|
||||
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];
|
||||
BitConverter.TryWriteBytes(bytes, value);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private static byte[] UInt32Bytes(uint value)
|
||||
{
|
||||
byte[] bytes = new byte[4];
|
||||
BitConverter.TryWriteBytes(bytes, value);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private static int ToEngineSlot(CCSPlayerController player)
|
||||
{
|
||||
return Math.Max(0, player.Slot - 1);
|
||||
}
|
||||
|
||||
private void ClearRules()
|
||||
{
|
||||
if (_sendProxy == null)
|
||||
return;
|
||||
|
||||
foreach (int ruleId in _activeRules.Values)
|
||||
_sendProxy.RemoveOverride(ruleId);
|
||||
_activeRules.Clear();
|
||||
}
|
||||
|
||||
private readonly record struct TargetState(int OwnerSlot, int ActiveWeaponIndex);
|
||||
private readonly record struct ConcealableWeapon(int EntityIndex, int OwnerSlot, int ActiveWeaponIndex);
|
||||
private readonly record struct RuleKey(int RecipientSlot, int EntityIndex, string ClassName, string FieldPath);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.369" />
|
||||
<Compile Include="..\SendProxyNative.cs" Link="SendProxyNative.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,91 @@
|
||||
using CounterStrikeSharp.API;
|
||||
using CounterStrikeSharp.API.Core;
|
||||
using CounterStrikeSharp.API.Modules.Timers;
|
||||
using SendProxyInterop;
|
||||
using Timer = CounterStrikeSharp.API.Modules.Timers.Timer;
|
||||
|
||||
namespace SendProxyHealth23;
|
||||
|
||||
public sealed class SendProxyHealth23Plugin : BasePlugin
|
||||
{
|
||||
private const int RecipientAll = -1;
|
||||
private const int SpoofedHealth = 23;
|
||||
|
||||
private readonly Dictionary<int, int> _rulesByPawnIndex = new();
|
||||
private SendProxyNative? _sendProxy;
|
||||
private Timer? _syncTimer;
|
||||
|
||||
public override string ModuleName => "SendProxy Health 23";
|
||||
public override string ModuleVersion => "0.1.0";
|
||||
public override string ModuleAuthor => "OpenAI Codex";
|
||||
|
||||
public override void Load(bool hotReload)
|
||||
{
|
||||
_sendProxy = SendProxyNative.FromGameDirectory(Server.GameDirectory);
|
||||
_syncTimer = AddTimer(0.25f, SyncRules, TimerFlags.REPEAT);
|
||||
Server.NextFrame(SyncRules);
|
||||
}
|
||||
|
||||
public override void Unload(bool hotReload)
|
||||
{
|
||||
_syncTimer?.Kill();
|
||||
ClearRules();
|
||||
_sendProxy?.Dispose();
|
||||
_sendProxy = null;
|
||||
}
|
||||
|
||||
private void SyncRules()
|
||||
{
|
||||
if (_sendProxy == null)
|
||||
return;
|
||||
|
||||
int[] desiredPawnIndexes;
|
||||
try
|
||||
{
|
||||
desiredPawnIndexes = Utilities.GetPlayers()
|
||||
.Select(player => player.PlayerPawn.Value)
|
||||
.Where(pawn => pawn != null && pawn.IsValid)
|
||||
.Select(pawn => (int)pawn!.Index)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
}
|
||||
catch (NativeException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var desired = desiredPawnIndexes.ToHashSet();
|
||||
|
||||
foreach (int pawnIndex in _rulesByPawnIndex.Keys.Except(desired).ToArray())
|
||||
{
|
||||
_sendProxy.RemoveOverride(_rulesByPawnIndex[pawnIndex]);
|
||||
_rulesByPawnIndex.Remove(pawnIndex);
|
||||
}
|
||||
|
||||
foreach (int pawnIndex in desiredPawnIndexes)
|
||||
{
|
||||
if (_rulesByPawnIndex.ContainsKey(pawnIndex))
|
||||
continue;
|
||||
|
||||
int ruleId = _sendProxy.SetInt32(
|
||||
RecipientAll,
|
||||
pawnIndex,
|
||||
"CBaseEntity",
|
||||
"m_iHealth",
|
||||
SpoofedHealth);
|
||||
|
||||
if (ruleId != 0)
|
||||
_rulesByPawnIndex[pawnIndex] = ruleId;
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearRules()
|
||||
{
|
||||
if (_sendProxy == null)
|
||||
return;
|
||||
|
||||
foreach (int ruleId in _rulesByPawnIndex.Values)
|
||||
_sendProxy.RemoveOverride(ruleId);
|
||||
_rulesByPawnIndex.Clear();
|
||||
}
|
||||
}
|
||||
@@ -6,19 +6,40 @@ namespace SendProxyInterop;
|
||||
|
||||
public sealed class SendProxyNative : IDisposable
|
||||
{
|
||||
private const string RelativeLibraryPath = "addons/sendproxy/bin/linuxsteamrt64/sendproxy.so";
|
||||
|
||||
private readonly IntPtr _library;
|
||||
private readonly SetOverrideDelegate _setOverride;
|
||||
private readonly SetVectorFirstFromFieldDelegate _setVectorFirstFromField;
|
||||
private readonly RemoveOverrideDelegate _removeOverride;
|
||||
private readonly ClearOverridesDelegate _clearOverrides;
|
||||
private readonly MarkDirtyDelegate _markDirty;
|
||||
private readonly DumpSchemaDelegate _dumpSchema;
|
||||
private readonly DumpEnumDelegate _dumpEnum;
|
||||
|
||||
public SendProxyNative(string libraryPath)
|
||||
{
|
||||
_library = NativeLibrary.Load(libraryPath);
|
||||
_setOverride = GetExport<SetOverrideDelegate>("SendProxy_SetOverride");
|
||||
_setVectorFirstFromField = GetExport<SetVectorFirstFromFieldDelegate>("SendProxy_SetVectorFirstFromField");
|
||||
_removeOverride = GetExport<RemoveOverrideDelegate>("SendProxy_RemoveOverride");
|
||||
_clearOverrides = GetExport<ClearOverridesDelegate>("SendProxy_ClearOverrides");
|
||||
_markDirty = GetExport<MarkDirtyDelegate>("SendProxy_MarkDirty");
|
||||
_dumpSchema = GetExport<DumpSchemaDelegate>("SendProxy_DumpSchema");
|
||||
_dumpEnum = GetExport<DumpEnumDelegate>("SendProxy_DumpEnum");
|
||||
}
|
||||
|
||||
public static SendProxyNative FromGameDirectory(string gameDirectory)
|
||||
{
|
||||
string csgoDirectory = Path.GetFileName(gameDirectory).Equals("csgo", StringComparison.OrdinalIgnoreCase)
|
||||
? gameDirectory
|
||||
: Path.Combine(gameDirectory, "csgo");
|
||||
|
||||
string libraryPath = Path.Combine(csgoDirectory, RelativeLibraryPath);
|
||||
if (!File.Exists(libraryPath))
|
||||
throw new FileNotFoundException("SendProxy Metamod addon was not found. Install it under game/csgo/addons/sendproxy.", libraryPath);
|
||||
|
||||
return new SendProxyNative(libraryPath);
|
||||
}
|
||||
|
||||
public int SetBytes(int recipientSlot, int entityIndex, string className, string fieldPath, ReadOnlySpan<byte> value)
|
||||
@@ -61,6 +82,11 @@ public sealed class SendProxyNative : IDisposable
|
||||
return SetBytes(recipientSlot, entityIndex, className, fieldPath, bytes);
|
||||
}
|
||||
|
||||
public int SetVectorFirstFromField(int recipientSlot, int entityIndex, string className, string vectorFieldPath, string sourceFieldPath)
|
||||
{
|
||||
return _setVectorFirstFromField(recipientSlot, entityIndex, className, vectorFieldPath, sourceFieldPath);
|
||||
}
|
||||
|
||||
public bool RemoveOverride(int ruleId)
|
||||
{
|
||||
return _removeOverride(ruleId);
|
||||
@@ -76,6 +102,16 @@ public sealed class SendProxyNative : IDisposable
|
||||
return _markDirty(entityIndex, className, fieldPath);
|
||||
}
|
||||
|
||||
public void DumpSchema(string className, string contains)
|
||||
{
|
||||
_dumpSchema(className, contains);
|
||||
}
|
||||
|
||||
public void DumpEnum(string enumName)
|
||||
{
|
||||
_dumpEnum(enumName);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
NativeLibrary.Free(_library);
|
||||
@@ -89,6 +125,9 @@ public sealed class SendProxyNative : IDisposable
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
private delegate int SetOverrideDelegate(int recipientSlot, int entityIndex, string className, string fieldPath, byte[] value, int valueSize);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
private delegate int SetVectorFirstFromFieldDelegate(int recipientSlot, int entityIndex, string className, string vectorFieldPath, string sourceFieldPath);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
private delegate bool RemoveOverrideDelegate(int ruleId);
|
||||
@@ -99,4 +138,10 @@ public sealed class SendProxyNative : IDisposable
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
[return: MarshalAs(UnmanagedType.I1)]
|
||||
private delegate bool MarkDirtyDelegate(int entityIndex, string className, string fieldPath);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
private delegate void DumpSchemaDelegate(string className, string contains);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
private delegate void DumpEnumDelegate(string enumName);
|
||||
}
|
||||
|
||||
@@ -24,11 +24,11 @@ public sealed class SendProxyWeaponVisibilityPlugin : BasePlugin
|
||||
|
||||
public override void Load(bool hotReload)
|
||||
{
|
||||
_sendProxy = new SendProxyNative(GetSendProxyLibraryPath());
|
||||
_sendProxy = SendProxyNative.FromGameDirectory(Server.GameDirectory);
|
||||
_syncTimer = AddTimer(0.25f, SyncRules, TimerFlags.REPEAT);
|
||||
RegisterListener<Listeners.OnEntityCreated>(_ => Server.NextFrame(SyncRules));
|
||||
RegisterListener<Listeners.OnEntityDeleted>(_ => Server.NextFrame(SyncRules));
|
||||
SyncRules();
|
||||
Server.NextFrame(SyncRules);
|
||||
}
|
||||
|
||||
public override void Unload(bool hotReload)
|
||||
@@ -45,9 +45,17 @@ public sealed class SendProxyWeaponVisibilityPlugin : BasePlugin
|
||||
return;
|
||||
|
||||
var desired = new Dictionary<RuleKey, RuleValue>();
|
||||
var viewers = Utilities.GetPlayers()
|
||||
.Where(player => player.IsValid && !player.IsBot && !player.IsHLTV)
|
||||
.ToArray();
|
||||
CCSPlayerController[] viewers;
|
||||
try
|
||||
{
|
||||
viewers = Utilities.GetPlayers()
|
||||
.Where(player => player.IsValid && !player.IsBot && !player.IsHLTV)
|
||||
.ToArray();
|
||||
}
|
||||
catch (NativeException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AddHealthRules(desired, viewers);
|
||||
|
||||
@@ -211,14 +219,6 @@ public sealed class SendProxyWeaponVisibilityPlugin : BasePlugin
|
||||
_activeRules.Clear();
|
||||
}
|
||||
|
||||
private string GetSendProxyLibraryPath()
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
private readonly record struct RuleKey(int RecipientSlot, int EntityIndex, string ClassName, string FieldPath);
|
||||
|
||||
private readonly record struct RuleValue(byte[] Bytes)
|
||||
|
||||
Reference in New Issue
Block a user