diff --git a/managed/src/SwiftlyS2.Core/Modules/Hooks/HookManager.cs b/managed/src/SwiftlyS2.Core/Modules/Hooks/HookManager.cs index 6965070bd..3e2b217fc 100644 --- a/managed/src/SwiftlyS2.Core/Modules/Hooks/HookManager.cs +++ b/managed/src/SwiftlyS2.Core/Modules/Hooks/HookManager.cs @@ -1,228 +1,209 @@ +using System.Collections.Concurrent; using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; using Spectre.Console; using SwiftlyS2.Core.Natives; using SwiftlyS2.Shared.Memory; namespace SwiftlyS2.Core.Hooks; +[UnmanagedFunctionPointer(CallingConvention.Cdecl)] +internal unsafe delegate void MidHookInternalDelegate( void* contextPtr ); + internal class HookManager { + private class HookNode + { + public required Guid Id { get; init; } + public nint HookHandle { get; set; } + public nint OriginalFuncPtr { get; set; } + public required Func, Delegate> CallbackBuilder { get; init; } + public Delegate? BuiltDelegate { get; set; } + public nint BuiltPointer { get; set; } + } - private class HookNode - { - public required Guid Id { get; init; } - - public nint HookHandle { get; set; } - public nint OriginalFuncPtr { get; set; } - public required Func, Delegate> CallbackBuilder { get; init; } - public Delegate? BuiltDelegate { get; set; } - public nint BuiltPointer { get; set; } - } - - private class MidHookNode - { - public required Guid Id { get; init; } - public nint HookHandle { get; set; } - public required MidHookDelegate BuiltDelegate { get; init; } - } - - private class HookChain - { - public bool Hooked { get; set; } = false; - public required nint FunctionAddress { get; set; } - public nint HookHandle { get; set; } - public nint OriginalFunctionAddress { get; set; } - public List Nodes { get; } = new(); - } - - private class MidHookChain - { - public bool Hooked { get; set; } = false; - public required nint Address { get; set; } - public nint HookHandle { get; set; } - public List Nodes { get; } = new(); - } - - private readonly Lock _sync = new(); - private readonly Dictionary _chains = new(); - private readonly Dictionary _midChains = new(); - - public bool IsMidHooked( nint address ) - { - lock (_sync) + private class MidHookNode { - return _midChains.TryGetValue(address, out var chain) && chain.Hooked; + public required Guid Id { get; init; } + public nint HookHandle { get; set; } + public required MidHookDelegate BuiltDelegate { get; init; } } - } - public bool IsHooked( nint functionAddress ) - { - lock (_sync) + private class HookChain { - return _chains.TryGetValue(functionAddress, out var chain) && chain.Hooked; + public bool Hooked { get; set; } = false; + public required nint FunctionAddress { get; set; } + public nint HookHandle { get; set; } + public nint OriginalFunctionAddress { get; set; } + public List Nodes { get; } = []; } - } - public nint GetOriginal( nint functionAddress ) - { - lock (_sync) + private class MidHookChain { - if (_chains.TryGetValue(functionAddress, out var chain)) - { - if (!chain.Hooked) - { - return functionAddress; - } - if (chain.Nodes.Count == 0) - { - return chain.OriginalFunctionAddress; - } - return chain.Nodes[^1].OriginalFuncPtr; - } - return nint.Zero; + public bool Hooked { get; set; } = false; + public required nint Address { get; set; } + public nint HookHandle { get; set; } + public List Nodes { get; } = []; + public MidHookInternalDelegate? InternalCallback { get; set; } // Keep delegate alive + } + + private readonly ConcurrentDictionary chains = new(); + private readonly ConcurrentDictionary midChains = new(); + + public bool IsMidHooked( nint address ) + { + return midChains.TryGetValue(address, out var chain) && chain.Hooked; + } + + public bool IsHooked( nint functionAddress ) + { + return chains.TryGetValue(functionAddress, out var chain) && chain.Hooked; } - } - public Guid AddMidHook( nint address, MidHookDelegate callback ) - { - MidHookChain chain; - MidHookNode node = new MidHookNode { - Id = Guid.NewGuid(), - BuiltDelegate = callback, - }; + public nint GetOriginal( nint functionAddress ) + { + return chains.TryGetValue(functionAddress, out var chain) + ? !chain.Hooked ? functionAddress : chain.Nodes.Count == 0 ? chain.OriginalFunctionAddress : chain.Nodes[^1].OriginalFuncPtr + : nint.Zero; + } - lock (_sync) + public Guid AddMidHook( nint address, MidHookDelegate callback ) { - if (!_midChains.TryGetValue(address, out chain)) - { - chain = new MidHookChain { Address = address }; - chain.HookHandle = NativeHooks.AllocateMHook(); - MidHookDelegate _unmanagedCallback = ( ref MidHookContext ctx ) => + var node = new MidHookNode { + Id = Guid.NewGuid(), + BuiltDelegate = callback, + }; + + if (!midChains.TryGetValue(address, out var chain)) { - try - { - foreach (var n in chain.Nodes) + chain = new MidHookChain { + Address = address, + HookHandle = NativeHooks.AllocateMHook() + }; + + MidHookInternalDelegate internalCallback; + + unsafe { - n.BuiltDelegate(ref ctx); + internalCallback = ( contextPtr ) => + { + try + { + ref var ctx = ref Unsafe.AsRef(contextPtr); + foreach (var n in chain.Nodes) + { + n.BuiltDelegate(ref ctx); + } + } + catch (Exception e) + { + if (!GlobalExceptionHandler.Handle(e)) return; + } + }; } - } - catch (Exception e) - { - if (!GlobalExceptionHandler.Handle(e)) return; - } - }; - NativeHooks.SetMHook(chain.HookHandle, address, Marshal.GetFunctionPointerForDelegate(_unmanagedCallback)); - NativeHooks.EnableMHook(chain.HookHandle); - chain.Hooked = true; - _midChains[address] = chain; - } - chain.Nodes.Add(node); - } - return node.Id; - } + // Keep delegate alive to prevent GC + chain.InternalCallback = internalCallback; + var callbackPtr = Marshal.GetFunctionPointerForDelegate(internalCallback); - public Guid AddHook( nint functionAddress, Func, Delegate> callbackBuilder ) - { - HookChain chain; - HookNode node = new HookNode { - Id = Guid.NewGuid(), - CallbackBuilder = callbackBuilder, - }; + NativeHooks.SetMHook(chain.HookHandle, address, callbackPtr); + NativeHooks.EnableMHook(chain.HookHandle); - lock (_sync) - { - if (!_chains.TryGetValue(functionAddress, out chain)) - { - chain = new HookChain { FunctionAddress = functionAddress }; - _chains[functionAddress] = chain; - } - chain.Nodes.Add(node); - RebuildChain(chain); - } + chain.Hooked = true; + midChains[address] = chain; + } - return node.Id; - } + chain.Nodes.Add(node); + return node.Id; + } - public void RemoveMidHook( List nodeIds ) - { - lock (_sync) + public Guid AddHook( nint functionAddress, Func, Delegate> callbackBuilder ) { - var chains = _midChains.Values.Where(c => c.Nodes.Any(n => nodeIds.Contains(n.Id))).ToList(); - if (chains.Count == 0) return; - foreach (var chain in chains) - { - chain.Nodes.RemoveAll(n => nodeIds.Contains(n.Id)); - } + var node = new HookNode { + Id = Guid.NewGuid(), + CallbackBuilder = callbackBuilder, + }; + + if (!chains.TryGetValue(functionAddress, out var chain)) + { + chain = new HookChain { FunctionAddress = functionAddress }; + chains[functionAddress] = chain; + } + chain.Nodes.Add(node); + RebuildChain(chain); + + return node.Id; } - } - public void Remove( List nodeIds ) - { - lock (_sync) + public void RemoveMidHook( List nodeIds ) { - var chains = _chains.Values.Where(c => c.Nodes.Any(n => nodeIds.Contains(n.Id))).ToList(); - if (chains.Count == 0) return; - foreach (var chain in chains) - { - chain.Nodes.RemoveAll(n => nodeIds.Contains(n.Id)); - RebuildChain(chain); - } + midChains.Values.Where(c => c.Nodes.Any(n => nodeIds.Contains(n.Id))).ToList().ForEach(chain => + { + _ = chain.Nodes.RemoveAll(n => nodeIds.Contains(n.Id)); + }); } - } - private void RebuildChain( HookChain chain ) - { - try + public void Remove( List nodeIds ) { - // Rebuild delegates from first to last, wiring each to previous pointer (or original for first) - if (chain.Hooked) - { - for (int i = 0; i < chain.Nodes.Count; i++) + chains.Values.Where(c => c.Nodes.Any(n => nodeIds.Contains(n.Id))).ToList().ForEach(chain => { - chain.Nodes[i].BuiltDelegate = null; - chain.Nodes[i].BuiltPointer = nint.Zero; - if (chain.Nodes[i].HookHandle != 0) - { - NativeHooks.DeallocateHook(chain.Nodes[i].HookHandle); - chain.Nodes[i].HookHandle = 0; - } - } - chain.OriginalFunctionAddress = 0; - NativeHooks.DeallocateHook(chain.HookHandle); - chain.HookHandle = 0; - chain.Hooked = false; - } - chain.HookHandle = NativeHooks.AllocateHook(); - - for (int i = 0; i < chain.Nodes.Count; i++) - { - var node = chain.Nodes[i]; - - var built = node.CallbackBuilder.Invoke(() => node.OriginalFuncPtr); - node.BuiltDelegate = built; - node.BuiltPointer = Marshal.GetFunctionPointerForDelegate(node.BuiltDelegate); - if (i == 0) + _ = chain.Nodes.RemoveAll(n => nodeIds.Contains(n.Id)); + RebuildChain(chain); + }); + } + + private void RebuildChain( HookChain chain ) + { + try { - NativeHooks.SetHook(chain.HookHandle, chain.FunctionAddress, node.BuiltPointer); - node.OriginalFuncPtr = NativeHooks.GetHookOriginal(chain.HookHandle); - chain.OriginalFunctionAddress = node.OriginalFuncPtr; - NativeHooks.EnableHook(chain.HookHandle); - chain.Hooked = true; + // Rebuild delegates from first to last, wiring each to previous pointer (or original for first) + if (chain.Hooked) + { + for (var i = 0; i < chain.Nodes.Count; i++) + { + chain.Nodes[i].BuiltDelegate = null; + chain.Nodes[i].BuiltPointer = nint.Zero; + if (chain.Nodes[i].HookHandle != 0) + { + NativeHooks.DeallocateHook(chain.Nodes[i].HookHandle); + chain.Nodes[i].HookHandle = 0; + } + } + chain.OriginalFunctionAddress = 0; + NativeHooks.DeallocateHook(chain.HookHandle); + chain.HookHandle = 0; + chain.Hooked = false; + } + chain.HookHandle = NativeHooks.AllocateHook(); + + for (var i = 0; i < chain.Nodes.Count; i++) + { + var node = chain.Nodes[i]; + + var built = node.CallbackBuilder.Invoke(() => node.OriginalFuncPtr); + node.BuiltDelegate = built; + node.BuiltPointer = Marshal.GetFunctionPointerForDelegate(node.BuiltDelegate); + if (i == 0) + { + NativeHooks.SetHook(chain.HookHandle, chain.FunctionAddress, node.BuiltPointer); + node.OriginalFuncPtr = NativeHooks.GetHookOriginal(chain.HookHandle); + chain.OriginalFunctionAddress = node.OriginalFuncPtr; + NativeHooks.EnableHook(chain.HookHandle); + chain.Hooked = true; + } + else + { + node.HookHandle = NativeHooks.AllocateHook(); + NativeHooks.SetHook(node.HookHandle, chain.Nodes[i - 1].OriginalFuncPtr, node.BuiltPointer); + NativeHooks.EnableHook(node.HookHandle); + node.OriginalFuncPtr = NativeHooks.GetHookOriginal(node.HookHandle); + } + } } - else + catch (Exception e) { - node.HookHandle = NativeHooks.AllocateHook(); - NativeHooks.SetHook(node.HookHandle, chain.Nodes[i - 1].OriginalFuncPtr, node.BuiltPointer); - NativeHooks.EnableHook(node.HookHandle); - node.OriginalFuncPtr = NativeHooks.GetHookOriginal(node.HookHandle); + if (!GlobalExceptionHandler.Handle(e)) return; + AnsiConsole.WriteException(e); } - } - } - catch (Exception e) - { - if (!GlobalExceptionHandler.Handle(e)) return; - AnsiConsole.WriteException(e); } - } } \ No newline at end of file diff --git a/managed/src/SwiftlyS2.Core/Modules/Memory/UnmanagedMemory.cs b/managed/src/SwiftlyS2.Core/Modules/Memory/UnmanagedMemory.cs index 94b749e41..cc4e9da19 100644 --- a/managed/src/SwiftlyS2.Core/Modules/Memory/UnmanagedMemory.cs +++ b/managed/src/SwiftlyS2.Core/Modules/Memory/UnmanagedMemory.cs @@ -1,57 +1,70 @@ using Microsoft.Extensions.Logging; using SwiftlyS2.Core.Hooks; -using SwiftlyS2.Core.Natives.NativeObjects; using SwiftlyS2.Shared.Memory; +using SwiftlyS2.Core.Natives.NativeObjects; namespace SwiftlyS2.Core.Memory; internal class UnmanagedMemory : NativeHandle, IUnmanagedMemory, IDisposable { public new nint Address { get; private set; } - private HookManager _HookManager { get; set; } - private ILogger _Logger { get; set; } - public List Hooks { get; } = new(); + public List Hooks { get; init; } + private readonly Lock hooksLock; + private readonly HookManager hookManager; + private readonly ILogger logger; public UnmanagedMemory( nint address, HookManager hookManager, ILoggerFactory loggerFactory ) : base(address) { - Address = address; - _HookManager = hookManager; - _Logger = loggerFactory.CreateLogger(); + this.Address = address; + this.Hooks = []; + + this.hooksLock = new(); + this.hookManager = hookManager; + this.logger = loggerFactory.CreateLogger(); } public Guid AddHook( MidHookDelegate callback ) { try { - var id = _HookManager.AddMidHook(Address, callback); - Hooks.Add(id); - return id; + lock (hooksLock) + { + var id = hookManager.AddMidHook(Address, callback); + Hooks.Add(id); + return id; + } } catch (Exception e) { if (!GlobalExceptionHandler.Handle(e)) return Guid.Empty; - _Logger.LogError(e, "Failed to add midhook to function {0}.", Address); + logger.LogError(e, "Failed to add midhook to function {Address}.", Address); return Guid.Empty; } } public void Dispose() { - _HookManager.Remove(Hooks); - Hooks.Clear(); + lock (hooksLock) + { + hookManager.Remove(Hooks); + Hooks.Clear(); + } } public void RemoveHook( Guid id ) { try { - _HookManager.RemoveMidHook(new List { id }); - Hooks.Remove(id); + lock (hooksLock) + { + hookManager.RemoveMidHook([id]); + _ = Hooks.Remove(id); + } } catch (Exception e) { if (!GlobalExceptionHandler.Handle(e)) return; - _Logger.LogError(e, "Failed to remove midhook {0} from function {1}.", id, Address); + logger.LogError(e, "Failed to remove midhook {Id} from function {Address}.", id, Address); } } } \ No newline at end of file diff --git a/managed/src/SwiftlyS2.Shared/Modules/Memory/IUnmanagedMemory.cs b/managed/src/SwiftlyS2.Shared/Modules/Memory/IUnmanagedMemory.cs index 680dd338b..8b558ce77 100644 --- a/managed/src/SwiftlyS2.Shared/Modules/Memory/IUnmanagedMemory.cs +++ b/managed/src/SwiftlyS2.Shared/Modules/Memory/IUnmanagedMemory.cs @@ -29,10 +29,25 @@ public struct MidHookContext { public Xmm XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7, XMM8, XMM9, XMM10, XMM11, XMM12, XMM13, XMM14, XMM15; public nuint RFLAGS, R15, R14, R13, R12, R11, R10, R9, R8, RDI, RSI, RDX, RCX, RBX, RAX, RBP, RSP, TRAMPOLINE_RSP, RIP; + + public override readonly unsafe string ToString() + { + var xmmRegs = new Xmm[] { XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7, XMM8, XMM9, XMM10, XMM11, XMM12, XMM13, XMM14, XMM15 }; + return string.Join("\n", new[] + { + $" RAX: 0x{RAX:X16} RBX: 0x{RBX:X16} RCX: 0x{RCX:X16} RDX: 0x{RDX:X16}", + $" RSI: 0x{RSI:X16} RDI: 0x{RDI:X16} RBP: 0x{RBP:X16} RSP: 0x{RSP:X16}", + $" R8: 0x{R8:X16} R9: 0x{R9:X16} R10: 0x{R10:X16} R11: 0x{R11:X16}", + $" R12: 0x{R12:X16} R13: 0x{R13:X16} R14: 0x{R14:X16} R15: 0x{R15:X16}", + string.Join("\n", Enumerable.Range(0, 8).Select(i =>$" XMM{i:D2}: {xmmRegs[i].U32[0]:X8} {xmmRegs[i].U32[1]:X8} {xmmRegs[i].U32[2]:X8} {xmmRegs[i].U32[3]:X8} XMM{i+8:D2}: {xmmRegs[i+8].U32[0]:X8} {xmmRegs[i+8].U32[1]:X8} {xmmRegs[i+8].U32[2]:X8} {xmmRegs[i+8].U32[3]:X8}")), + $" RIP: 0x{RIP:X16} RFLAGS: 0x{RFLAGS:X16}", + $" TRAMPOLINE_RSP: 0x{TRAMPOLINE_RSP:X16}" + }); + } } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] -public delegate void MidHookDelegate(ref MidHookContext context); +public delegate void MidHookDelegate( ref MidHookContext context ); public interface IUnmanagedMemory { @@ -46,11 +61,11 @@ public interface IUnmanagedMemory /// The callback receives a context structure that allows reading and modifying CPU registers. /// /// The callback to call when the code reaches that address. - public Guid AddHook(MidHookDelegate callback); + public Guid AddHook( MidHookDelegate callback ); /// /// Unhook a hook by its id. /// /// The id of the hook to unhook. - public void RemoveHook(Guid id); + public void RemoveHook( Guid id ); } \ No newline at end of file diff --git a/managed/src/TestPlugin/TestPlugin.cs b/managed/src/TestPlugin/TestPlugin.cs index 34127d9a0..0ff420e1a 100644 --- a/managed/src/TestPlugin/TestPlugin.cs +++ b/managed/src/TestPlugin/TestPlugin.cs @@ -352,8 +352,6 @@ public void TestCommand1( ICommandContext context ) { var ret = SteamGameServerUGC.DownloadItem(new PublishedFileId_t(3596198331), true); Console.WriteLine(SteamGameServer.GetPublicIP().ToIPAddress()); - - } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] @@ -362,8 +360,23 @@ public void TestCommand1( ICommandContext context ) IUnmanagedFunction? _dispatchspawn; + [Command("h0")] + public void TestCommand0( ICommandContext _ ) + { + var targetAddress = Core.Memory.GetAddressBySignature(Library.Server, "E8 ? ? ? ? 48 8B 46 ? 48 85 DB"); + if (targetAddress.HasValue) + { + var unmanagedMemory = Core.Memory.GetUnmanagedMemoryByAddress(targetAddress.Value); + var hookId = unmanagedMemory.AddHook(( ref MidHookContext context ) => + { + Console.WriteLine($"Mid-hook triggered at 0x{targetAddress.Value:X}"); + Console.WriteLine($"RAX: 0x{context.RAX:X}, RCX: 0x{context.RCX:X}, RDX: 0x{context.RDX:X}"); + }); + } + } + [Command("h1")] - public void TestCommand2( ICommandContext context ) + public void TestCommand2( ICommandContext _ ) { // var token = Core.Scheduler.DelayAndRepeat(500, 1000, () => // { @@ -371,8 +384,8 @@ public void TestCommand2( ICommandContext context ) // }); var addres = Core.GameData.GetSignature("CBaseEntity::DispatchSpawn"); - var func = Core.Memory.GetUnmanagedFunctionByAddress(addres); + var func = Core.Memory.GetUnmanagedFunctionByAddress(addres); var guid = func.AddHook(( next ) => { return ( pEntity, pKV ) => @@ -382,15 +395,20 @@ public void TestCommand2( ICommandContext context ) }; }); - _dispatchspawn.AddHook(( next ) => + var memory = Core.Memory.GetUnmanagedMemoryByAddress(addres); + var guid1 = memory.AddHook(( ref MidHookContext context ) => { - return ( pEntity, pKV ) => - { - Console.WriteLine("TestPlugin DispatchSpawn2 " + order++); - return next()(pEntity, pKV); - }; + Core.Logger.LogInformation("MidHookContext:\n{Context}", context); }); + // _dispatchspawn.AddHook(( next ) => + // { + // return ( pEntity, pKV ) => + // { + // Console.WriteLine("TestPlugin DispatchSpawn2 " + order++); + // return next()(pEntity, pKV); + // }; + // }); } [EventListener] @@ -1002,6 +1020,17 @@ public void MenuResourceUsageCommand( ICommandContext context ) Core.MenusAPI.OpenMenuForPlayer(context.Sender!, mainMenu.Build()); } + [Command("tb2m")] + public void TeleportBotToMeCommand( ICommandContext context ) + { + var player = context.Sender!; + Core.PlayerManager.GetAllPlayers() + .Where(p => p.IsValid && p.IsFakeClient) + .ToList() + .FirstOrDefault() + ?.Teleport(player.PlayerPawn!.AbsOrigin!.Value, player.PlayerPawn!.EyeAngles, Vector.Zero); + } + public override void Unload() { Console.WriteLine("TestPlugin unloaded"); diff --git a/src/memory/hooks/mfunction.cpp b/src/memory/hooks/mfunction.cpp index 4bf036d17..a7a585d5a 100644 --- a/src/memory/hooks/mfunction.cpp +++ b/src/memory/hooks/mfunction.cpp @@ -20,21 +20,31 @@ void MFunctionHook::SetHookFunction(void* addr, void* callback) { - if (!addr || !callback) return; + if (!addr || !callback) + { + return; + } - m_oHook = safetyhook::create_mid(addr, (safetyhook::MidHookFn)callback, safetyhook::MidHook::StartDisabled); + auto callbackFunc = (void (*)(safetyhook::Context&))callback; + m_oHook = safetyhook::create_mid(addr, callbackFunc, safetyhook::MidHook::StartDisabled); } void MFunctionHook::Enable() { - if (m_oHook.enabled()) return; - m_oHook.enable(); + if (m_oHook.enabled()) + { + return; + } + (void)m_oHook.enable(); } + void MFunctionHook::Disable() { - if (!m_oHook.enabled()) return; - - m_oHook.disable(); + if (!m_oHook.enabled()) + { + return; + } + (void)m_oHook.disable(); } bool MFunctionHook::IsEnabled() diff --git a/src/memory/hooks/mfunction.h b/src/memory/hooks/mfunction.h index 6045f6508..3c88b9317 100644 --- a/src/memory/hooks/mfunction.h +++ b/src/memory/hooks/mfunction.h @@ -29,7 +29,6 @@ class MFunctionHook : public IMFunctionHook virtual void Enable() override; virtual void Disable() override; - virtual bool IsEnabled() override; private: diff --git a/src/scripting/memory/hooks.cpp b/src/scripting/memory/hooks.cpp index f75ddc55a..39fd84604 100644 --- a/src/scripting/memory/hooks.cpp +++ b/src/scripting/memory/hooks.cpp @@ -17,6 +17,7 @@ ************************************************************************************************/ #include +#include #include void* Bridge_Hooks_AllocateHook()