mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-15 05:04:15 +01:00
feat: IHookProvider service, no more static hook creation
This commit is contained in:
parent
b96ef30c20
commit
e1da238cb5
5 changed files with 238 additions and 34 deletions
|
|
@ -12,7 +12,7 @@ namespace Dalamud.Hooking;
|
|||
/// This class is basically a thin wrapper around the LocalHook type to provide helper functions.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Delegate type to represents a function prototype. This must be the same prototype as original function do.</typeparam>
|
||||
public abstract class Hook<T> : IDisposable, IDalamudHook where T : Delegate
|
||||
public abstract class Hook<T> : IDalamudHook where T : Delegate
|
||||
{
|
||||
#pragma warning disable SA1310
|
||||
// ReSharper disable once InconsistentNaming
|
||||
|
|
@ -70,6 +70,27 @@ public abstract class Hook<T> : IDisposable, IDalamudHook where T : Delegate
|
|||
|
||||
/// <inheritdoc/>
|
||||
public virtual string BackendName => throw new NotImplementedException();
|
||||
|
||||
/// <summary>
|
||||
/// Remove a hook from the current process.
|
||||
/// </summary>
|
||||
public virtual void Dispose()
|
||||
{
|
||||
if (this.IsDisposed)
|
||||
return;
|
||||
|
||||
this.IsDisposed = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts intercepting a call to the function.
|
||||
/// </summary>
|
||||
public virtual void Enable() => throw new NotImplementedException();
|
||||
|
||||
/// <summary>
|
||||
/// Stops intercepting a call to the function.
|
||||
/// </summary>
|
||||
public virtual void Disable() => throw new NotImplementedException();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a hook by rewriting import table address.
|
||||
|
|
@ -77,7 +98,7 @@ public abstract class Hook<T> : IDisposable, IDalamudHook where T : Delegate
|
|||
/// <param name="address">A memory address to install a hook.</param>
|
||||
/// <param name="detour">Callback function. Delegate must have a same original function prototype.</param>
|
||||
/// <returns>The hook with the supplied parameters.</returns>
|
||||
public static unsafe Hook<T> FromFunctionPointerVariable(IntPtr address, T detour)
|
||||
internal static Hook<T> FromFunctionPointerVariable(IntPtr address, T detour)
|
||||
{
|
||||
return new FunctionPointerVariableHook<T>(address, detour, Assembly.GetCallingAssembly());
|
||||
}
|
||||
|
|
@ -91,7 +112,7 @@ public abstract class Hook<T> : IDisposable, IDalamudHook where T : Delegate
|
|||
/// <param name="hintOrOrdinal">Hint or ordinal. 0 to unspecify.</param>
|
||||
/// <param name="detour">Callback function. Delegate must have a same original function prototype.</param>
|
||||
/// <returns>The hook with the supplied parameters.</returns>
|
||||
public static unsafe Hook<T> FromImport(ProcessModule? module, string moduleName, string functionName, uint hintOrOrdinal, T detour)
|
||||
internal static unsafe Hook<T> 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<T> : IDisposable, IDalamudHook where T : Delegate
|
|||
/// <param name="exportName">A name of the exported function name (e.g. send).</param>
|
||||
/// <param name="detour">Callback function. Delegate must have a same original function prototype.</param>
|
||||
/// <returns>The hook with the supplied parameters.</returns>
|
||||
public static Hook<T> FromSymbol(string moduleName, string exportName, T detour)
|
||||
internal static Hook<T> FromSymbol(string moduleName, string exportName, T detour)
|
||||
=> FromSymbol(moduleName, exportName, detour, false);
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -169,7 +190,7 @@ public abstract class Hook<T> : IDisposable, IDalamudHook where T : Delegate
|
|||
/// <param name="detour">Callback function. Delegate must have a same original function prototype.</param>
|
||||
/// <param name="useMinHook">Use the MinHook hooking library instead of Reloaded.</param>
|
||||
/// <returns>The hook with the supplied parameters.</returns>
|
||||
public static Hook<T> FromSymbol(string moduleName, string exportName, T detour, bool useMinHook)
|
||||
internal static Hook<T> FromSymbol(string moduleName, string exportName, T detour, bool useMinHook)
|
||||
{
|
||||
if (EnvironmentConfiguration.DalamudForceMinHook)
|
||||
useMinHook = true;
|
||||
|
|
@ -198,7 +219,7 @@ public abstract class Hook<T> : IDisposable, IDalamudHook where T : Delegate
|
|||
/// <param name="detour">Callback function. Delegate must have a same original function prototype.</param>
|
||||
/// <param name="useMinHook">Use the MinHook hooking library instead of Reloaded.</param>
|
||||
/// <returns>The hook with the supplied parameters.</returns>
|
||||
public static Hook<T> FromAddress(IntPtr procAddress, T detour, bool useMinHook = false)
|
||||
internal static Hook<T> FromAddress(IntPtr procAddress, T detour, bool useMinHook = false)
|
||||
{
|
||||
if (EnvironmentConfiguration.DalamudForceMinHook)
|
||||
useMinHook = true;
|
||||
|
|
@ -210,27 +231,6 @@ public abstract class Hook<T> : IDisposable, IDalamudHook where T : Delegate
|
|||
return new ReloadedHook<T>(procAddress, detour, Assembly.GetCallingAssembly());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a hook from the current process.
|
||||
/// </summary>
|
||||
public virtual void Dispose()
|
||||
{
|
||||
if (this.IsDisposed)
|
||||
return;
|
||||
|
||||
this.IsDisposed = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts intercepting a call to the function.
|
||||
/// </summary>
|
||||
public virtual void Enable() => throw new NotImplementedException();
|
||||
|
||||
/// <summary>
|
||||
/// Stops intercepting a call to the function.
|
||||
/// </summary>
|
||||
public virtual void Disable() => throw new NotImplementedException();
|
||||
|
||||
/// <summary>
|
||||
/// Check if this object has been disposed already.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ namespace Dalamud.Hooking;
|
|||
/// <summary>
|
||||
/// Interface describing a generic hook.
|
||||
/// </summary>
|
||||
public interface IDalamudHook
|
||||
public interface IDalamudHook : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address to hook.
|
||||
|
|
|
|||
99
Dalamud/Hooking/Internal/HookProviderPluginScoped.cs
Normal file
99
Dalamud/Hooking/Internal/HookProviderPluginScoped.cs
Normal file
|
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Plugin-scoped version of a texture manager.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.ScopedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<IHookProvider>]
|
||||
#pragma warning restore SA1015
|
||||
internal class HookProviderPluginScoped : IHookProvider, IServiceType, IDisposable
|
||||
{
|
||||
private readonly LocalPlugin plugin;
|
||||
private readonly SigScanner scanner;
|
||||
|
||||
private readonly ConcurrentBag<IDalamudHook> trackedHooks = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HookProviderPluginScoped"/> class.
|
||||
/// </summary>
|
||||
/// <param name="plugin">Plugin this instance belongs to.</param>
|
||||
/// <param name="scanner">SigScanner instance for target module.</param>
|
||||
public HookProviderPluginScoped(LocalPlugin plugin, SigScanner scanner)
|
||||
{
|
||||
this.plugin = plugin;
|
||||
this.scanner = scanner;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void InitializeFromAttributes(object self)
|
||||
{
|
||||
foreach (var hook in SignatureHelper.Initialise(self))
|
||||
this.trackedHooks.Add(hook);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Hook<T> FromFunctionPointerVariable<T>(IntPtr address, T detour) where T : Delegate
|
||||
{
|
||||
var hook = Hook<T>.FromFunctionPointerVariable(address, detour);
|
||||
this.trackedHooks.Add(hook);
|
||||
return hook;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Hook<T> FromImport<T>(ProcessModule? module, string moduleName, string functionName, uint hintOrOrdinal, T detour) where T : Delegate
|
||||
{
|
||||
var hook = Hook<T>.FromImport(module, moduleName, functionName, hintOrOrdinal, detour);
|
||||
this.trackedHooks.Add(hook);
|
||||
return hook;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Hook<T> FromSymbol<T>(string moduleName, string exportName, T detour, IHookProvider.HookBackend backend = IHookProvider.HookBackend.Automatic) where T : Delegate
|
||||
{
|
||||
var hook = Hook<T>.FromSymbol(moduleName, exportName, detour, backend == IHookProvider.HookBackend.MinHook);
|
||||
this.trackedHooks.Add(hook);
|
||||
return hook;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Hook<T> FromAddress<T>(IntPtr procAddress, T detour, IHookProvider.HookBackend backend = IHookProvider.HookBackend.Automatic) where T : Delegate
|
||||
{
|
||||
var hook = Hook<T>.FromAddress(procAddress, detour, backend == IHookProvider.HookBackend.MinHook);
|
||||
this.trackedHooks.Add(hook);
|
||||
return hook;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Hook<T> FromSignature<T>(string signature, T detour, IHookProvider.HookBackend backend = IHookProvider.HookBackend.Automatic) where T : Delegate
|
||||
=> this.FromAddress(this.scanner.ScanText(signature), detour);
|
||||
|
||||
/// <inheritdoc/>
|
||||
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();
|
||||
}
|
||||
}
|
||||
97
Dalamud/Plugin/Services/IHookProvider.cs
Normal file
97
Dalamud/Plugin/Services/IHookProvider.cs
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
using System.Diagnostics;
|
||||
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Utility.Signatures;
|
||||
|
||||
namespace Dalamud.Plugin.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Service responsible for the creation of hooks.
|
||||
/// </summary>
|
||||
public interface IHookProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Available hooking backends.
|
||||
/// </summary>
|
||||
public enum HookBackend
|
||||
{
|
||||
/// <summary>
|
||||
/// Choose the best backend automatically.
|
||||
/// </summary>
|
||||
Automatic,
|
||||
|
||||
/// <summary>
|
||||
/// Use Reloaded hooks.
|
||||
/// </summary>
|
||||
Reloaded,
|
||||
|
||||
/// <summary>
|
||||
/// Use MinHook.
|
||||
/// You should never have to use this without talking to us first.
|
||||
/// </summary>
|
||||
MinHook,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize <see cref="Hook{T}"/> members decorated with the <see cref="SignatureAttribute"/>.
|
||||
/// Errors for fallible signatures will be logged.
|
||||
/// </summary>
|
||||
/// <param name="self">The object to initialise.</param>
|
||||
public void InitializeFromAttributes(object self);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a hook by rewriting import table address.
|
||||
/// </summary>
|
||||
/// <param name="address">A memory address to install a hook.</param>
|
||||
/// <param name="detour">Callback function. Delegate must have a same original function prototype.</param>
|
||||
/// <returns>The hook with the supplied parameters.</returns>
|
||||
/// <typeparam name="T">Delegate of detour.</typeparam>
|
||||
public Hook<T> FromFunctionPointerVariable<T>(IntPtr address, T detour) where T : Delegate;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a hook by rewriting import table address.
|
||||
/// </summary>
|
||||
/// <param name="module">Module to check for. Current process' main module if null.</param>
|
||||
/// <param name="moduleName">Name of the DLL, including the extension.</param>
|
||||
/// <param name="functionName">Decorated name of the function.</param>
|
||||
/// <param name="hintOrOrdinal">Hint or ordinal. 0 to unspecify.</param>
|
||||
/// <param name="detour">Callback function. Delegate must have a same original function prototype.</param>
|
||||
/// <returns>The hook with the supplied parameters.</returns>
|
||||
/// <typeparam name="T">Delegate of detour.</typeparam>
|
||||
public Hook<T> FromImport<T>(ProcessModule? module, string moduleName, string functionName, uint hintOrOrdinal, T detour) where T : Delegate;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">A name of the module currently loaded in the memory. (e.g. ws2_32.dll).</param>
|
||||
/// <param name="exportName">A name of the exported function name (e.g. send).</param>
|
||||
/// <param name="detour">Callback function. Delegate must have a same original function prototype.</param>
|
||||
/// <param name="backend">Hooking library to use.</param>
|
||||
/// <returns>The hook with the supplied parameters.</returns>
|
||||
/// <typeparam name="T">Delegate of detour.</typeparam>
|
||||
Hook<T> FromSymbol<T>(string moduleName, string exportName, T detour, HookBackend backend = HookBackend.Automatic) where T : Delegate;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="procAddress">A memory address to install a hook.</param>
|
||||
/// <param name="detour">Callback function. Delegate must have a same original function prototype.</param>
|
||||
/// <param name="backend">Hooking library to use.</param>
|
||||
/// <returns>The hook with the supplied parameters.</returns>
|
||||
/// <typeparam name="T">Delegate of detour.</typeparam>
|
||||
Hook<T> FromAddress<T>(IntPtr procAddress, T detour, HookBackend backend = HookBackend.Automatic) where T : Delegate;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a hook from a signature into the Dalamud target module.
|
||||
/// </summary>
|
||||
/// <param name="signature">Signature of function to hook.</param>
|
||||
/// <param name="detour">Callback function. Delegate must have a same original function prototype.</param>
|
||||
/// <param name="backend">Hooking library to use.</param>
|
||||
/// <returns>The hook with the supplied parameters.</returns>
|
||||
/// <typeparam name="T">Delegate of detour.</typeparam>
|
||||
Hook<T> FromSignature<T>(string signature, T detour, HookBackend backend = HookBackend.Automatic) where T : Delegate;
|
||||
}
|
||||
|
|
@ -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;
|
|||
/// <summary>
|
||||
/// A utility class to help reduce signature boilerplate code.
|
||||
/// </summary>
|
||||
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
|
|||
/// </summary>
|
||||
/// <param name="self">The object to initialise.</param>
|
||||
/// <param name="log">If warnings should be logged using <see cref="PluginLog"/>.</param>
|
||||
public static void Initialise(object self, bool log = true)
|
||||
/// <returns>Collection of created IDalamudHooks.</returns>
|
||||
internal static IEnumerable<IDalamudHook> Initialise(object self, bool log = true)
|
||||
{
|
||||
var scanner = Service<SigScanner>.Get();
|
||||
var selfType = self.GetType();
|
||||
|
|
@ -33,6 +36,8 @@ public static class SignatureHelper
|
|||
.Select(field => (field, field.GetCustomAttribute<SignatureAttribute>()))
|
||||
.Where(field => field.Item2 != null);
|
||||
|
||||
var createdHooks = new List<IDalamudHook>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue