From 11f9e2e54135ee81d939ce185876b5bf4445da4c Mon Sep 17 00:00:00 2001 From: SlynxCZ Date: Sat, 31 Jan 2026 19:58:28 +0400 Subject: [PATCH] update readme --- README.md | 332 ++++++++++------------------------------------ public/Example.cs | 174 ++++++++++++++++++++++++ 2 files changed, 247 insertions(+), 259 deletions(-) create mode 100644 public/Example.cs diff --git a/README.md b/README.md index a1f507d..9bc83d8 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,46 @@ # Ray-Trace -**Shared ray tracing interface for Metamod:Source & CounterStrikeSharp -plugins** +**Shared ray tracing interface for Metamod:Source & CounterStrikeSharp plugins** ------------------------------------------------------------------------ ## Overview -`Ray-Trace` is a lightweight **Metamod interface module** for\ +`Ray-Trace` is a lightweight **Metamod interface module** for **Counter-Strike 2** servers. It exposes a shared interface: `CRayTraceInterface001` which can be consumed from: -- Native **Metamod C++ plugins** -- Managed **CounterStrikeSharp C# plugins** +- Native **Metamod C++ plugins** +- Managed **CounterStrikeSharp C# plugins** The interface is implemented as a **C++ virtual class** and is accessed from C# by calling its **vtable functions directly** using a native handle. The goal is to provide a **single tracing backend** usable from both -worlds\ -without duplicating engine detours. +worlds without duplicating engine detours. ------------------------------------------------------------------------ ## Features -- Metamod meta interface (`CRayTraceInterface001`) -- Works in **C++ and C#** -- Physics / hitbox / world trace presets -- Custom collision masks -- Optional debug beam rendering -- Zero configuration -- Ultra low overhead -- ABI-safe (no STL types in interface) -- Virtual destructor for safe lifetime management +- Metamod meta interface (`CRayTraceInterface001`) +- Works in **C++ and C#** +- Physics / hitbox / world trace presets +- Custom collision masks +- Optional debug beam rendering +- Zero configuration +- Ultra low overhead +- ABI-safe (no STL types in interface) +- Virtual destructor for safe lifetime management ------------------------------------------------------------------------ ## Exposed Interface (C++) -``` cpp +```cpp class CRayTraceInterface { public: @@ -72,18 +70,20 @@ public: TraceResult* pOutResult ) = 0; }; -``` +```` -**Return value:** - true → trace hit something, TraceResult is valid\ -- false → no hit +**Return value:** ------------------------------------------------------------------------- +* `true` → trace hit something, `TraceResult` is valid +* `false` → no hit + +--- ## Getting the interface -**C++ (Metamod plugin)** +### C++ (Metamod plugin) -``` cpp +```cpp CRayTraceInterface* g_pRayTrace = nullptr; bool g_bRayTraceLoaded = false; @@ -106,36 +106,28 @@ bool LoadRayTrace() } ``` -**C# (CounterStrikeSharp plugin)** +### C# (CounterStrikeSharp plugin) -``` csharp -private nint g_pRayTraceHandle = nint.Zero; -private bool g_bRayTraceLoaded = false; +For managed plugins, use the provided official wrapper: -public override void Load(bool hotReload) -{ - g_pRayTraceHandle = Utilities.MetaFactory("CRayTraceInterface001"); - - if (g_pRayTraceHandle == nint.Zero) - { - throw new Exception("Failed to get Ray-Trace interface handle"); - } - - Bind(); - g_bRayTraceLoaded = true; -} +``` +public/Example.cs ``` -The returned handle is a pointer to the native CRayTraceInterface -object. +This file contains: ------------------------------------------------------------------------- +* Correct vtable bindings for Linux & Windows +* Native-compatible struct layouts (`TraceOptions`, `TraceResult`) +* High-level safe API for tracing +* No need to manually bind delegates + +--- ## Calling methods from C++ (Metamod) -**TraceShape example** +### TraceShape example -``` cpp +```cpp Vector vecOrigin{}; QAngle angView{}; TraceOptions traceOpts{}; @@ -165,250 +157,72 @@ if (g_pRayTrace && g_bRayTraceLoaded) } ``` -**TraceEndShape example** - -``` cpp -TraceResult traceResult{}; - -bool bHit = g_pRayTrace->TraceEndShape( - &vecStartPos, - &vecEndPos, - nullptr, - &traceOpts, - &traceResult -); -``` - -**TraceShapeEx (low-level)** - -``` cpp -Ray_t ray{}; -CTraceFilter filter( - static_cast(MASK_SHOT_FULL), - COLLISION_GROUP_DEFAULT, - true -); - -TraceResult traceResult{}; - -bool bHit = g_pRayTrace->TraceShapeEx( - &vecStartPos, - &vecEndPos, - &filter, - ray, - &traceResult -); -``` - ------------------------------------------------------------------------- +--- ## Calling methods from C# (CounterStrikeSharp plugin) -``` csharp -private delegate bool TraceShapeFn( - nint pThis, - nint pOrigin, - nint pAngles, - nint pIgnoreEntity, - nint pOptions, - nint pOutResult -); +The official managed API is implemented in: -private TraceShapeFn? _traceShape; -private TraceShapeFn? _traceEndShape; -private TraceShapeFn? _traceShapeEx; - -private void Bind() -{ - _traceShape = VirtualFunction.Create(g_pRayTraceHandle, 1); - _traceEndShape = VirtualFunction.Create(g_pRayTraceHandle, 2); - _traceShapeEx = VirtualFunction.Create(g_pRayTraceHandle, 3); -} - -public bool TraceShape( - nint pOrigin, - nint pAngles, - nint pIgnoreEntity, - nint pOptions, - nint pOutResult) -{ - if (!g_bRayTraceLoaded || g_pRayTraceHandle == nint.Zero) - return false; - - return _traceShape!( - g_pRayTraceHandle, - pOrigin, - pAngles, - pIgnoreEntity, - pOptions, - pOutResult - ); -} +``` +public/Example.cs ``` ------------------------------------------------------------------------- +### Example usage -## Memory allocation from C# (Important) +```csharp +using RayTrace; +using CounterStrikeSharp.API.Modules.Utils; -When calling `TraceShape` or `TraceEndShape` from C#, the plugin **must -allocate native memory** for the following structures: - -- `TraceOptions` -- `TraceResult` - -These parameters are native pointers in C++ (`TraceOptions*` and -`TraceResult*`) and must remain valid for the duration of the call. - -The recommended and safest approach is using **stackalloc** (or unsafe -stack variables) to provide native memory on the stack. - -Failing to allocate valid memory for these parameters will result in -crashes or undefined behavior. - -### Example (C# stackalloc) - -``` csharp -[StructLayout(LayoutKind.Sequential, Pack = 8)] -public struct TraceOptions +public void DoTrace(CCSPlayerController player) { - public ulong InteractsWith; - public ulong InteractsExclude; - public int DrawBeam; -} + Vector origin = player.PlayerPawn.Value!.AbsOrigin; + QAngle angles = player.PlayerPawn.Value!.EyeAngles; -[StructLayout(LayoutKind.Sequential, Pack = 8)] -public struct TraceResult -{ - public Vector EndPos; - public nint HitEntity; - public float Fraction; - public int AllSolid; - public Vector Normal; -} - -unsafe -{ - Vector origin = player.Position; - QAngle angles = player.ViewAngles; - - TraceOptions* opts = stackalloc TraceOptions[1]; - opts->InteractsWith = (ulong)MASK_SHOT_FULL; - opts->InteractsExclude = 0; - opts->DrawBeam = 0; - - TraceResult* result = stackalloc TraceResult[1]; - - bool hit = _traceShape!( - g_pRayTraceHandle, - (nint)&origin, - (nint)&angles, - nint.Zero, - (nint)opts, - (nint)result + TraceOptions options = new( + InteractionLayers.MASK_SHOT_FULL ); - if (hit) + if (CRayTrace.TraceShape(origin, angles, null, options, out TraceResult result)) { - Console.WriteLine($"Hit at: {result->EndPos}"); + Console.WriteLine($"Hit fraction: {result.Fraction}"); + Console.WriteLine($"EndPos: {result.EndPos}"); } } ``` ------------------------------------------------------------------------- +--- -## Low-level usage from C# (Ray_t & CTraceFilter) +## VTable offsets (ABI note) -When using the low-level method `TraceShapeEx` from a C# plugin, the -plugin must provide its own native-compatible implementations of the -following engine structures: +Due to C++ ABI differences: -- `Ray_t` -- `CTraceFilter` +| Platform | TraceShape index | TraceEndShape index | +| ------------------- | ---------------- | ------------------- | +| Linux (Itanium ABI) | 2 | 3 | +| Windows (MSVC ABI) | 1 | 2 | -These structures are not exposed directly by the Ray-Trace interface and -must be recreated in managed code with correct memory layout. +`public/Example.cs` already applies the correct offsets internally. -### Ray_t (C#) - -The C# plugin must define a struct that matches the native `Ray_t` -layout used by the engine. - -Example (simplified): - -``` csharp -[StructLayout(LayoutKind.Sequential)] -public struct Ray_t -{ - public Vector3 m_vecStart; - public Vector3 m_vecDelta; - public byte m_IsRay; - public byte m_IsSwept; -} -``` - -(Exact layout depends on the engine version and must match native -memory.) - -**CTraceFilter (C#)**\ -For `CTraceFilter`, the plugin must: - -- Define a managed struct matching the native layout. -- Resolve the CTraceFilter vtable pointer using a signature scan. -- Assign the resolved vtable to the struct before calling - TraceShapeEx. - -Example concept: - -``` csharp -[StructLayout(LayoutKind.Sequential)] -public unsafe struct CTraceFilter -{ - public nint __vtable; - public ulong m_nInteractsWith; - public T ...; -} -``` - -**Important notes** - This setup is only required when using the -low-level API: - TraceShapeEx(...) - High-level functions (TraceShape, -TraceEndShape) do not require custom Ray_t or CTraceFilter handling from -C#. - Incorrect structure layout or invalid vtable resolution will -result in crashes or undefined behavior. - This is considered an -advanced use case intended for engine-level plugins. - ------------------------------------------------------------------------- +--- ## Notes about ABI & Destructor -- CRayTraceInterface has a virtual destructor. -- The object is owned by the Ray-Trace Metamod module. -- Plugins must never call delete on the interface pointer. -- C# must only clear its handle on unload. -- All parameters are passed as native pointers (nint). +* `CRayTraceInterface` has a virtual destructor. +* The object is owned by the Ray-Trace Metamod module. +* Plugins must never call `delete` on the interface pointer. +* C# must only clear its handle on unload. +* All parameters are passed as native pointers (`nint`). -# Build - -## Requirements - -- HL2SDK-CS2 -- Metamod:Source -- CMake - -``` bash -git clone https://github.com/FUNPLAY-pro-CS2/Ray-Trace.git -cd Ray-Trace -git submodule update --init --recursive -docker compose -f docker/docker-compose.yml up -``` - ------------------------------------------------------------------------- +--- ## License -GPLv3\ -https://www.gnu.org/licenses/gpl-3.0.en.html +GPLv3 +[https://www.gnu.org/licenses/gpl-3.0.en.html](https://www.gnu.org/licenses/gpl-3.0.en.html) + +--- ## Author -**Michal "Slynx" Přikryl**\ -https://slynxdev.cz +**Michal "Slynx" Přikryl** +[https://slynxdev.cz](https://slynxdev.cz) diff --git a/public/Example.cs b/public/Example.cs new file mode 100644 index 0000000..1e05f3a --- /dev/null +++ b/public/Example.cs @@ -0,0 +1,174 @@ +using System.Numerics; +using System.Runtime.InteropServices; +using CounterStrikeSharp.API; +using CounterStrikeSharp.API.Core; +using CounterStrikeSharp.API.Modules.Memory; +using CounterStrikeSharp.API.Modules.Utils; +using Vector = CounterStrikeSharp.API.Modules.Utils.Vector; + +namespace RayTrace +{ +#region Native Structs (matching C++ layout exactly) + [Flags] + public enum InteractionLayers: ulong + { + Solid = 0x1, + Hitboxes = 0x2, + Trigger = 0x4, + Sky = 0x8, + PlayerClip = 0x10, + NPCClip = 0x20, + BlockLOS = 0x40, + BlockLight = 0x80, + Ladder = 0x100, + Pickup = 0x200, + BlockSound = 0x400, + NoDraw = 0x800, + Window = 0x1000, + PassBullets = 0x2000, + WorldGeometry = 0x4000, + Water = 0x8000, + Slime = 0x10000, + TouchAll = 0x20000, + Player = 0x40000, + NPC = 0x80000, + Debris = 0x100000, + Physics_Prop = 0x200000, + NavIgnore = 0x400000, + NavLocalIgnore = 0x800000, + PostProcessingVolume = 0x1000000, + UnusedLayer3 = 0x2000000, + CarriedObject = 0x4000000, + PushAway = 0x8000000, + ServerEntityOnClient = 0x10000000, + CarriedWeapon = 0x20000000, + StaticLevel = 0x40000000, + csgo_team1 = 0x80000000, + csgo_team2 = 0x100000000, + csgo_grenadeclip = 0x200000000, + csgo_droneclip = 0x400000000, + csgo_moveable = 0x800000000, + csgo_opaque = 0x1000000000, + csgo_monster = 0x2000000000, + csgo_thrown_grenade = 0x8000000000, + + MASK_SHOT_PHYSICS = Solid | PlayerClip | Window | PassBullets | Player | NPC | Physics_Prop, + MASK_SHOT_HITBOX = Hitboxes | Player | NPC, + MASK_SHOT_FULL = MASK_SHOT_PHYSICS | Hitboxes, + MASK_WORLD_ONLY = Solid | Window | PassBullets, + MASK_GRENADE = Solid | Window | Physics_Prop | PassBullets, + MASK_BRUSH_ONLY = Solid | Window, + MASK_PLAYER_MOVE = Solid | Window | PlayerClip | PassBullets, + MASK_NPC_MOVE = Solid | Window | NPCClip | PassBullets + } + + [StructLayout(LayoutKind.Explicit, Size = 24)] + public struct TraceOptions + { + [FieldOffset(0)] public ulong InteractsWith; + [FieldOffset(8)] public ulong InteractsExclude; + [FieldOffset(16)] public int DrawBeam; + + public TraceOptions() + { + InteractsWith = (ulong)InteractionLayers.MASK_SHOT_PHYSICS; + InteractsExclude = 0; + DrawBeam = 0; + } + + public TraceOptions(InteractionLayers interactsWith, InteractionLayers interactsExclude = 0, bool drawBeam = false) + { + InteractsWith = (ulong)interactsWith; + InteractsExclude = (ulong)interactsExclude; + DrawBeam = drawBeam ? 1 : 0; + } + } + + [StructLayout(LayoutKind.Explicit, Size = 44)] + public struct TraceResult + { + [FieldOffset(0)] public float EndPosX; + [FieldOffset(4)] public float EndPosY; + [FieldOffset(8)] public float EndPosZ; + [FieldOffset(16)] public nint HitEntity; + [FieldOffset(24)] public float Fraction; + [FieldOffset(28)] public int AllSolid; + [FieldOffset(32)] public float NormalX; + [FieldOffset(36)] public float NormalY; + [FieldOffset(40)] public float NormalZ; + + public Vector3 EndPos => new(EndPosX, EndPosY, EndPosZ); + public Vector3 Normal => new(NormalX, NormalY, NormalZ); + public bool DidHit => Fraction < 1.0f; + public bool IsAllSolid => AllSolid != 0; + } +#endregion + + public static class CRayTrace + { + private static nint g_pRayTraceHandle = nint.Zero; + private static bool g_bRayTraceLoaded = false; + + private static Func? _traceShape; + private static Func? _traceEndShape; + + public static void Init() + { + g_pRayTraceHandle = (nint)Utilities.MetaFactory("CRayTraceInterface001")!; + + if (g_pRayTraceHandle == nint.Zero) + throw new Exception("Failed to get Ray-Trace interface handle. Is Ray-Trace MetaMod module loaded?"); + + Bind(); + g_bRayTraceLoaded = true; + } + + private static void Bind() + { + _traceShape = VirtualFunction.Create(g_pRayTraceHandle, 2); + _traceEndShape = VirtualFunction.Create(g_pRayTraceHandle, 3); + } + + public static unsafe bool TraceShape(Vector origin, QAngle angles, CBaseEntity? ignoreEntity, TraceOptions options, out TraceResult result) + { + result = default; + + if (!g_bRayTraceLoaded || g_pRayTraceHandle == nint.Zero) + return false; + + TraceResult resultBuffer = default; + TraceOptions optionsBuffer = options; + + bool success = _traceShape!(g_pRayTraceHandle, + origin.Handle, + angles.Handle, + ignoreEntity?.Handle ?? nint.Zero, + (nint)(&optionsBuffer), + (nint)(&resultBuffer)); + + result = resultBuffer; + return success; + } + + public static unsafe bool TraceEndShape(Vector origin, Vector endOrigin, CBaseEntity? ignoreEntity, TraceOptions options, out TraceResult result) + { + result = default; + + if (!g_bRayTraceLoaded || g_pRayTraceHandle == nint.Zero) + return false; + + TraceResult resultBuffer = default; + TraceOptions optionsBuffer = options; + + bool success = _traceEndShape!(g_pRayTraceHandle, + origin.Handle, + endOrigin.Handle, + ignoreEntity?.Handle ?? nint.Zero, + (nint)(&optionsBuffer), + (nint)(&resultBuffer)); + + result = resultBuffer; + return success; + } + } +}