initial commit -- v1 code

This commit is contained in:
2026-08-10 14:21:07 +02:00
commit 1ecb7f777e
25 changed files with 2472 additions and 0 deletions

View File

@@ -0,0 +1,102 @@
using System;
using System.Buffers.Binary;
using System.Runtime.InteropServices;
namespace SendProxyInterop;
public sealed class SendProxyNative : IDisposable
{
private readonly IntPtr _library;
private readonly SetOverrideDelegate _setOverride;
private readonly RemoveOverrideDelegate _removeOverride;
private readonly ClearOverridesDelegate _clearOverrides;
private readonly MarkDirtyDelegate _markDirty;
public SendProxyNative(string libraryPath)
{
_library = NativeLibrary.Load(libraryPath);
_setOverride = GetExport<SetOverrideDelegate>("SendProxy_SetOverride");
_removeOverride = GetExport<RemoveOverrideDelegate>("SendProxy_RemoveOverride");
_clearOverrides = GetExport<ClearOverridesDelegate>("SendProxy_ClearOverrides");
_markDirty = GetExport<MarkDirtyDelegate>("SendProxy_MarkDirty");
}
public int SetBytes(int recipientSlot, int entityIndex, string className, string fieldPath, ReadOnlySpan<byte> value)
{
byte[] bytes = value.ToArray();
return _setOverride(recipientSlot, entityIndex, className, fieldPath, bytes, bytes.Length);
}
public int SetBool(int recipientSlot, int entityIndex, string className, string fieldPath, bool value)
{
Span<byte> bytes = stackalloc byte[1];
bytes[0] = value ? (byte)1 : (byte)0;
return SetBytes(recipientSlot, entityIndex, className, fieldPath, bytes);
}
public int SetUInt32(int recipientSlot, int entityIndex, string className, string fieldPath, uint value)
{
Span<byte> bytes = stackalloc byte[4];
BinaryPrimitives.WriteUInt32LittleEndian(bytes, value);
return SetBytes(recipientSlot, entityIndex, className, fieldPath, bytes);
}
public int SetInt32(int recipientSlot, int entityIndex, string className, string fieldPath, int value)
{
Span<byte> bytes = stackalloc byte[4];
BinaryPrimitives.WriteInt32LittleEndian(bytes, value);
return SetBytes(recipientSlot, entityIndex, className, fieldPath, bytes);
}
public int SetFloat(int recipientSlot, int entityIndex, string className, string fieldPath, float value)
{
Span<byte> bytes = stackalloc byte[4];
BinaryPrimitives.WriteInt32LittleEndian(bytes, BitConverter.SingleToInt32Bits(value));
return SetBytes(recipientSlot, entityIndex, className, fieldPath, bytes);
}
public int SetColor(int recipientSlot, int entityIndex, string className, string fieldPath, byte r, byte g, byte b, byte a)
{
Span<byte> bytes = stackalloc byte[] { r, g, b, a };
return SetBytes(recipientSlot, entityIndex, className, fieldPath, bytes);
}
public bool RemoveOverride(int ruleId)
{
return _removeOverride(ruleId);
}
public void ClearOverrides()
{
_clearOverrides();
}
public bool MarkDirty(int entityIndex, string className, string fieldPath)
{
return _markDirty(entityIndex, className, fieldPath);
}
public void Dispose()
{
NativeLibrary.Free(_library);
}
private T GetExport<T>(string name) where T : Delegate
{
return Marshal.GetDelegateForFunctionPointer<T>(NativeLibrary.GetExport(_library, name));
}
[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)]
[return: MarshalAs(UnmanagedType.I1)]
private delegate bool RemoveOverrideDelegate(int ruleId);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void ClearOverridesDelegate();
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
[return: MarshalAs(UnmanagedType.I1)]
private delegate bool MarkDirtyDelegate(int entityIndex, string className, string fieldPath);
}

View File

@@ -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>

View File

@@ -0,0 +1,246 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Timers;
using CounterStrikeSharp.API.Modules.Utils;
using SendProxyInterop;
using Timer = CounterStrikeSharp.API.Modules.Timers.Timer;
namespace SendProxyWeaponVisibility;
public sealed class SendProxyWeaponVisibilityPlugin : BasePlugin
{
private const int TeamT = 2;
private const int TeamCt = 3;
private const uint EfNoDraw = 0x20;
private const int SpoofedHealth = 42;
private readonly Dictionary<RuleKey, int> _activeRules = new();
private SendProxyNative? _sendProxy;
private Timer? _syncTimer;
public override string ModuleName => "SendProxy Weapon Visibility";
public override string ModuleVersion => "0.1.0";
public override string ModuleAuthor => "OpenAI Codex";
public override void Load(bool hotReload)
{
_sendProxy = new SendProxyNative(GetSendProxyLibraryPath());
_syncTimer = AddTimer(0.25f, SyncRules, TimerFlags.REPEAT);
RegisterListener<Listeners.OnEntityCreated>(_ => Server.NextFrame(SyncRules));
RegisterListener<Listeners.OnEntityDeleted>(_ => Server.NextFrame(SyncRules));
SyncRules();
}
public override void Unload(bool hotReload)
{
_syncTimer?.Kill();
ClearNativeRules();
_sendProxy?.Dispose();
_sendProxy = null;
}
private void SyncRules()
{
if (_sendProxy == null)
return;
var desired = new Dictionary<RuleKey, RuleValue>();
var viewers = Utilities.GetPlayers()
.Where(player => player.IsValid && !player.IsBot && !player.IsHLTV)
.ToArray();
AddHealthRules(desired, viewers);
foreach (var weapon in EnumerateWeapons())
{
var owner = GetOwner(weapon);
bool dropped = owner == null;
int weaponIndex = (int)weapon.Index;
string designerName = weapon.DesignerName;
foreach (var viewer in viewers)
{
int recipientSlot = viewer.Slot;
if (owner != null && IsOtherPlayerWeapon(viewer, owner))
{
AddDesiredRule(
desired,
recipientSlot,
weaponIndex,
"CBaseEntity",
"m_fEffects",
RuleValue.UInt32(EfNoDraw));
continue;
}
if (!dropped)
continue;
int team = (int)viewer.Team;
if (team == TeamT && IsAk47(designerName))
{
AddDesiredRule(
desired,
recipientSlot,
weaponIndex,
"CBaseModelEntity",
"m_clrRender",
RuleValue.Color(255, 0, 0, 255));
}
else if (team == TeamCt && IsM4A1S(designerName))
{
AddDesiredRule(
desired,
recipientSlot,
weaponIndex,
"CBaseModelEntity",
"m_clrRender",
RuleValue.Color(0, 96, 255, 255));
}
}
}
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 = _sendProxy.SetBytes(
key.RecipientSlot,
key.EntityIndex,
key.ClassName,
key.FieldPath,
value.Bytes);
if (ruleId != 0)
_activeRules[key] = ruleId;
}
}
private static void AddHealthRules(Dictionary<RuleKey, RuleValue> desired, IReadOnlyCollection<CCSPlayerController> viewers)
{
foreach (var viewer in viewers)
{
var viewerPawn = viewer.PlayerPawn.Value;
int viewerPawnIndex = viewerPawn != null && viewerPawn.IsValid ? (int)viewerPawn.Index : -1;
foreach (var target in viewers)
{
var targetPawn = target.PlayerPawn.Value;
if (targetPawn == null || !targetPawn.IsValid)
continue;
int targetPawnIndex = (int)targetPawn.Index;
if (targetPawnIndex == viewerPawnIndex)
continue;
AddDesiredRule(
desired,
viewer.Slot,
targetPawnIndex,
"CBaseEntity",
"m_iHealth",
RuleValue.Int32(SpoofedHealth));
}
}
}
private static void AddDesiredRule(
Dictionary<RuleKey, RuleValue> desired,
int recipientSlot,
int entityIndex,
string className,
string fieldPath,
RuleValue value)
{
desired[new RuleKey(recipientSlot, entityIndex, className, fieldPath)] = value;
}
private IEnumerable<CBasePlayerWeapon> EnumerateWeapons()
{
foreach (var entity in Utilities.GetAllEntities())
{
if (!entity.IsValid || !entity.DesignerName.StartsWith("weapon_", StringComparison.Ordinal))
continue;
CBasePlayerWeapon weapon = entity.As<CBasePlayerWeapon>();
if (weapon.IsValid)
yield return weapon;
}
}
private static CBaseEntity? GetOwner(CBasePlayerWeapon weapon)
{
var owner = weapon.OwnerEntity.Value;
return owner != null && owner.IsValid ? owner : null;
}
private static bool IsOtherPlayerWeapon(CCSPlayerController viewer, CBaseEntity owner)
{
var ownPawn = viewer.PlayerPawn.Value;
if (ownPawn == null || !ownPawn.IsValid)
return owner.DesignerName.Contains("player", StringComparison.Ordinal);
return owner.Index != ownPawn.Index && owner.DesignerName.Contains("player", StringComparison.Ordinal);
}
private static bool IsAk47(string designerName)
{
return designerName.Equals("weapon_ak47", StringComparison.Ordinal);
}
private static bool IsM4A1S(string designerName)
{
return designerName.Equals("weapon_m4a1_silencer", StringComparison.Ordinal);
}
private void ClearNativeRules()
{
if (_sendProxy == null)
return;
foreach (int ruleId in _activeRules.Values)
_sendProxy.RemoveOverride(ruleId);
_activeRules.Clear();
}
private string GetSendProxyLibraryPath()
{
string addonsDirectory = Path.GetFullPath(Path.Combine(ModuleDirectory, "..", "..", ".."));
string libraryPath = Path.Combine(addonsDirectory, "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)
{
public static RuleValue Int32(int value)
{
byte[] bytes = new byte[4];
BitConverter.TryWriteBytes(bytes, value);
return new RuleValue(bytes);
}
public static RuleValue UInt32(uint value)
{
byte[] bytes = new byte[4];
BitConverter.TryWriteBytes(bytes, value);
return new RuleValue(bytes);
}
public static RuleValue Color(byte r, byte g, byte b, byte a)
{
return new RuleValue(new[] { r, g, b, a });
}
}
}