diff --git a/Dalamud/Hooking/Hook.cs b/Dalamud/Hooking/Hook.cs
index 2e785191c..da65fedc7 100644
--- a/Dalamud/Hooking/Hook.cs
+++ b/Dalamud/Hooking/Hook.cs
@@ -12,7 +12,7 @@ namespace Dalamud.Hooking;
/// 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 abstract class Hook : IDisposable, IDalamudHook where T : Delegate
+public abstract class Hook : IDalamudHook where T : Delegate
{
#pragma warning disable SA1310
// ReSharper disable once InconsistentNaming
@@ -70,6 +70,27 @@ public abstract class Hook : IDisposable, IDalamudHook where T : Delegate
///
public virtual string BackendName => throw new NotImplementedException();
+
+ ///
+ /// Remove a hook from the current process.
+ ///
+ public virtual void Dispose()
+ {
+ if (this.IsDisposed)
+ return;
+
+ this.IsDisposed = true;
+ }
+
+ ///
+ /// Starts intercepting a call to the function.
+ ///
+ public virtual void Enable() => throw new NotImplementedException();
+
+ ///
+ /// Stops intercepting a call to the function.
+ ///
+ public virtual void Disable() => throw new NotImplementedException();
///
/// Creates a hook by rewriting import table address.
@@ -77,7 +98,7 @@ public abstract class Hook : IDisposable, IDalamudHook where T : Delegate
/// A memory address to install a hook.
/// Callback function. Delegate must have a same original function prototype.
/// The hook with the supplied parameters.
- public static unsafe Hook FromFunctionPointerVariable(IntPtr address, T detour)
+ internal static Hook FromFunctionPointerVariable(IntPtr address, T detour)
{
return new FunctionPointerVariableHook(address, detour, Assembly.GetCallingAssembly());
}
@@ -91,7 +112,7 @@ public abstract class Hook : IDisposable, IDalamudHook where T : Delegate
/// Hint or ordinal. 0 to unspecify.
/// Callback function. Delegate must have a same original function prototype.
/// The hook with the supplied parameters.
- public static unsafe Hook FromImport(ProcessModule? module, string moduleName, string functionName, uint hintOrOrdinal, T detour)
+ internal static unsafe Hook FromImport(ProcessModule? module, string moduleName, string functionName, uint hintOrOrdinal, T detour)
{
module ??= Process.GetCurrentProcess().MainModule;
if (module == null)
@@ -156,7 +177,7 @@ public abstract class Hook : IDisposable, IDalamudHook where T : Delegate
/// 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)
+ internal static Hook FromSymbol(string moduleName, string exportName, T detour)
=> FromSymbol(moduleName, exportName, detour, false);
///
@@ -169,7 +190,7 @@ public abstract class Hook : IDisposable, IDalamudHook where T : Delegate
/// 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)
+ internal static Hook FromSymbol(string moduleName, string exportName, T detour, bool useMinHook)
{
if (EnvironmentConfiguration.DalamudForceMinHook)
useMinHook = true;
@@ -198,7 +219,7 @@ public abstract class Hook : IDisposable, IDalamudHook where T : Delegate
/// 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 FromAddress(IntPtr procAddress, T detour, bool useMinHook = false)
+ internal static Hook FromAddress(IntPtr procAddress, T detour, bool useMinHook = false)
{
if (EnvironmentConfiguration.DalamudForceMinHook)
useMinHook = true;
@@ -210,27 +231,6 @@ public abstract class Hook : IDisposable, IDalamudHook where T : Delegate
return new ReloadedHook(procAddress, detour, Assembly.GetCallingAssembly());
}
- ///
- /// Remove a hook from the current process.
- ///
- public virtual void Dispose()
- {
- if (this.IsDisposed)
- return;
-
- this.IsDisposed = true;
- }
-
- ///
- /// Starts intercepting a call to the function.
- ///
- public virtual void Enable() => throw new NotImplementedException();
-
- ///
- /// Stops intercepting a call to the function.
- ///
- public virtual void Disable() => throw new NotImplementedException();
-
///
/// Check if this object has been disposed already.
///
diff --git a/Dalamud/Hooking/IDalamudHook.cs b/Dalamud/Hooking/IDalamudHook.cs
index 1104597a1..bd7084d86 100644
--- a/Dalamud/Hooking/IDalamudHook.cs
+++ b/Dalamud/Hooking/IDalamudHook.cs
@@ -5,7 +5,7 @@ namespace Dalamud.Hooking;
///
/// Interface describing a generic hook.
///
-public interface IDalamudHook
+public interface IDalamudHook : IDisposable
{
///
/// Gets the address to hook.
diff --git a/Dalamud/Hooking/Internal/HookProviderPluginScoped.cs b/Dalamud/Hooking/Internal/HookProviderPluginScoped.cs
new file mode 100644
index 000000000..fa4497799
--- /dev/null
+++ b/Dalamud/Hooking/Internal/HookProviderPluginScoped.cs
@@ -0,0 +1,99 @@
+using System.Collections.Concurrent;
+using System.Diagnostics;
+using System.Linq;
+
+using Dalamud.Game;
+using Dalamud.IoC;
+using Dalamud.IoC.Internal;
+using Dalamud.Plugin.Internal.Types;
+using Dalamud.Plugin.Services;
+using Dalamud.Utility.Signatures;
+using Serilog;
+
+namespace Dalamud.Hooking.Internal;
+
+///
+/// Plugin-scoped version of a texture manager.
+///
+[PluginInterface]
+[InterfaceVersion("1.0")]
+[ServiceManager.ScopedService]
+#pragma warning disable SA1015
+[ResolveVia]
+#pragma warning restore SA1015
+internal class HookProviderPluginScoped : IHookProvider, IServiceType, IDisposable
+{
+ private readonly LocalPlugin plugin;
+ private readonly SigScanner scanner;
+
+ private readonly ConcurrentBag trackedHooks = new();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Plugin this instance belongs to.
+ /// SigScanner instance for target module.
+ public HookProviderPluginScoped(LocalPlugin plugin, SigScanner scanner)
+ {
+ this.plugin = plugin;
+ this.scanner = scanner;
+ }
+
+ ///
+ public void InitializeFromAttributes(object self)
+ {
+ foreach (var hook in SignatureHelper.Initialise(self))
+ this.trackedHooks.Add(hook);
+ }
+
+ ///
+ public Hook FromFunctionPointerVariable(IntPtr address, T detour) where T : Delegate
+ {
+ var hook = Hook.FromFunctionPointerVariable(address, detour);
+ this.trackedHooks.Add(hook);
+ return hook;
+ }
+
+ ///
+ public Hook FromImport(ProcessModule? module, string moduleName, string functionName, uint hintOrOrdinal, T detour) where T : Delegate
+ {
+ var hook = Hook.FromImport(module, moduleName, functionName, hintOrOrdinal, detour);
+ this.trackedHooks.Add(hook);
+ return hook;
+ }
+
+ ///
+ public Hook FromSymbol(string moduleName, string exportName, T detour, IHookProvider.HookBackend backend = IHookProvider.HookBackend.Automatic) where T : Delegate
+ {
+ var hook = Hook.FromSymbol(moduleName, exportName, detour, backend == IHookProvider.HookBackend.MinHook);
+ this.trackedHooks.Add(hook);
+ return hook;
+ }
+
+ ///
+ public Hook FromAddress(IntPtr procAddress, T detour, IHookProvider.HookBackend backend = IHookProvider.HookBackend.Automatic) where T : Delegate
+ {
+ var hook = Hook.FromAddress(procAddress, detour, backend == IHookProvider.HookBackend.MinHook);
+ this.trackedHooks.Add(hook);
+ return hook;
+ }
+
+ ///
+ public Hook FromSignature(string signature, T detour, IHookProvider.HookBackend backend = IHookProvider.HookBackend.Automatic) where T : Delegate
+ => this.FromAddress(this.scanner.ScanText(signature), detour);
+
+ ///
+ public void Dispose()
+ {
+ var notDisposed = this.trackedHooks.Where(x => !x.IsDisposed).ToArray();
+ if (notDisposed.Length != 0)
+ Log.Warning("{PluginName} is leaking {Num} hooks! Make sure that all of them are disposed properly.", this.plugin.InternalName, notDisposed.Length);
+
+ foreach (var hook in notDisposed)
+ {
+ hook.Dispose();
+ }
+
+ this.trackedHooks.Clear();
+ }
+}
diff --git a/Dalamud/Plugin/Services/IHookProvider.cs b/Dalamud/Plugin/Services/IHookProvider.cs
new file mode 100644
index 000000000..dc7d29913
--- /dev/null
+++ b/Dalamud/Plugin/Services/IHookProvider.cs
@@ -0,0 +1,97 @@
+using System.Diagnostics;
+
+using Dalamud.Hooking;
+using Dalamud.Utility.Signatures;
+
+namespace Dalamud.Plugin.Services;
+
+///
+/// Service responsible for the creation of hooks.
+///
+public interface IHookProvider
+{
+ ///
+ /// Available hooking backends.
+ ///
+ public enum HookBackend
+ {
+ ///
+ /// Choose the best backend automatically.
+ ///
+ Automatic,
+
+ ///
+ /// Use Reloaded hooks.
+ ///
+ Reloaded,
+
+ ///
+ /// Use MinHook.
+ /// You should never have to use this without talking to us first.
+ ///
+ MinHook,
+ }
+
+ ///
+ /// Initialize members decorated with the .
+ /// Errors for fallible signatures will be logged.
+ ///
+ /// The object to initialise.
+ public void InitializeFromAttributes(object self);
+
+ ///
+ /// Creates a hook by rewriting import table address.
+ ///
+ /// A memory address to install a hook.
+ /// Callback function. Delegate must have a same original function prototype.
+ /// The hook with the supplied parameters.
+ /// Delegate of detour.
+ public Hook FromFunctionPointerVariable(IntPtr address, T detour) where T : Delegate;
+
+ ///
+ /// Creates a hook by rewriting import table address.
+ ///
+ /// Module to check for. Current process' main module if null.
+ /// Name of the DLL, including the extension.
+ /// Decorated name of the function.
+ /// Hint or ordinal. 0 to unspecify.
+ /// Callback function. Delegate must have a same original function prototype.
+ /// The hook with the supplied parameters.
+ /// Delegate of detour.
+ public Hook FromImport(ProcessModule? module, string moduleName, string functionName, uint hintOrOrdinal, T detour) where T : Delegate;
+
+ ///
+ /// 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.
+ /// Hooking library to use.
+ /// The hook with the supplied parameters.
+ /// Delegate of detour.
+ Hook FromSymbol(string moduleName, string exportName, T detour, HookBackend backend = HookBackend.Automatic) where T : Delegate;
+
+ ///
+ /// 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 memory address to install a hook.
+ /// Callback function. Delegate must have a same original function prototype.
+ /// Hooking library to use.
+ /// The hook with the supplied parameters.
+ /// Delegate of detour.
+ Hook FromAddress(IntPtr procAddress, T detour, HookBackend backend = HookBackend.Automatic) where T : Delegate;
+
+ ///
+ /// Creates a hook from a signature into the Dalamud target module.
+ ///
+ /// Signature of function to hook.
+ /// Callback function. Delegate must have a same original function prototype.
+ /// Hooking library to use.
+ /// The hook with the supplied parameters.
+ /// Delegate of detour.
+ Hook FromSignature(string signature, T detour, HookBackend backend = HookBackend.Automatic) where T : Delegate;
+}
diff --git a/Dalamud/Utility/Signatures/SignatureHelper.cs b/Dalamud/Utility/Signatures/SignatureHelper.cs
index bd99b8515..e133e5453 100755
--- a/Dalamud/Utility/Signatures/SignatureHelper.cs
+++ b/Dalamud/Utility/Signatures/SignatureHelper.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
@@ -6,6 +7,7 @@ using System.Runtime.InteropServices;
using Dalamud.Game;
using Dalamud.Hooking;
using Dalamud.Logging;
+using Dalamud.Plugin.Services;
using Dalamud.Utility.Signatures.Wrappers;
using Serilog;
@@ -14,7 +16,7 @@ namespace Dalamud.Utility.Signatures;
///
/// A utility class to help reduce signature boilerplate code.
///
-public static class SignatureHelper
+internal static class SignatureHelper
{
private const BindingFlags Flags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
@@ -24,7 +26,8 @@ public static class SignatureHelper
///
/// The object to initialise.
/// If warnings should be logged using .
- public static void Initialise(object self, bool log = true)
+ /// Collection of created IDalamudHooks.
+ internal static IEnumerable Initialise(object self, bool log = true)
{
var scanner = Service.Get();
var selfType = self.GetType();
@@ -33,6 +36,8 @@ public static class SignatureHelper
.Select(field => (field, field.GetCustomAttribute()))
.Where(field => field.Item2 != null);
+ var createdHooks = new List();
+
foreach (var (info, sig) in fields)
{
var wasWrapped = false;
@@ -149,15 +154,16 @@ public static class SignatureHelper
detour = del;
}
- var ctor = actualType.GetConstructor(new[] { typeof(IntPtr), hookDelegateType });
- if (ctor == null)
+ var creator = actualType.GetMethod("FromAddress", BindingFlags.Static | BindingFlags.NonPublic);
+ if (creator == null)
{
- Log.Error("Error in SignatureHelper: could not find Hook constructor");
+ Log.Error("Error in SignatureHelper: could not find Hook creator");
continue;
}
- var hook = ctor.Invoke(new object?[] { ptr, detour });
+ var hook = creator.Invoke(null, new object?[] { ptr, detour, IHookProvider.HookBackend.Automatic }) as IDalamudHook;
info.SetValue(self, hook);
+ createdHooks.Add(hook);
break;
}
@@ -182,5 +188,7 @@ public static class SignatureHelper
}
}
}
+
+ return createdHooks;
}
}