diff --git a/Dalamud/Configuration/Internal/EnvironmentConfiguration.cs b/Dalamud/Configuration/Internal/EnvironmentConfiguration.cs index b271ee4a4..31bee1204 100644 --- a/Dalamud/Configuration/Internal/EnvironmentConfiguration.cs +++ b/Dalamud/Configuration/Internal/EnvironmentConfiguration.cs @@ -25,7 +25,7 @@ namespace Dalamud.Configuration.Internal /// /// Gets a value indicating whether the DalamudForceCoreHook setting has been enabled. /// - public static bool DalamudForceCoreHook { get; } = GetEnvironmentVariable("DALAMUD_FORCE_COREHOOK"); + public static bool DalamudForceMinHook { get; } = GetEnvironmentVariable("DALAMUD_FORCE_COREHOOK"); private static bool GetEnvironmentVariable(string name) => bool.Parse(Environment.GetEnvironmentVariable(name) ?? "false"); diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 92c88db5c..57b33b75e 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -64,10 +64,10 @@ - + @@ -94,12 +94,6 @@ - - - PreserveNewest - - - diff --git a/Dalamud/Hooking/AsmHook.cs b/Dalamud/Hooking/AsmHook.cs index 49c951867..35eac4efd 100644 --- a/Dalamud/Hooking/AsmHook.cs +++ b/Dalamud/Hooking/AsmHook.cs @@ -112,6 +112,9 @@ namespace Dalamud.Hooking /// public bool IsDisposed { get; private set; } + /// + public string BackendName => "Reloaded/Asm"; + /// /// Remove a hook from the current process. /// diff --git a/Dalamud/Hooking/Hook.cs b/Dalamud/Hooking/Hook.cs index 7171e2ec7..ba19ffb18 100644 --- a/Dalamud/Hooking/Hook.cs +++ b/Dalamud/Hooking/Hook.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Reflection; using Dalamud.Configuration.Internal; @@ -18,8 +19,8 @@ namespace Dalamud.Hooking { private readonly IntPtr address; private readonly Reloaded.Hooks.Definitions.IHook hookImpl; - private readonly CoreHook.IHook coreHookImpl; - private readonly bool isCoreHook; + private readonly MinSharp.Hook minHookImpl; + private readonly bool isMinHook; /// /// Initializes a new instance of the class. @@ -35,15 +36,15 @@ namespace Dalamud.Hooking /// /// Initializes a new instance of the class. /// Hook is not activated until Enable() method is called. - /// Please do not use CoreHook unless you have thoroughly troubleshot why Reloaded does not work. + /// Please do not use MinHook unless you have thoroughly troubleshot why Reloaded does not work. /// /// A memory address to install a hook. /// Callback function. Delegate must have a same original function prototype. - /// Use the corehook hooking library instead of Reloaded. - public Hook(IntPtr address, T detour, bool useCoreHook) + /// Use the MinHook hooking library instead of Reloaded. + public Hook(IntPtr address, T detour, bool useMinHook) { address = HookManager.FollowJmp(address); - this.isCoreHook = useCoreHook || EnvironmentConfiguration.DalamudForceCoreHook; + this.isMinHook = EnvironmentConfiguration.DalamudForceMinHook || useMinHook; var hasOtherHooks = HookManager.Originals.ContainsKey(address); if (!hasOtherHooks) @@ -53,18 +54,17 @@ namespace Dalamud.Hooking } this.address = address; - if (this.isCoreHook) + if (this.isMinHook) { - try - { - this.coreHookImpl = CoreHook.HookFactory.CreateHook(address, detour); - } - catch (Exception ex) - { - this.isCoreHook = false; - Log.Error(ex, "CoreHook is having a bad day, defaulting to Reloaded"); - this.hookImpl = ReloadedHooks.Instance.CreateHook(detour, address.ToInt64()); - } + var indexList = hasOtherHooks + ? HookManager.MultiHookTracker[address] + : HookManager.MultiHookTracker[address] = new(); + var index = (ulong)indexList.Count; + + this.minHookImpl = new MinSharp.Hook(address, detour, index); + + // Add afterwards, so the hookIdent starts at 0. + indexList.Add(this); } else { @@ -96,9 +96,9 @@ namespace Dalamud.Hooking get { this.CheckDisposed(); - if (this.isCoreHook) + if (this.isMinHook) { - return this.coreHookImpl.Original; + return this.minHookImpl.Original; } else { @@ -115,9 +115,9 @@ namespace Dalamud.Hooking get { this.CheckDisposed(); - if (this.isCoreHook) + if (this.isMinHook) { - return this.coreHookImpl.Enabled; + return this.minHookImpl.Enabled; } else { @@ -131,6 +131,18 @@ namespace Dalamud.Hooking /// public bool IsDisposed { get; private set; } + /// + public string BackendName + { + get + { + if (this.isMinHook) + return "MinHook"; + + return "Reloaded"; + } + } + /// /// Creates a hook. Hooking address is inferred by calling to GetProcAddress() function. /// The hook is not activated until Enable() method is called. @@ -145,14 +157,14 @@ namespace Dalamud.Hooking /// /// 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 CoreHook unless you have thoroughly troubleshot why Reloaded does not work. + /// Please do not use MinHook unless you have thoroughly troubleshot why Reloaded does not work. /// /// A name of the module currently loaded in the memory. (e.g. ws2_32.dll). /// A name of the exported function name (e.g. send). /// Callback function. Delegate must have a same original function prototype. - /// Use the corehook hooking library instead of Reloaded. + /// Use the MinHook hooking library instead of Reloaded. /// The hook with the supplied parameters. - public static Hook FromSymbol(string moduleName, string exportName, T detour, bool useCoreHook) + public static Hook FromSymbol(string moduleName, string exportName, T detour, bool useMinHook) { var moduleHandle = NativeFunctions.GetModuleHandleW(moduleName); if (moduleHandle == IntPtr.Zero) @@ -162,7 +174,7 @@ namespace Dalamud.Hooking if (procAddress == IntPtr.Zero) throw new Exception($"Could not get the address of {moduleName}::{exportName}"); - return new Hook(procAddress, detour, useCoreHook); + return new Hook(procAddress, detour, useMinHook); } /// @@ -173,12 +185,10 @@ namespace Dalamud.Hooking if (this.IsDisposed) return; - if (this.isCoreHook) + if (this.isMinHook) { - this.Disable(); - // Disposing CoreHook causes an APPCRASH on game exit. - // We already overwrite the original hook code, so there shouldn't be any real risk with not disposing here. - // this.coreHookImpl.Dispose(); + this.minHookImpl.Dispose(); + HookManager.MultiHookTracker[this.address] = null; } else { @@ -195,10 +205,12 @@ namespace Dalamud.Hooking { this.CheckDisposed(); - if (this.isCoreHook) + if (this.isMinHook) { - if (!this.coreHookImpl.Enabled) - this.coreHookImpl.Enabled = true; + if (!this.minHookImpl.Enabled) + { + this.minHookImpl.Enable(); + } } else { @@ -217,10 +229,12 @@ namespace Dalamud.Hooking { this.CheckDisposed(); - if (this.isCoreHook) + if (this.isMinHook) { - if (this.coreHookImpl.Enabled) - this.coreHookImpl.Enabled = false; + if (this.minHookImpl.Enabled) + { + this.minHookImpl.Disable(); + } } else { diff --git a/Dalamud/Hooking/IDalamudHook.cs b/Dalamud/Hooking/IDalamudHook.cs index deaf62957..5a9ae2716 100644 --- a/Dalamud/Hooking/IDalamudHook.cs +++ b/Dalamud/Hooking/IDalamudHook.cs @@ -21,5 +21,10 @@ namespace Dalamud.Hooking /// Gets a value indicating whether or not the hook is disposed. /// public bool IsDisposed { get; } + + /// + /// Gets the name of the hooking backend used for the hook. + /// + public string BackendName { get; } } } diff --git a/Dalamud/Hooking/Internal/HookManager.cs b/Dalamud/Hooking/Internal/HookManager.cs index 1f67ee1e8..8990930a0 100644 --- a/Dalamud/Hooking/Internal/HookManager.cs +++ b/Dalamud/Hooking/Internal/HookManager.cs @@ -79,6 +79,11 @@ namespace Dalamud.Hooking.Internal /// internal static Dictionary Originals { get; } = new(); + /// + /// Gets a static dictionary of the number of hooks on a given address. + /// + internal static Dictionary> MultiHookTracker { get; } = new(); + /// public void Dispose() { diff --git a/Dalamud/Interface/Internal/Windows/DataWindow.cs b/Dalamud/Interface/Internal/Windows/DataWindow.cs index f6a45846d..e7355e2ed 100644 --- a/Dalamud/Interface/Internal/Windows/DataWindow.cs +++ b/Dalamud/Interface/Internal/Windows/DataWindow.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Numerics; using System.Reflection; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; @@ -27,6 +28,7 @@ using Dalamud.Game.Gui; using Dalamud.Game.Gui.FlyText; using Dalamud.Game.Gui.Toast; using Dalamud.Game.Text; +using Dalamud.Hooking; using Dalamud.Interface.Colors; using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.Windowing; @@ -69,6 +71,9 @@ namespace Dalamud.Interface.Internal.Windows private UIDebug addonInspector = null; + private Hook? messageBoxMinHook; + private bool hookUseMinHook = false; + // IPC private ICallGateProvider ipcPub; private ICallGateSubscriber ipcSub; @@ -117,6 +122,12 @@ namespace Dalamud.Interface.Internal.Windows this.Load(); } + private delegate int MessageBoxWDelegate( + IntPtr hWnd, + [MarshalAs(UnmanagedType.LPWStr)] string text, + [MarshalAs(UnmanagedType.LPWStr)] string caption, + NativeFunctions.MessageBoxType type); + private enum DataKind { Server_OpCode, @@ -143,6 +154,7 @@ namespace Dalamud.Interface.Internal.Windows Gamepad, Configuration, TaskSched, + Hook, } /// @@ -318,6 +330,10 @@ namespace Dalamud.Interface.Internal.Windows case DataKind.TaskSched: this.DrawTaskSched(); break; + + case DataKind.Hook: + this.DrawHook(); + break; } } else @@ -335,6 +351,12 @@ namespace Dalamud.Interface.Internal.Windows ImGui.EndChild(); } + private int MessageBoxWDetour(IntPtr hwnd, string text, string caption, NativeFunctions.MessageBoxType type) + { + Log.Information("[DATAHOOK] {0} {1} {2} {3}", hwnd, text, caption, type); + return this.messageBoxMinHook.Original(hwnd, text, caption, type); + } + private void DrawServerOpCode() { ImGui.TextUnformatted(this.serverOpString); @@ -975,7 +997,7 @@ namespace Dalamud.Interface.Internal.Windows ImGui.TableSetupColumn("Size", ImGuiTableColumnFlags.WidthFixed, fontWidth * 10); ImGui.TableSetupColumn("Pointer", ImGuiTableColumnFlags.WidthStretch); ImGui.TableHeadersRow(); - for (int numberArrayIndex = 0; numberArrayIndex < atkArrayDataHolder->NumberArrayCount; numberArrayIndex++) + for (var numberArrayIndex = 0; numberArrayIndex < atkArrayDataHolder->NumberArrayCount; numberArrayIndex++) { ImGui.TableNextRow(); ImGui.TableNextColumn(); @@ -989,7 +1011,7 @@ namespace Dalamud.Interface.Internal.Windows if (ImGui.TreeNodeEx($"{(long)numberArrayData:X}###{numberArrayIndex}", ImGuiTreeNodeFlags.SpanFullWidth)) { ImGui.NewLine(); - int tableHeight = Math.Min(40, numberArrayData->AtkArrayData.Size + 4); + var tableHeight = Math.Min(40, numberArrayData->AtkArrayData.Size + 4); if (ImGui.BeginTable($"NumberArrayDataTable", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY, new Vector2(0.0F, fontHeight * tableHeight))) { ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, fontWidth * 6); @@ -997,7 +1019,7 @@ namespace Dalamud.Interface.Internal.Windows ImGui.TableSetupColumn("Integer", ImGuiTableColumnFlags.WidthFixed, fontWidth * 12); ImGui.TableSetupColumn("Float", ImGuiTableColumnFlags.WidthFixed, fontWidth * 20); ImGui.TableHeadersRow(); - for (int numberIndex = 0; numberIndex < numberArrayData->AtkArrayData.Size; numberIndex++) + for (var numberIndex = 0; numberIndex < numberArrayData->AtkArrayData.Size; numberIndex++) { ImGui.TableNextRow(); ImGui.TableNextColumn(); @@ -1038,7 +1060,7 @@ namespace Dalamud.Interface.Internal.Windows ImGui.TableSetupColumn("Size", ImGuiTableColumnFlags.WidthFixed, fontWidth * 10); ImGui.TableSetupColumn("Pointer", ImGuiTableColumnFlags.WidthStretch); ImGui.TableHeadersRow(); - for (int stringArrayIndex = 0; stringArrayIndex < atkArrayDataHolder->StringArrayCount; stringArrayIndex++) + for (var stringArrayIndex = 0; stringArrayIndex < atkArrayDataHolder->StringArrayCount; stringArrayIndex++) { ImGui.TableNextRow(); ImGui.TableNextColumn(); @@ -1052,13 +1074,13 @@ namespace Dalamud.Interface.Internal.Windows if (ImGui.TreeNodeEx($"{(long)stringArrayData:X}###{stringArrayIndex}", ImGuiTreeNodeFlags.SpanFullWidth)) { ImGui.NewLine(); - int tableHeight = Math.Min(40, stringArrayData->AtkArrayData.Size + 4); + var tableHeight = Math.Min(40, stringArrayData->AtkArrayData.Size + 4); if (ImGui.BeginTable($"StringArrayDataTable", 2, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY, new Vector2(0.0F, fontHeight * tableHeight))) { ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed, fontWidth * 6); ImGui.TableSetupColumn("String", ImGuiTableColumnFlags.WidthStretch); ImGui.TableHeadersRow(); - for (int stringIndex = 0; stringIndex < stringArrayData->AtkArrayData.Size; stringIndex++) + for (var stringIndex = 0; stringIndex < stringArrayData->AtkArrayData.Size; stringIndex++) { ImGui.TableNextRow(); ImGui.TableNextColumn(); @@ -1563,6 +1585,42 @@ namespace Dalamud.Interface.Internal.Windows } } + private void DrawHook() + { + try + { + ImGui.Checkbox("Use MinHook", ref this.hookUseMinHook); + + if (ImGui.Button("Create")) + this.messageBoxMinHook = Hook.FromSymbol("User32", "MessageBoxW", this.MessageBoxWDetour, this.hookUseMinHook); + + if (ImGui.Button("Enable")) + this.messageBoxMinHook?.Enable(); + + if (ImGui.Button("Disable")) + this.messageBoxMinHook?.Disable(); + + if (ImGui.Button("Call Original")) + this.messageBoxMinHook?.Original(IntPtr.Zero, "Hello from .Original", "Hook Test", NativeFunctions.MessageBoxType.Ok); + + if (ImGui.Button("Dispose")) + { + this.messageBoxMinHook?.Dispose(); + this.messageBoxMinHook = null; + } + + if (ImGui.Button("Test")) + _ = NativeFunctions.MessageBoxW(IntPtr.Zero, "Hi", "Hello", NativeFunctions.MessageBoxType.Ok); + + if (this.messageBoxMinHook != null) + ImGui.Text("Enabled: " + this.messageBoxMinHook?.IsEnabled); + } + catch (Exception ex) + { + Log.Error(ex, "MinHook error caught"); + } + } + private async Task TestTaskInTaskDelay() { await Task.Delay(5000); diff --git a/Dalamud/Interface/Internal/Windows/PluginStatWindow.cs b/Dalamud/Interface/Internal/Windows/PluginStatWindow.cs index e3760794a..257b67857 100644 --- a/Dalamud/Interface/Internal/Windows/PluginStatWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginStatWindow.cs @@ -4,6 +4,7 @@ using System.Reflection; using Dalamud.Game; using Dalamud.Game.Internal; +using Dalamud.Hooking; using Dalamud.Hooking.Internal; using Dalamud.Interface.Windowing; using Dalamud.Plugin.Internal; @@ -175,11 +176,12 @@ namespace Dalamud.Interface.Internal.Windows if (ImGui.BeginTabItem("Hooks")) { - ImGui.Columns(3); + ImGui.Columns(4); - ImGui.SetColumnWidth(0, ImGui.GetWindowContentRegionWidth() - 280); + ImGui.SetColumnWidth(0, ImGui.GetWindowContentRegionWidth() - 330); ImGui.SetColumnWidth(1, 180f); ImGui.SetColumnWidth(2, 100f); + ImGui.SetColumnWidth(3, 100f); ImGui.Text("Detour Method"); ImGui.SameLine(); @@ -196,6 +198,9 @@ namespace Dalamud.Interface.Internal.Windows ImGui.Text("Status"); ImGui.NextColumn(); + ImGui.Text("Backend"); + ImGui.NextColumn(); + ImGui.Separator(); ImGui.Separator(); @@ -240,6 +245,10 @@ namespace Dalamud.Interface.Internal.Windows } ImGui.NextColumn(); + + ImGui.Text(trackedHook.Hook.BackendName); + + ImGui.NextColumn(); } catch (Exception ex) { diff --git a/Dalamud/Support/Troubleshooting.cs b/Dalamud/Support/Troubleshooting.cs index e2e892b77..8034bb979 100644 --- a/Dalamud/Support/Troubleshooting.cs +++ b/Dalamud/Support/Troubleshooting.cs @@ -74,7 +74,7 @@ namespace Dalamud.Support DoPluginTest = configuration.DoPluginTest, InterfaceLoaded = interfaceManager?.IsReady ?? false, ThirdRepo = configuration.ThirdRepoList, - ForcedCoreHook = EnvironmentConfiguration.DalamudForceCoreHook, + ForcedMinHook = EnvironmentConfiguration.DalamudForceMinHook, }; var encodedPayload = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(payload))); @@ -113,7 +113,7 @@ namespace Dalamud.Support public bool InterfaceLoaded { get; set; } - public bool ForcedCoreHook { get; set; } + public bool ForcedMinHook { get; set; } public List ThirdRepo { get; set; } } diff --git a/Dalamud/corehook64.dll b/Dalamud/corehook64.dll deleted file mode 100644 index 9b21a40d1..000000000 Binary files a/Dalamud/corehook64.dll and /dev/null differ