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