using System; using System.Buffers.Binary; using System.Runtime.InteropServices; 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("SendProxy_SetOverride"); _setVectorFirstFromField = GetExport("SendProxy_SetVectorFirstFromField"); _removeOverride = GetExport("SendProxy_RemoveOverride"); _clearOverrides = GetExport("SendProxy_ClearOverrides"); _markDirty = GetExport("SendProxy_MarkDirty"); _dumpSchema = GetExport("SendProxy_DumpSchema"); _dumpEnum = GetExport("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 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 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 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 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 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 bytes = stackalloc byte[] { r, g, b, a }; 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); } public void ClearOverrides() { _clearOverrides(); } public bool MarkDirty(int entityIndex, string className, string fieldPath) { 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); } private T GetExport(string name) where T : Delegate { return Marshal.GetDelegateForFunctionPointer(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, 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); [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); [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); }