2026-08-10 17:31:33 +02:00
2026-08-10 14:21:07 +02:00
2026-08-10 14:21:07 +02:00
2026-08-10 17:31:33 +02:00
2026-08-10 14:21:07 +02:00
2026-08-10 14:21:07 +02:00
2026-08-10 14:21:07 +02:00
2026-08-10 14:21:07 +02:00
2026-08-10 17:02:48 +02:00

SendProxy for CS2

SendProxy is an experimental Counter-Strike 2 Metamod addon plus CounterStrikeSharp interop layer for per-recipient networked field spoofing.

The goal is similar to Source 1 SendProxy: let plugin code change the value serialized to one viewer without changing the real server entity state. For example, player A can see an entity field as one value while player B sees another value.

Current Architecture

The Metamod addon is intentionally small:

  • Hooks CServerSideClient::SendSnapshot to identify the recipient slot for the current snapshot.
  • Hooks CNetworkGameServer::PackEntity.
  • Looks up exact rules by packed entity index.
  • Temporarily writes raw bytes to the entity field.
  • Calls the original entity packer.
  • Restores the original bytes immediately.

There is no value parser or high-level rule language in Metamod. CounterStrikeSharp owns typed values, selectors, policy, and rule lifecycle.

Native rule shape:

recipient slot + entity index + schema field path = raw bytes

The native side resolves className + fieldPath to a schema offset when a rule is added. The pack hot path does not parse strings, walk entities, or query schema.

Native ABI

The addon exports a small C ABI from sendproxy.so:

extern "C" int SendProxy_SetOverride(
    int recipientSlot,
    int entityIndex,
    const char* className,
    const char* fieldPath,
    const void* value,
    int valueSize);

extern "C" bool SendProxy_RemoveOverride(int ruleId);
extern "C" void SendProxy_ClearOverrides();
extern "C" bool SendProxy_MarkDirty(int entityIndex, const char* className, const char* fieldPath);

recipientSlot may be -1 for all recipients, or 0..63 for one viewer.

SendProxy_SetOverride returns a rule ID. Keep this ID in managed code and pass it to SendProxy_RemoveOverride when the spoof is no longer wanted.

Disabled native rules are compacted on every map start.

CounterStrikeSharp Wrapper

The C# wrapper is in:

examples/CounterStrikeSharp/SendProxyNative.cs

It loads the native addon with NativeLibrary.Load and exposes byte-oriented and typed helpers. In a CSS plugin, construct it from Server.GameDirectory:

var sendProxy = SendProxyNative.FromGameDirectory(Server.GameDirectory);

int ruleId = sendProxy.SetInt32(
    recipientSlot: viewer.Slot,
    entityIndex: (int)targetPawn.Index,
    className: "CBaseEntity",
    fieldPath: "m_iHealth",
    value: 42);

sendProxy.RemoveOverride(ruleId);

Use SetBytes for custom/fixed-layout fields and typed helpers such as SetInt32, SetUInt32, SetFloat, SetBool, and SetColor for common values.

Example CSS Plugins

Health spoof smoke test:

examples/CounterStrikeSharp/SendProxyHealth23/

It spoofs every player pawn's CBaseEntity::m_iHealth to 23 for all recipients without changing real health.

Weapon visibility experiment:

examples/CounterStrikeSharp/SendProxyWeaponVisibility/

It demonstrates:

  • Spoofing other players' pawn health to 42 per viewer while leaving the viewer's own health real.
  • Hiding weapon entities owned by other players with spoofed CBaseEntity::m_fEffects.
  • Coloring dropped AK-47s red for Terrorist viewers.
  • Coloring dropped M4A1-S blue for CT viewers.

Concealed-carry weapon-list spoof:

examples/CounterStrikeSharp/SendProxyConcealedCarry/

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

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.

Building the Metamod Addon

Prerequisites:

  • AMBuild 2.2+
  • Metamod:Source
  • HL2SDK for CS2
  • funchook and generated protobuf headers as referenced by AMBuildScript

The defaults in configure.py match this workspace:

--hl2sdk-root /home/csgo/am/hl2sdk-root
--mms_path /home/csgo/am/metamod-source

Configure and build:

mkdir -p build
cd build
python3 ../configure.py --enable-debug
ambuild

The packaged addon is written to:

build/package/addons/sendproxy/
build/package/addons/metamod/sendproxy.vdf

Installing

Copy the package into the CS2 server's game/csgo/addons tree:

cp -a build/package/addons/sendproxy /home/csgo/serverfiles/game/csgo/addons/
cp -a build/package/addons/metamod/sendproxy.vdf /home/csgo/serverfiles/game/csgo/addons/metamod/

In this workspace, /home/csgo/codex/sendproxy/serverfiles is a bind-mounted target equivalent to /home/csgo/serverfiles.

Verify the native ABI exports:

nm -D /home/csgo/serverfiles/game/csgo/addons/sendproxy/bin/linuxsteamrt64/sendproxy.so \
  | rg 'SendProxy_(SetOverride|RemoveOverride|ClearOverrides|MarkDirty)'

Building the CSS Example

This container currently has the CounterStrikeSharp .NET runtime but not a .NET SDK, so the C# sample cannot be compiled here as-is.

On a machine with the .NET SDK:

cd examples/CounterStrikeSharp/SendProxyWeaponVisibility
dotnet build -c Release

Install the built CSS plugin under:

game/csgo/addons/counterstrikesharp/plugins/SendProxyWeaponVisibility/

Use the matching example directory name when building/installing another sample, for example SendProxyHealth23 or SendProxyConcealedCarry.

Make sure SendProxyNative.cs is included in your CSS project or copied into your plugin source.

The example plugin expects the Metamod addon at:

game/csgo/addons/sendproxy/bin/linuxsteamrt64/sendproxy.so

Notes and Limits

  • Only fields serialized through the hooked PackEntity path are affected.
  • The real server entity state is restored immediately after packing.
  • The value byte layout must match the server schema field layout.
  • The native addon validates that valueSize is not larger than the resolved schema field size when schema size is available.
  • Rule updates should be event-driven where possible. Avoid removing and recreating many rules every tick.
  • If per-entity rule lists become large, the next native optimization is indexing by (entityIndex, recipientSlot) instead of only entityIndex.
Description
A MetaMod+CounterStrikeSharp plugin for overriding netprops per-recipient
Readme 141 KiB
v1.0.0 Latest
2026-08-10 22:16:40 +02:00
Languages
C++ 67.2%
Python 32.3%
C 0.5%