using System; using System.Reflection; using Dalamud.Configuration.Internal; using Dalamud.Hooking.Internal; using Dalamud.Memory; using Reloaded.Hooks; namespace Dalamud.Hooking { /// /// Manages a hook which can be used to intercept a call to native function. /// This class is basically a thin wrapper around the LocalHook type to provide helper functions. /// /// Delegate type to represents a function prototype. This must be the same prototype as original function do. public sealed class Hook : IDisposable, IDalamudHook where T : Delegate { private readonly IntPtr address; private readonly Reloaded.Hooks.Definitions.IHook hookImpl; private readonly MinSharp.Hook minHookImpl; private readonly bool isMinHook; /// /// Initializes a new instance of the class. /// Hook is not activated until Enable() method is called. /// /// A memory address to install a hook. /// Callback function. Delegate must have a same original function prototype. public Hook(IntPtr address, T detour) : this(address, detour, false, Assembly.GetCallingAssembly()) { } /// /// Initializes a new instance of the class. /// Hook is not activated until Enable() method is called. /// Please do not use MinHook unless you have thoroughly troubleshot why Reloaded does not work. /// /// A memory address to install a hook. /// Callback function. Delegate must have a same original function prototype. /// Use the MinHook hooking library instead of Reloaded. public Hook(IntPtr address, T detour, bool useMinHook) : this(address, detour, useMinHook, Assembly.GetCallingAssembly()) { } private Hook(IntPtr address, T detour, bool useMinHook, Assembly callingAssembly) { address = HookManager.FollowJmp(address); this.isMinHook = !EnvironmentConfiguration.DalamudForceReloaded && (EnvironmentConfiguration.DalamudForceMinHook || useMinHook); var hasOtherHooks = HookManager.Originals.ContainsKey(address); if (!hasOtherHooks) { MemoryHelper.ReadRaw(address, 0x32, out var original); HookManager.Originals[address] = original; } this.address = address; if (this.isMinHook) { var indexList = hasOtherHooks ? HookManager.MultiHookTracker[address] : HookManager.MultiHookTracker[address] = new(); var index = (ulong)indexList.Count; this.minHookImpl = new MinSharp.Hook(address, detour, index); // Add afterwards, so the hookIdent starts at 0. indexList.Add(this); } else { this.hookImpl = ReloadedHooks.Instance.CreateHook(detour, address.ToInt64()); } HookManager.TrackedHooks.Add(new HookInfo(this, detour, callingAssembly)); } /// /// Gets a memory address of the target function. /// /// Hook is already disposed. public IntPtr Address { get { this.CheckDisposed(); return this.address; } } /// /// Gets a delegate function that can be used to call the actual function as if function is not hooked yet. /// /// Hook is already disposed. public T Original { get { this.CheckDisposed(); if (this.isMinHook) { return this.minHookImpl.Original; } else { return this.hookImpl.OriginalFunction; } } } /// /// Gets a value indicating whether or not the hook is enabled. /// public bool IsEnabled { get { this.CheckDisposed(); if (this.isMinHook) { return this.minHookImpl.Enabled; } else { return this.hookImpl.IsHookEnabled; } } } /// /// Gets a value indicating whether or not the hook has been disposed. /// public bool IsDisposed { get; private set; } /// public string BackendName { get { if (this.isMinHook) return "MinHook"; return "Reloaded"; } } /// /// Creates a hook. Hooking address is inferred by calling to GetProcAddress() function. /// The hook is not activated until Enable() method is called. /// /// A name of the module currently loaded in the memory. (e.g. ws2_32.dll). /// A name of the exported function name (e.g. send). /// Callback function. Delegate must have a same original function prototype. /// The hook with the supplied parameters. public static Hook FromSymbol(string moduleName, string exportName, T detour) => FromSymbol(moduleName, exportName, detour, false); /// /// Creates a hook. Hooking address is inferred by calling to GetProcAddress() function. /// The hook is not activated until Enable() method is called. /// Please do not use MinHook unless you have thoroughly troubleshot why Reloaded does not work. /// /// A name of the module currently loaded in the memory. (e.g. ws2_32.dll). /// A name of the exported function name (e.g. send). /// Callback function. Delegate must have a same original function prototype. /// Use the MinHook hooking library instead of Reloaded. /// The hook with the supplied parameters. public static Hook FromSymbol(string moduleName, string exportName, T detour, bool useMinHook) { var moduleHandle = NativeFunctions.GetModuleHandleW(moduleName); if (moduleHandle == IntPtr.Zero) throw new Exception($"Could not get a handle to module {moduleName}"); var procAddress = NativeFunctions.GetProcAddress(moduleHandle, exportName); if (procAddress == IntPtr.Zero) throw new Exception($"Could not get the address of {moduleName}::{exportName}"); return new Hook(procAddress, detour, useMinHook); } /// /// Remove a hook from the current process. /// public void Dispose() { if (this.IsDisposed) return; if (this.isMinHook) { this.minHookImpl.Dispose(); HookManager.MultiHookTracker[this.address] = null; } else { this.Disable(); } this.IsDisposed = true; } /// /// Starts intercepting a call to the function. /// public void Enable() { this.CheckDisposed(); if (this.isMinHook) { if (!this.minHookImpl.Enabled) { this.minHookImpl.Enable(); } } else { if (!this.hookImpl.IsHookActivated) this.hookImpl.Activate(); if (!this.hookImpl.IsHookEnabled) this.hookImpl.Enable(); } } /// /// Stops intercepting a call to the function. /// public void Disable() { this.CheckDisposed(); if (this.isMinHook) { if (this.minHookImpl.Enabled) { this.minHookImpl.Disable(); } } else { if (!this.hookImpl.IsHookActivated) return; if (this.hookImpl.IsHookEnabled) this.hookImpl.Disable(); } } /// /// Check if this object has been disposed already. /// private void CheckDisposed() { if (this.IsDisposed) { throw new ObjectDisposedException(message: "Hook is already disposed", null); } } } }