mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
Add hook stress test
This commit is contained in:
parent
81ced564d6
commit
672793b6c0
2 changed files with 189 additions and 16 deletions
|
|
@ -15,10 +15,10 @@ namespace Dalamud.Hooking.Internal;
|
||||||
/// Only the specific callsite hooked is modified, if the game calls the virtual function from other locations this hook will not be triggered.
|
/// Only the specific callsite hooked is modified, if the game calls the virtual function from other locations this hook will not be triggered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">Delegate signature for this hook.</typeparam>
|
/// <typeparam name="T">Delegate signature for this hook.</typeparam>
|
||||||
internal class CallHook<T> : IDisposable where T : Delegate
|
internal class CallHook<T> : IDalamudHook where T : Delegate
|
||||||
{
|
{
|
||||||
private readonly Reloaded.Hooks.AsmHook asmHook;
|
private readonly Reloaded.Hooks.AsmHook asmHook;
|
||||||
|
|
||||||
private T? detour;
|
private T? detour;
|
||||||
private bool activated;
|
private bool activated;
|
||||||
|
|
||||||
|
|
@ -29,7 +29,10 @@ internal class CallHook<T> : IDisposable where T : Delegate
|
||||||
/// <param name="detour">Delegate to invoke.</param>
|
/// <param name="detour">Delegate to invoke.</param>
|
||||||
internal CallHook(nint address, T detour)
|
internal CallHook(nint address, T detour)
|
||||||
{
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(detour);
|
||||||
|
|
||||||
this.detour = detour;
|
this.detour = detour;
|
||||||
|
this.Address = address;
|
||||||
|
|
||||||
var detourPtr = Marshal.GetFunctionPointerForDelegate(this.detour);
|
var detourPtr = Marshal.GetFunctionPointerForDelegate(this.detour);
|
||||||
var code = new[]
|
var code = new[]
|
||||||
|
|
@ -38,14 +41,14 @@ internal class CallHook<T> : IDisposable where T : Delegate
|
||||||
$"mov rax, 0x{detourPtr:X8}",
|
$"mov rax, 0x{detourPtr:X8}",
|
||||||
"call rax",
|
"call rax",
|
||||||
};
|
};
|
||||||
|
|
||||||
var opt = new AsmHookOptions
|
var opt = new AsmHookOptions
|
||||||
{
|
{
|
||||||
PreferRelativeJump = true,
|
PreferRelativeJump = true,
|
||||||
Behaviour = Reloaded.Hooks.Definitions.Enums.AsmHookBehaviour.DoNotExecuteOriginal,
|
Behaviour = Reloaded.Hooks.Definitions.Enums.AsmHookBehaviour.DoNotExecuteOriginal,
|
||||||
MaxOpcodeSize = 5,
|
MaxOpcodeSize = 5,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.asmHook = new Reloaded.Hooks.AsmHook(code, (nuint)address, opt);
|
this.asmHook = new Reloaded.Hooks.AsmHook(code, (nuint)address, opt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,7 +56,16 @@ internal class CallHook<T> : IDisposable where T : Delegate
|
||||||
/// Gets a value indicating whether or not the hook is enabled.
|
/// Gets a value indicating whether or not the hook is enabled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsEnabled => this.asmHook.IsEnabled;
|
public bool IsEnabled => this.asmHook.IsEnabled;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IntPtr Address { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public string BackendName => "Reloaded AsmHook";
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool IsDisposed => this.detour == null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Starts intercepting a call to the function.
|
/// Starts intercepting a call to the function.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -65,7 +77,7 @@ internal class CallHook<T> : IDisposable where T : Delegate
|
||||||
this.asmHook.Activate();
|
this.asmHook.Activate();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.asmHook.Enable();
|
this.asmHook.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,15 @@
|
||||||
using System.Runtime.InteropServices;
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using Dalamud.Game;
|
||||||
|
using Dalamud.Game.Addon.Lifecycle;
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
|
using Dalamud.Hooking.Internal;
|
||||||
|
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using PInvoke;
|
using PInvoke;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
@ -10,23 +19,46 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Widget for displaying hook information.
|
/// Widget for displaying hook information.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class HookWidget : IDataWindowWidget
|
internal unsafe class HookWidget : IDataWindowWidget
|
||||||
{
|
{
|
||||||
|
private readonly List<IDalamudHook> hookStressTestList = [];
|
||||||
|
|
||||||
private Hook<MessageBoxWDelegate>? messageBoxMinHook;
|
private Hook<MessageBoxWDelegate>? messageBoxMinHook;
|
||||||
private bool hookUseMinHook;
|
private bool hookUseMinHook;
|
||||||
|
|
||||||
|
private int hookStressTestCount = 0;
|
||||||
|
private int hookStressTestMax = 1000;
|
||||||
|
private int hookStressTestWait = 100;
|
||||||
|
private int hookStressTestMaxDegreeOfParallelism = 10;
|
||||||
|
private StressTestHookTarget hookStressTestHookTarget = StressTestHookTarget.Random;
|
||||||
|
private bool hookStressTestRunning = false;
|
||||||
|
|
||||||
|
private MessageBoxWDelegate? messageBoxWOriginal;
|
||||||
|
private AddonFinalizeDelegate? addonFinalizeOriginal;
|
||||||
|
|
||||||
|
private AddonLifecycleAddressResolver? address;
|
||||||
|
|
||||||
private delegate int MessageBoxWDelegate(
|
private delegate int MessageBoxWDelegate(
|
||||||
IntPtr hWnd,
|
IntPtr hWnd,
|
||||||
[MarshalAs(UnmanagedType.LPWStr)] string text,
|
[MarshalAs(UnmanagedType.LPWStr)] string text,
|
||||||
[MarshalAs(UnmanagedType.LPWStr)] string caption,
|
[MarshalAs(UnmanagedType.LPWStr)] string caption,
|
||||||
NativeFunctions.MessageBoxType type);
|
NativeFunctions.MessageBoxType type);
|
||||||
|
|
||||||
|
private delegate void AddonFinalizeDelegate(AtkUnitManager* unitManager, AtkUnitBase** atkUnitBase);
|
||||||
|
|
||||||
|
private enum StressTestHookTarget
|
||||||
|
{
|
||||||
|
MessageBoxW,
|
||||||
|
AddonFinalize,
|
||||||
|
Random,
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public string DisplayName { get; init; } = "Hook";
|
public string DisplayName { get; init; } = "Hook";
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public string[]? CommandShortcuts { get; init; } = { "hook" };
|
public string[]? CommandShortcuts { get; init; } = { "hook" };
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public bool Ready { get; set; }
|
public bool Ready { get; set; }
|
||||||
|
|
||||||
|
|
@ -34,6 +66,9 @@ internal class HookWidget : IDataWindowWidget
|
||||||
public void Load()
|
public void Load()
|
||||||
{
|
{
|
||||||
this.Ready = true;
|
this.Ready = true;
|
||||||
|
|
||||||
|
this.address = new AddonLifecycleAddressResolver();
|
||||||
|
this.address.Setup(Service<TargetSigScanner>.Get());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
@ -41,7 +76,9 @@ internal class HookWidget : IDataWindowWidget
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ImGui.Checkbox("Use MinHook", ref this.hookUseMinHook);
|
ImGui.Checkbox("Use MinHook (only for regular hooks, AsmHook is Reloaded-only)", ref this.hookUseMinHook);
|
||||||
|
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
if (ImGui.Button("Create"))
|
if (ImGui.Button("Create"))
|
||||||
this.messageBoxMinHook = Hook<MessageBoxWDelegate>.FromSymbol("User32", "MessageBoxW", this.MessageBoxWDetour, this.hookUseMinHook);
|
this.messageBoxMinHook = Hook<MessageBoxWDelegate>.FromSymbol("User32", "MessageBoxW", this.MessageBoxWDetour, this.hookUseMinHook);
|
||||||
|
|
@ -66,18 +103,94 @@ internal class HookWidget : IDataWindowWidget
|
||||||
|
|
||||||
if (this.messageBoxMinHook != null)
|
if (this.messageBoxMinHook != null)
|
||||||
ImGui.Text("Enabled: " + this.messageBoxMinHook?.IsEnabled);
|
ImGui.Text("Enabled: " + this.messageBoxMinHook?.IsEnabled);
|
||||||
|
|
||||||
|
ImGui.Separator();
|
||||||
|
|
||||||
|
ImGui.BeginDisabled(this.hookStressTestRunning);
|
||||||
|
ImGui.Text("Stress Test");
|
||||||
|
|
||||||
|
if (ImGui.InputInt("Max", ref this.hookStressTestMax))
|
||||||
|
this.hookStressTestCount = 0;
|
||||||
|
|
||||||
|
ImGui.InputInt("Wait (ms)", ref this.hookStressTestWait);
|
||||||
|
ImGui.InputInt("Max Degree of Parallelism", ref this.hookStressTestMaxDegreeOfParallelism);
|
||||||
|
|
||||||
|
if (ImGui.BeginCombo("Target", HookTargetToString(this.hookStressTestHookTarget)))
|
||||||
|
{
|
||||||
|
foreach (var target in Enum.GetValues<StressTestHookTarget>())
|
||||||
|
{
|
||||||
|
if (ImGui.Selectable(HookTargetToString(target), this.hookStressTestHookTarget == target))
|
||||||
|
this.hookStressTestHookTarget = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndCombo();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.Button("Stress Test"))
|
||||||
|
{
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
this.hookStressTestRunning = true;
|
||||||
|
this.hookStressTestCount = 0;
|
||||||
|
Parallel.For(
|
||||||
|
0,
|
||||||
|
this.hookStressTestMax,
|
||||||
|
new ParallelOptions
|
||||||
|
{
|
||||||
|
MaxDegreeOfParallelism = this.hookStressTestMaxDegreeOfParallelism,
|
||||||
|
},
|
||||||
|
_ =>
|
||||||
|
{
|
||||||
|
this.hookStressTestList.Add(this.HookTarget(this.hookStressTestHookTarget));
|
||||||
|
this.hookStressTestCount++;
|
||||||
|
Thread.Sleep(this.hookStressTestWait);
|
||||||
|
});
|
||||||
|
}).ContinueWith(t =>
|
||||||
|
{
|
||||||
|
if (t.IsFaulted)
|
||||||
|
{
|
||||||
|
Log.Error(t.Exception, "Stress test failed");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Information("Stress test completed");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hookStressTestRunning = false;
|
||||||
|
this.hookStressTestList.ForEach(hook =>
|
||||||
|
{
|
||||||
|
hook.Dispose();
|
||||||
|
});
|
||||||
|
this.hookStressTestList.Clear();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndDisabled();
|
||||||
|
|
||||||
|
ImGui.TextUnformatted("Status: " + (this.hookStressTestRunning ? "Running" : "Idle"));
|
||||||
|
ImGui.ProgressBar(this.hookStressTestCount / (float)this.hookStressTestMax, new System.Numerics.Vector2(0, 0), $"{this.hookStressTestCount}/{this.hookStressTestMax}");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log.Error(ex, "MinHook error caught");
|
Log.Error(ex, "Hook error caught");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string HookTargetToString(StressTestHookTarget target)
|
||||||
|
{
|
||||||
|
return target switch
|
||||||
|
{
|
||||||
|
StressTestHookTarget.MessageBoxW => "MessageBoxW (Hook)",
|
||||||
|
StressTestHookTarget.AddonFinalize => "AddonFinalize (Hook)",
|
||||||
|
_ => target.ToString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private int MessageBoxWDetour(IntPtr hwnd, string text, string caption, NativeFunctions.MessageBoxType type)
|
private int MessageBoxWDetour(IntPtr hwnd, string text, string caption, NativeFunctions.MessageBoxType type)
|
||||||
{
|
{
|
||||||
Log.Information("[DATAHOOK] {Hwnd} {Text} {Caption} {Type}", hwnd, text, caption, type);
|
Log.Information("[DATAHOOK] {Hwnd} {Text} {Caption} {Type}", hwnd, text, caption, type);
|
||||||
|
|
||||||
var result = this.messageBoxMinHook!.Original(hwnd, "Cause Access Violation?", caption, NativeFunctions.MessageBoxType.YesNo);
|
var result = this.messageBoxWOriginal!(hwnd, "Cause Access Violation?", caption, NativeFunctions.MessageBoxType.YesNo);
|
||||||
|
|
||||||
if (result == (int)User32.MessageBoxResult.IDYES)
|
if (result == (int)User32.MessageBoxResult.IDYES)
|
||||||
{
|
{
|
||||||
|
|
@ -86,4 +199,52 @@ internal class HookWidget : IDataWindowWidget
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnAddonFinalize(AtkUnitManager* unitManager, AtkUnitBase** atkUnitBase)
|
||||||
|
{
|
||||||
|
Log.Information("OnAddonFinalize");
|
||||||
|
this.addonFinalizeOriginal!(unitManager, atkUnitBase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAddonUpdate(AtkUnitBase* thisPtr, float delta)
|
||||||
|
{
|
||||||
|
Log.Information("OnAddonUpdate");
|
||||||
|
}
|
||||||
|
|
||||||
|
private IDalamudHook HookMessageBoxW()
|
||||||
|
{
|
||||||
|
var hook = Hook<MessageBoxWDelegate>.FromSymbol(
|
||||||
|
"User32",
|
||||||
|
"MessageBoxW",
|
||||||
|
this.MessageBoxWDetour,
|
||||||
|
this.hookUseMinHook);
|
||||||
|
|
||||||
|
this.messageBoxWOriginal = hook.Original;
|
||||||
|
hook.Enable();
|
||||||
|
return hook;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IDalamudHook HookAddonFinalize()
|
||||||
|
{
|
||||||
|
var hook = Hook<AddonFinalizeDelegate>.FromAddress(this.address!.AddonFinalize, this.OnAddonFinalize);
|
||||||
|
|
||||||
|
this.addonFinalizeOriginal = hook.Original;
|
||||||
|
hook.Enable();
|
||||||
|
return hook;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IDalamudHook HookTarget(StressTestHookTarget target)
|
||||||
|
{
|
||||||
|
if (target == StressTestHookTarget.Random)
|
||||||
|
{
|
||||||
|
target = (StressTestHookTarget)Random.Shared.Next(0, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return target switch
|
||||||
|
{
|
||||||
|
StressTestHookTarget.MessageBoxW => this.HookMessageBoxW(),
|
||||||
|
StressTestHookTarget.AddonFinalize => this.HookAddonFinalize(),
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(target), target, null),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue