179 lines
5.8 KiB
Markdown
179 lines
5.8 KiB
Markdown
# 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:
|
|
|
|
```text
|
|
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`:
|
|
|
|
```cpp
|
|
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:
|
|
|
|
```text
|
|
examples/CounterStrikeSharp/SendProxyNative.cs
|
|
```
|
|
|
|
It loads the native addon with `NativeLibrary.Load` and exposes byte-oriented and typed helpers. In a CSS plugin, resolve the native library from the normal addons layout:
|
|
|
|
```csharp
|
|
string addonsDirectory = Path.GetFullPath(Path.Combine(ModuleDirectory, "..", "..", ".."));
|
|
string libraryPath = Path.Combine(addonsDirectory, "sendproxy", "bin", "linuxsteamrt64", "sendproxy.so");
|
|
var sendProxy = new SendProxyNative(libraryPath);
|
|
|
|
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 Plugin
|
|
|
|
Example source:
|
|
|
|
```text
|
|
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.
|
|
|
|
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:
|
|
|
|
```text
|
|
--hl2sdk-root /home/csgo/am/hl2sdk-root
|
|
--mms_path /home/csgo/am/metamod-source
|
|
```
|
|
|
|
Configure and build:
|
|
|
|
```bash
|
|
mkdir -p build
|
|
cd build
|
|
python3 ../configure.py --enable-debug
|
|
ambuild
|
|
```
|
|
|
|
The packaged addon is written to:
|
|
|
|
```text
|
|
build/package/addons/sendproxy/
|
|
build/package/addons/metamod/sendproxy.vdf
|
|
```
|
|
|
|
## Installing
|
|
|
|
Copy the package into the CS2 server's `game/csgo/addons` tree:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
cd examples/CounterStrikeSharp/SendProxyWeaponVisibility
|
|
dotnet build -c Release
|
|
```
|
|
|
|
Install the built CSS plugin under:
|
|
|
|
```text
|
|
game/csgo/addons/counterstrikesharp/plugins/SendProxyWeaponVisibility/
|
|
```
|
|
|
|
Make sure `SendProxyNative.cs` is included in your CSS project or copied into your plugin source.
|
|
|
|
The example plugin expects the Metamod addon at:
|
|
|
|
```text
|
|
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`.
|