feat: IHookProvider service, no more static hook creation

This commit is contained in:
goat 2023-08-06 20:58:55 +02:00
parent b96ef30c20
commit e1da238cb5
No known key found for this signature in database
GPG key ID: 49E2AA8C6A76498B
5 changed files with 238 additions and 34 deletions

View file

@ -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>

View file

@ -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.

View 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();
}
}