From 8eeab700a69e95252cb65c916f408c257c9ea5a8 Mon Sep 17 00:00:00 2001 From: Ambr0se Date: Sat, 22 Nov 2025 19:30:26 +0800 Subject: [PATCH 1/8] chore: Add test code --- managed/src/TestPlugin/TestPlugin.cs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/managed/src/TestPlugin/TestPlugin.cs b/managed/src/TestPlugin/TestPlugin.cs index 34127d9a0..335c72954 100644 --- a/managed/src/TestPlugin/TestPlugin.cs +++ b/managed/src/TestPlugin/TestPlugin.cs @@ -97,6 +97,21 @@ public HookResult OnPlayerSpawn( EventPlayerSpawn @event ) public override void Load( bool hotReload ) { + // Setup mid-hook for signature pattern + var targetAddress = Core.Memory.GetAddressBySignature(Library.Server, "48 85 C9 0F 84 ? ? ? ? 48 63 B5"); + 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}"); + // You can modify registers here if needed + // context.RAX = newValue; + }); + Console.WriteLine($"Mid-hook installed successfully at 0x{targetAddress.Value:X} with ID: {hookId}"); + } + // Core.Command.HookClientCommand((playerId, commandLine) => // { // Console.WriteLine("TestPlugin HookClientCommand " + playerId + " " + commandLine); @@ -1002,6 +1017,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"); From 1b1a148e5ea8ac93fab3e605c7e39ed85cc6fa87 Mon Sep 17 00:00:00 2001 From: Ambr0se Date: Sun, 23 Nov 2025 16:39:08 +0800 Subject: [PATCH 2/8] fix: Midhook callback crash --- .../Modules/Hooks/HookManager.cs | 351 +++++++++--------- .../Modules/Memory/UnmanagedMemory.cs | 45 ++- src/memory/hooks/mfunction.cpp | 43 ++- src/memory/hooks/mfunction.h | 3 + src/scripting/memory/hooks.cpp | 4 +- 5 files changed, 239 insertions(+), 207 deletions(-) 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/src/memory/hooks/mfunction.cpp b/src/memory/hooks/mfunction.cpp index 4bf036d17..f9721178a 100644 --- a/src/memory/hooks/mfunction.cpp +++ b/src/memory/hooks/mfunction.cpp @@ -17,22 +17,55 @@ ************************************************************************************************/ #include "mfunction.h" +#include +#include + +MFunctionHook* MFunctionHook::s_currentInstance = nullptr; + +static void CppMidHookWrapper(safetyhook::Context& ctx) +{ + if (!MFunctionHook::s_currentInstance || !MFunctionHook::s_currentInstance->m_userCallback) + { + return; + } + + __try + { + auto callback = (void (*)(void*))MFunctionHook::s_currentInstance->m_userCallback; + callback(&ctx); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + printf("[MFunctionHook::CppWrapper] EXCEPTION during callback! Code: 0x%X\n", GetExceptionCode()); + } +} 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); + m_userCallback = callback; + s_currentInstance = this; + m_oHook = safetyhook::create_mid(addr, CppMidHookWrapper, safetyhook::MidHook::StartDisabled); } void MFunctionHook::Enable() { - if (m_oHook.enabled()) return; - m_oHook.enable(); + if (m_oHook.enabled()) + { + return; + } + + auto result = m_oHook.enable(); } + void MFunctionHook::Disable() { - if (!m_oHook.enabled()) return; + if (!m_oHook.enabled()) + return; m_oHook.disable(); } diff --git a/src/memory/hooks/mfunction.h b/src/memory/hooks/mfunction.h index 6045f6508..b1cd2b433 100644 --- a/src/memory/hooks/mfunction.h +++ b/src/memory/hooks/mfunction.h @@ -32,6 +32,9 @@ class MFunctionHook : public IMFunctionHook virtual bool IsEnabled() override; + static MFunctionHook* s_currentInstance; + void* m_userCallback = nullptr; + private: SafetyHookMid m_oHook; }; diff --git a/src/scripting/memory/hooks.cpp b/src/scripting/memory/hooks.cpp index f75ddc55a..dbd055d14 100644 --- a/src/scripting/memory/hooks.cpp +++ b/src/scripting/memory/hooks.cpp @@ -17,6 +17,7 @@ ************************************************************************************************/ #include +#include #include void* Bridge_Hooks_AllocateHook() @@ -34,7 +35,8 @@ void* Bridge_Hooks_AllocateVHook() void* Bridge_Hooks_AllocateMHook() { auto hooksmanager = g_ifaceService.FetchInterface(HOOKSMANAGER_INTERFACE_VERSION); - return hooksmanager->CreateMFunctionHook(); + auto result = hooksmanager->CreateMFunctionHook(); + return result; } void Bridge_Hooks_DeallocateHook(void* hook) From cdc2138c50e62eab74f6f233ef1b764a2b2d0dd0 Mon Sep 17 00:00:00 2001 From: Ambr0se Date: Sun, 23 Nov 2025 17:09:41 +0800 Subject: [PATCH 3/8] chore: Add test code --- .../Modules/Memory/IUnmanagedMemory.cs | 21 ++++++++++++++--- managed/src/TestPlugin/TestPlugin.cs | 23 +++++++++++-------- 2 files changed, 31 insertions(+), 13 deletions(-) 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 335c72954..4570a0e1c 100644 --- a/managed/src/TestPlugin/TestPlugin.cs +++ b/managed/src/TestPlugin/TestPlugin.cs @@ -367,8 +367,6 @@ public void TestCommand1( ICommandContext context ) { var ret = SteamGameServerUGC.DownloadItem(new PublishedFileId_t(3596198331), true); Console.WriteLine(SteamGameServer.GetPublicIP().ToIPAddress()); - - } [UnmanagedFunctionPointer(CallingConvention.Cdecl)] @@ -378,7 +376,7 @@ public void TestCommand1( ICommandContext context ) IUnmanagedFunction? _dispatchspawn; [Command("h1")] - public void TestCommand2( ICommandContext context ) + public void TestCommand2( ICommandContext _ ) { // var token = Core.Scheduler.DelayAndRepeat(500, 1000, () => // { @@ -386,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 ) => @@ -397,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] From 2aa8891e4550d228d01b90b54d1a8cf9b5996e88 Mon Sep 17 00:00:00 2001 From: Ambr0se Date: Sun, 23 Nov 2025 17:56:53 +0800 Subject: [PATCH 4/8] chore: Clean up code --- src/memory/hooks/mfunction.cpp | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/memory/hooks/mfunction.cpp b/src/memory/hooks/mfunction.cpp index f9721178a..f78568249 100644 --- a/src/memory/hooks/mfunction.cpp +++ b/src/memory/hooks/mfunction.cpp @@ -17,27 +17,18 @@ ************************************************************************************************/ #include "mfunction.h" -#include -#include MFunctionHook* MFunctionHook::s_currentInstance = nullptr; -static void CppMidHookWrapper(safetyhook::Context& ctx) +static void MidHookWrapper(safetyhook::Context& ctx) { if (!MFunctionHook::s_currentInstance || !MFunctionHook::s_currentInstance->m_userCallback) { return; } - __try - { - auto callback = (void (*)(void*))MFunctionHook::s_currentInstance->m_userCallback; - callback(&ctx); - } - __except (EXCEPTION_EXECUTE_HANDLER) - { - printf("[MFunctionHook::CppWrapper] EXCEPTION during callback! Code: 0x%X\n", GetExceptionCode()); - } + auto callback = (void (*)(void*))MFunctionHook::s_currentInstance->m_userCallback; + callback(&ctx); } void MFunctionHook::SetHookFunction(void* addr, void* callback) @@ -49,7 +40,7 @@ void MFunctionHook::SetHookFunction(void* addr, void* callback) m_userCallback = callback; s_currentInstance = this; - m_oHook = safetyhook::create_mid(addr, CppMidHookWrapper, safetyhook::MidHook::StartDisabled); + m_oHook = safetyhook::create_mid(addr, MidHookWrapper, safetyhook::MidHook::StartDisabled); } void MFunctionHook::Enable() From c4905de4fc0591c89f7a106449d4d7f8d2dd9688 Mon Sep 17 00:00:00 2001 From: Ambr0se Date: Sun, 23 Nov 2025 21:20:34 +0800 Subject: [PATCH 5/8] chore: Remove debug code and finalize multi-hook support --- managed/src/TestPlugin/TestPlugin.cs | 6 +---- src/memory/hooks/mfunction.cpp | 39 ++++++++++++++++++++-------- src/memory/hooks/mfunction.h | 18 +++++++++++-- 3 files changed, 45 insertions(+), 18 deletions(-) diff --git a/managed/src/TestPlugin/TestPlugin.cs b/managed/src/TestPlugin/TestPlugin.cs index 4570a0e1c..2bff4d207 100644 --- a/managed/src/TestPlugin/TestPlugin.cs +++ b/managed/src/TestPlugin/TestPlugin.cs @@ -97,8 +97,7 @@ public HookResult OnPlayerSpawn( EventPlayerSpawn @event ) public override void Load( bool hotReload ) { - // Setup mid-hook for signature pattern - var targetAddress = Core.Memory.GetAddressBySignature(Library.Server, "48 85 C9 0F 84 ? ? ? ? 48 63 B5"); + var targetAddress = Core.Memory.GetAddressBySignature(Library.Server, "E8 ? ? ? ? 48 8B 46 ? 48 85 DB"); if (targetAddress.HasValue) { var unmanagedMemory = Core.Memory.GetUnmanagedMemoryByAddress(targetAddress.Value); @@ -106,10 +105,7 @@ public override void Load( bool hotReload ) { 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}"); - // You can modify registers here if needed - // context.RAX = newValue; }); - Console.WriteLine($"Mid-hook installed successfully at 0x{targetAddress.Value:X} with ID: {hookId}"); } // Core.Command.HookClientCommand((playerId, commandLine) => diff --git a/src/memory/hooks/mfunction.cpp b/src/memory/hooks/mfunction.cpp index f78568249..536249f1d 100644 --- a/src/memory/hooks/mfunction.cpp +++ b/src/memory/hooks/mfunction.cpp @@ -18,17 +18,27 @@ #include "mfunction.h" -MFunctionHook* MFunctionHook::s_currentInstance = nullptr; +std::unordered_map MFunctionHook::s_instances; +std::shared_mutex MFunctionHook::s_instancesMutex; static void MidHookWrapper(safetyhook::Context& ctx) { - if (!MFunctionHook::s_currentInstance || !MFunctionHook::s_currentInstance->m_userCallback) + std::shared_lock lock(MFunctionHook::s_instancesMutex); + + for (const auto& pair : MFunctionHook::s_instances) { - return; - } + MFunctionHook* instance = pair.second; + if (instance && instance->IsEnabled() && instance->m_userCallback) + { + // Release lock before calling into managed code to avoid deadlocks + lock.unlock(); + + auto callback = (void (*)(void*))instance->m_userCallback; + callback(&ctx); - auto callback = (void (*)(void*))MFunctionHook::s_currentInstance->m_userCallback; - callback(&ctx); + return; + } + } } void MFunctionHook::SetHookFunction(void* addr, void* callback) @@ -38,8 +48,15 @@ void MFunctionHook::SetHookFunction(void* addr, void* callback) return; } + m_hookAddress = addr; m_userCallback = callback; - s_currentInstance = this; + + // Register this instance in global map + { + std::unique_lock lock(s_instancesMutex); + s_instances[addr] = this; + } + m_oHook = safetyhook::create_mid(addr, MidHookWrapper, safetyhook::MidHook::StartDisabled); } @@ -49,16 +66,16 @@ void MFunctionHook::Enable() { return; } - - auto result = m_oHook.enable(); + (void)m_oHook.enable(); } void MFunctionHook::Disable() { if (!m_oHook.enabled()) + { return; - - m_oHook.disable(); + } + (void)m_oHook.disable(); } bool MFunctionHook::IsEnabled() diff --git a/src/memory/hooks/mfunction.h b/src/memory/hooks/mfunction.h index b1cd2b433..6f6fbb457 100644 --- a/src/memory/hooks/mfunction.h +++ b/src/memory/hooks/mfunction.h @@ -21,18 +21,32 @@ #include #include +#include +#include class MFunctionHook : public IMFunctionHook { public: + MFunctionHook() = default; + ~MFunctionHook() + { + if (m_hookAddress) + { + std::unique_lock lock(s_instancesMutex); + s_instances.erase(m_hookAddress); + } + } + virtual void SetHookFunction(void* addr, void* callback) override; virtual void Enable() override; virtual void Disable() override; - virtual bool IsEnabled() override; - static MFunctionHook* s_currentInstance; + static std::unordered_map s_instances; + static std::shared_mutex s_instancesMutex; + + void* m_hookAddress = nullptr; void* m_userCallback = nullptr; private: From c847b1bc235c266a40d1c6112f27dc4165f20a71 Mon Sep 17 00:00:00 2001 From: Ambr0se Date: Sun, 23 Nov 2025 21:49:25 +0800 Subject: [PATCH 6/8] refactor: Simplify midhook implementation by removing C++ wrapper layer --- src/memory/hooks/mfunction.cpp | 35 ++-------------------------------- src/memory/hooks/mfunction.h | 18 ----------------- 2 files changed, 2 insertions(+), 51 deletions(-) diff --git a/src/memory/hooks/mfunction.cpp b/src/memory/hooks/mfunction.cpp index 536249f1d..a7a585d5a 100644 --- a/src/memory/hooks/mfunction.cpp +++ b/src/memory/hooks/mfunction.cpp @@ -18,29 +18,6 @@ #include "mfunction.h" -std::unordered_map MFunctionHook::s_instances; -std::shared_mutex MFunctionHook::s_instancesMutex; - -static void MidHookWrapper(safetyhook::Context& ctx) -{ - std::shared_lock lock(MFunctionHook::s_instancesMutex); - - for (const auto& pair : MFunctionHook::s_instances) - { - MFunctionHook* instance = pair.second; - if (instance && instance->IsEnabled() && instance->m_userCallback) - { - // Release lock before calling into managed code to avoid deadlocks - lock.unlock(); - - auto callback = (void (*)(void*))instance->m_userCallback; - callback(&ctx); - - return; - } - } -} - void MFunctionHook::SetHookFunction(void* addr, void* callback) { if (!addr || !callback) @@ -48,16 +25,8 @@ void MFunctionHook::SetHookFunction(void* addr, void* callback) return; } - m_hookAddress = addr; - m_userCallback = callback; - - // Register this instance in global map - { - std::unique_lock lock(s_instancesMutex); - s_instances[addr] = this; - } - - m_oHook = safetyhook::create_mid(addr, MidHookWrapper, safetyhook::MidHook::StartDisabled); + auto callbackFunc = (void (*)(safetyhook::Context&))callback; + m_oHook = safetyhook::create_mid(addr, callbackFunc, safetyhook::MidHook::StartDisabled); } void MFunctionHook::Enable() diff --git a/src/memory/hooks/mfunction.h b/src/memory/hooks/mfunction.h index 6f6fbb457..3c88b9317 100644 --- a/src/memory/hooks/mfunction.h +++ b/src/memory/hooks/mfunction.h @@ -21,34 +21,16 @@ #include #include -#include -#include class MFunctionHook : public IMFunctionHook { public: - MFunctionHook() = default; - ~MFunctionHook() - { - if (m_hookAddress) - { - std::unique_lock lock(s_instancesMutex); - s_instances.erase(m_hookAddress); - } - } - virtual void SetHookFunction(void* addr, void* callback) override; virtual void Enable() override; virtual void Disable() override; virtual bool IsEnabled() override; - static std::unordered_map s_instances; - static std::shared_mutex s_instancesMutex; - - void* m_hookAddress = nullptr; - void* m_userCallback = nullptr; - private: SafetyHookMid m_oHook; }; From 90eb36ccec9f1eb8b5d23fbda5c56429f38049bc Mon Sep 17 00:00:00 2001 From: Ambr0se Date: Sun, 23 Nov 2025 21:55:15 +0800 Subject: [PATCH 7/8] [no ci] chore: Clean up code --- src/scripting/memory/hooks.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/scripting/memory/hooks.cpp b/src/scripting/memory/hooks.cpp index dbd055d14..39fd84604 100644 --- a/src/scripting/memory/hooks.cpp +++ b/src/scripting/memory/hooks.cpp @@ -35,8 +35,7 @@ void* Bridge_Hooks_AllocateVHook() void* Bridge_Hooks_AllocateMHook() { auto hooksmanager = g_ifaceService.FetchInterface(HOOKSMANAGER_INTERFACE_VERSION); - auto result = hooksmanager->CreateMFunctionHook(); - return result; + return hooksmanager->CreateMFunctionHook(); } void Bridge_Hooks_DeallocateHook(void* hook) From be1ad23d5da40451d2d5f224dfc7e97d400e175c Mon Sep 17 00:00:00 2001 From: Ambr0se Date: Sun, 23 Nov 2025 22:10:19 +0800 Subject: [PATCH 8/8] chore: Move test code to appropriate location --- managed/src/TestPlugin/TestPlugin.cs | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/managed/src/TestPlugin/TestPlugin.cs b/managed/src/TestPlugin/TestPlugin.cs index 2bff4d207..0ff420e1a 100644 --- a/managed/src/TestPlugin/TestPlugin.cs +++ b/managed/src/TestPlugin/TestPlugin.cs @@ -97,17 +97,6 @@ public HookResult OnPlayerSpawn( EventPlayerSpawn @event ) public override void Load( bool hotReload ) { - 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}"); - }); - } - // Core.Command.HookClientCommand((playerId, commandLine) => // { // Console.WriteLine("TestPlugin HookClientCommand " + playerId + " " + commandLine); @@ -371,6 +360,21 @@ 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 _ ) {