diff --git a/Dalamud/Hooking/Hook.cs b/Dalamud/Hooking/Hook.cs
index c6311a79d..a2c4ec3f6 100644
--- a/Dalamud/Hooking/Hook.cs
+++ b/Dalamud/Hooking/Hook.cs
@@ -1,4 +1,5 @@
using System;
+using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using EasyHook;
@@ -9,9 +10,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 sealed class Hook : IDisposable where T : Delegate {
- private bool isDisposed;
-
+ public sealed class Hook : IDisposable, IDalamudHook where T : Delegate {
private readonly IntPtr address;
private readonly T original;
@@ -69,19 +68,20 @@ namespace Dalamud.Hooking {
this.hookInfo = LocalHook.Create(address, detour, callbackParam); // Installs a hook here
this.address = address;
this.original = Marshal.GetDelegateForFunctionPointer(this.hookInfo.HookBypassAddress);
+ HookInfo.TrackedHooks.Add(new HookInfo() { Delegate = detour, Hook = this, Assembly = Assembly.GetCallingAssembly()});
}
///
/// Remove a hook from the current process.
///
public void Dispose() {
- if (this.isDisposed) {
+ if (this.IsDisposed) {
return;
}
this.hookInfo.Dispose();
- this.isDisposed = true;
+ this.IsDisposed = true;
}
///
@@ -104,9 +104,24 @@ namespace Dalamud.Hooking {
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CheckDisposed() {
- if (this.isDisposed) {
+ if (this.IsDisposed) {
throw new ObjectDisposedException("Hook is already disposed.");
}
}
+
+ ///
+ /// Check if the hook is enabled.
+ ///
+ public bool IsEnabled {
+ get {
+ CheckDisposed();
+ return this.hookInfo.ThreadACL.IsExclusive;
+ }
+ }
+
+ ///
+ /// Check if the hook has been disposed
+ ///
+ public bool IsDisposed { get; private set; }
}
}
diff --git a/Dalamud/Hooking/HookInfo.cs b/Dalamud/Hooking/HookInfo.cs
new file mode 100644
index 000000000..6f5e1d9e0
--- /dev/null
+++ b/Dalamud/Hooking/HookInfo.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Reflection;
+
+namespace Dalamud.Hooking {
+ internal class HookInfo {
+
+ internal static List TrackedHooks = new List();
+
+ internal IDalamudHook Hook { get; set; }
+ internal Delegate Delegate { get; set; }
+ internal Assembly Assembly { get; set; }
+
+ private ulong? inProcessMemory = 0;
+ internal ulong? InProcessMemory {
+ get {
+ if (Hook.IsDisposed) return 0;
+ if (this.inProcessMemory == null) return null;
+ if (this.inProcessMemory.Value > 0) return this.inProcessMemory.Value;
+ var p = Process.GetCurrentProcess().MainModule;
+ var begin = (ulong) p.BaseAddress.ToInt64();
+ var end = begin + (ulong) p.ModuleMemorySize;
+ var hookAddr = (ulong) Hook.Address.ToInt64();
+ if (hookAddr >= begin && hookAddr <= end) {
+ this.inProcessMemory = hookAddr - begin;
+ return this.inProcessMemory.Value;
+ } else {
+ this.inProcessMemory = null;
+ return null;
+ }
+ }
+ }
+
+ }
+}
diff --git a/Dalamud/Hooking/IDalamudHook.cs b/Dalamud/Hooking/IDalamudHook.cs
new file mode 100644
index 000000000..fb756229d
--- /dev/null
+++ b/Dalamud/Hooking/IDalamudHook.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace Dalamud.Hooking {
+ internal interface IDalamudHook {
+ public IntPtr Address { get; }
+ public bool IsEnabled { get; }
+ public bool IsDisposed { get; }
+ }
+}
diff --git a/Dalamud/Interface/DalamudPluginStatWindow.cs b/Dalamud/Interface/DalamudPluginStatWindow.cs
index 0aad890fd..6d2a6c7d7 100644
--- a/Dalamud/Interface/DalamudPluginStatWindow.cs
+++ b/Dalamud/Interface/DalamudPluginStatWindow.cs
@@ -1,6 +1,8 @@
using System;
using System.Linq;
+using System.Reflection;
using Dalamud.Game.Internal;
+using Dalamud.Hooking;
using Dalamud.Plugin;
using ImGuiNET;
@@ -8,6 +10,8 @@ namespace Dalamud.Interface {
internal class DalamudPluginStatWindow : IDisposable {
private readonly PluginManager pluginManager;
+ private bool showDalamudHooks;
+
public DalamudPluginStatWindow(PluginManager pluginManager) {
this.pluginManager = pluginManager;
}
@@ -122,10 +126,70 @@ namespace Dalamud.Interface {
ImGui.EndTabItem();
}
+ if (ImGui.BeginTabItem("Hooks")) {
+ ImGui.Columns(3);
+ ImGui.SetColumnWidth(0, ImGui.GetWindowContentRegionWidth() - 280);
+ ImGui.SetColumnWidth(1, 180f);
+ ImGui.SetColumnWidth(2, 100f);
+ ImGui.Text("Detour Method");
+ ImGui.SameLine();
+ ImGui.Text(" ");
+ ImGui.SameLine();
+ ImGui.Checkbox("Show Dalamud Hooks ###showDalamudHooksCheckbox", ref showDalamudHooks);
+ ImGui.NextColumn();
+ ImGui.Text("Address");
+ ImGui.NextColumn();
+ ImGui.Text("Status");
+ ImGui.NextColumn();
+ ImGui.Separator();
+ ImGui.Separator();
+
+ foreach (var trackedHook in HookInfo.TrackedHooks) {
+ try {
+ if (!this.showDalamudHooks && trackedHook.Assembly == Assembly.GetExecutingAssembly()) continue;
+
+
+ ImGui.Text($"{trackedHook.Delegate.Target} :: {trackedHook.Delegate.Method.Name}");
+ ImGui.TextDisabled(trackedHook.Assembly.FullName);
+ ImGui.NextColumn();
+ if (!trackedHook.Hook.IsDisposed) {
+ ImGui.Text($"{trackedHook.Hook.Address.ToInt64():X}");
+ if (ImGui.IsItemClicked()) ImGui.SetClipboardText($"{trackedHook.Hook.Address.ToInt64():X}");
+
+ var processMemoryOffset = trackedHook.InProcessMemory;
+ if (processMemoryOffset.HasValue) {
+ ImGui.Text($"ffxiv_dx11.exe + {processMemoryOffset:X}");
+ if (ImGui.IsItemClicked()) ImGui.SetClipboardText($"ffxiv_dx11.exe+{processMemoryOffset:X}");
+ }
+
+ }
+ ImGui.NextColumn();
+
+ if (trackedHook.Hook.IsDisposed) {
+ ImGui.Text("Disposed");
+ } else {
+ ImGui.Text(trackedHook.Hook.IsEnabled ? "Enabled" : "Disabled");
+ }
+
+ ImGui.NextColumn();
+ } catch (Exception ex) {
+ ImGui.Text(ex.Message);
+ ImGui.NextColumn();
+ while (ImGui.GetColumnIndex() != 0) ImGui.NextColumn();
+ }
+
+ ImGui.Separator();
}
+ ImGui.Columns();
}
+ if (ImGui.IsWindowAppearing()) {
+ HookInfo.TrackedHooks.RemoveAll(h => h.Hook.IsDisposed);
+ }
+
+
+
ImGui.EndTabBar();
ImGui.End();