From d09f491e67f409ed7d40ef6619a307e89b4f1cbc Mon Sep 17 00:00:00 2001 From: Andrew Opalach Date: Wed, 4 Feb 2026 13:12:15 -0500 Subject: [PATCH 1/3] ImGui usability tweaks --- Assets/Common/AddressRecords.json | 5 + SharpPluginLoader.Core/Rendering/Renderer.cs | 136 +++++++++++++++++-- 2 files changed, 128 insertions(+), 13 deletions(-) diff --git a/Assets/Common/AddressRecords.json b/Assets/Common/AddressRecords.json index bf4783a..bd11e23 100644 --- a/Assets/Common/AddressRecords.json +++ b/Assets/Common/AddressRecords.json @@ -463,5 +463,10 @@ "Name": "EmAction:VTableAssignment", "Pattern": "C7 41 0C 89 88 88 3C 89 71 08 48 8D 05 ?? ?? ?? ?? 48 89 01", "Offset": 13 + }, + { + "Name": "Keyboard:WriteInput", + "Pattern": "41 57 48 81 EC 20 01 00 00 48 8B E9 48 8B FA 48 8B 89 10 0A 00 00", + "Offset": -10 } ] diff --git a/SharpPluginLoader.Core/Rendering/Renderer.cs b/SharpPluginLoader.Core/Rendering/Renderer.cs index d1af85d..01ba479 100644 --- a/SharpPluginLoader.Core/Rendering/Renderer.cs +++ b/SharpPluginLoader.Core/Rendering/Renderer.cs @@ -6,7 +6,6 @@ using ImGuiNET; using SharpPluginLoader.Core.IO; using SharpPluginLoader.Core.Memory; -using SharpPluginLoader.Core.MtTypes; namespace SharpPluginLoader.Core.Rendering { @@ -68,7 +67,7 @@ public static TextureHandle LoadTexture(string path, out uint width, out uint he /// Fonts must be registered before the first call to . /// Ideally, fonts should be registered in the method. /// - public static unsafe void RegisterFont(string name, string path, float size, nint glyphRanges = 0, + public static unsafe void RegisterFont(string name, string path, float size, nint glyphRanges = 0, bool merge = false, int oversampleV = 0, int oversampleH = 0) { if (_fontsSubmitted) @@ -232,10 +231,13 @@ Initializing Renderer with if (ImGui.GetCurrentContext() != 0) return ImGui.GetCurrentContext(); - + ImGui.CreateContext(); var io = ImGui.GetIO(); io.ConfigFlags |= ImGuiConfigFlags.DockingEnable; + // Set NavNoCaptureKeyboard because it doesn't include ctrl+tab and handle it by + // checking io.NavActive when deciding to block keyboard or not. + io.ConfigFlags |= ImGuiConfigFlags.NavNoCaptureKeyboard; // Currently causes a freeze when dragging a window outside of the main window. // Most likely the WndProc doesn't process events anymore which causes windows to think it's frozen. // io.ConfigFlags |= ImGuiConfigFlags.ViewportsEnable; @@ -248,13 +250,106 @@ Initializing Renderer with _mouseUpdateHook = Hook.Create(sMhMouse.GetVirtualFunction(6), m => { // Prevent the game from doing any mouse updates if an ImGui window is focused. - if (ImGui.GetIO().MouseDrawCursor) + var anyFocused = ImGui.IsWindowFocused(ImGuiFocusedFlags.AnyWindow); + if (anyFocused) + { + unsafe + { + // Zero mouse delta because it won't get updated. + *(int*)(m + 0xFC) = 0; + *(int*)(m + 0x100) = 0; + } + _lastUpdateHadFocus = true; return; + } _mouseUpdateHook.Original(m); + + // Block mouse clicks if an ImGui window is hovered. Use _lastUpdateHadFocus + // to more consistently block a click used to unfocus the ImGui window. + var anyHovered = ImGui.IsWindowHovered(ImGuiHoveredFlags.AnyWindow); + if (anyHovered || _lastUpdateHadFocus) + { + unsafe + { + *(byte*)(m + 0x188) = 0; + } + _lastUpdateHadFocus = false; + } + }); + } + + var sMhKeyboard = SingletonManager.GetSingleton("sMhKeyboard"); + if (sMhKeyboard is not null) + { + _keyboardUpdateHook = Hook.Create(AddressRepository.Get("Keyboard:WriteInput"), (kb, kbState) => + { + _keyboardUpdateHook.Original(kb, kbState); + + if (_lastUpdateHadKeyboard) + { + // Block Escape/Enter if they were presumably used to exit an input field. + // If for some reason the menu key is being held here, this will supersede it + // and cause the menu to be re-toggled on the release of Escape or Enter. + if (Input.IsDown(Key.Escape)) + { + _waitForRelease = Key.Escape; + } + else if (Input.IsDown(Key.Enter)) + { + _waitForRelease = Key.Enter; + } + } + + if (_waitForRelease == null) + { + // Block the key used to bring up the menu. + if (Input.IsDown(_menuKey)) + { + _showMenu = !_showMenu; + _waitForRelease = _menuKey; + } +#if DEBUG + else if (Input.IsDown(_demoKey)) + { + _showDemo = !_showDemo; + _waitForRelease = _demoKey; + } +#endif + } + + unsafe + { + KeyboardState* state = (KeyboardState*)kbState; + + if (_waitForRelease != null) + { + byte* vkTable = (byte*)(kb + 0x38); + byte vk = vkTable[(int)_waitForRelease]; + uint vkMask = 1u << (vk & 0x1F); + // No longer down = Released. + if ((state->On[vk >> 5] & vkMask) == 0) + { + _waitForRelease = null; + } + else + { + // On the update a key is first "Down", it seems to only be set in + // KeyboardState::On. So negating that should effectively block it. + state->On[vk >> 5] &= ~vkMask; + } + } + + var io = ImGui.GetIO(); + _lastUpdateHadKeyboard = io.WantCaptureKeyboard || io.NavActive; + if (_lastUpdateHadKeyboard) + { + Unsafe.InitBlockUnaligned((byte*)state, 0, (uint)Marshal.SizeOf()); + } + } }); } - + Log.Debug("Renderer.Initialize"); return ImGui.GetCurrentContext(); @@ -270,11 +365,6 @@ internal static void Render() [UnmanagedCallersOnly] internal static unsafe nint ImGuiRender() { - if (Input.IsPressed(_menuKey)) - _showMenu = !_showMenu; - if (Input.IsPressed(_demoKey)) - _showDemo = !_showDemo; - var io = ImGui.GetIO(); var anyFocused = ImGui.IsWindowFocused(ImGuiFocusedFlags.AnyWindow); var anyHovered = ImGui.IsWindowHovered(ImGuiHoveredFlags.AnyWindow); @@ -293,6 +383,19 @@ internal static unsafe nint ImGuiRender() { if (ImGui.BeginMenu("Options")) { + bool keyboardNav = (io.ConfigFlags & ImGuiConfigFlags.NavEnableKeyboard) != 0; + if (ImGui.Checkbox("Keyboard Navigation", ref keyboardNav)) + { + if (keyboardNav) + { + io.ConfigFlags |= ImGuiConfigFlags.NavEnableKeyboard; + } + else + { + io.ConfigFlags &= ~ImGuiConfigFlags.NavEnableKeyboard; + } + } + ImGui.Checkbox("Draw Primitives as Wireframe", ref MemoryUtil.AsRef(_renderingOptionPointers.DrawPrimitivesAsWireframe)); @@ -341,7 +444,7 @@ ref MemoryUtil.AsRef(_renderingOptionPointers.LineThickness), ImGui.ShowDemoWindow(ref _showDemo); #endif - ImGui.PushStyleVar(ImGuiStyleVar.WindowRounding, 5f); + ImGui.PushStyleVar(ImGuiStyleVar.WindowRounding, 5.0f); InternalCalls.RenderNotifications(); ImGui.PopStyleVar(); @@ -473,16 +576,23 @@ private static nint GetCursorPositionHook(nint app, out Point pos) private delegate void MouseUpdateDelegate(nint sMhMouse); private static Hook _getCursorPositionHook = null!; private static Hook _mouseUpdateHook = null!; + private static bool _lastUpdateHadFocus = false; + private delegate void KeyboardUpdateDelegate(nint sMhKeyboard, nint kbState); + private static Hook _keyboardUpdateHook = null!; + private static bool _lastUpdateHadKeyboard = false; + private static Key? _waitForRelease = null; private static bool _showMenu = false; + private static Key _menuKey = DefaultMenuKey; +#if DEBUG private static bool _showDemo = false; + private static Key _demoKey = DefaultDemoKey; +#endif private static RenderingOptionPointers _renderingOptionPointers; private static Vector2 _viewportSize; private static Vector2 _windowSize; private static Vector2 _mousePos; private static Vector2 _mousePosScalingFactor; private static float _fontScale = 1.0f; - private static Key _menuKey = DefaultMenuKey; - private static Key _demoKey = DefaultDemoKey; private static bool _fontsSubmitted = false; private static NativeArray CustomFonts; From dbbec766edcf7cd75645a29efaae41ec048544ea Mon Sep 17 00:00:00 2001 From: Andrew Opalach Date: Wed, 4 Feb 2026 17:10:35 -0500 Subject: [PATCH 2/3] Improve mouse click blocking --- SharpPluginLoader.Core/Rendering/Renderer.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/SharpPluginLoader.Core/Rendering/Renderer.cs b/SharpPluginLoader.Core/Rendering/Renderer.cs index 01ba479..adef48d 100644 --- a/SharpPluginLoader.Core/Rendering/Renderer.cs +++ b/SharpPluginLoader.Core/Rendering/Renderer.cs @@ -265,14 +265,15 @@ Initializing Renderer with _mouseUpdateHook.Original(m); - // Block mouse clicks if an ImGui window is hovered. Use _lastUpdateHadFocus + // Block mouse1 clicks if an ImGui window is hovered. Use _lastUpdateHadFocus // to more consistently block a click used to unfocus the ImGui window. var anyHovered = ImGui.IsWindowHovered(ImGuiHoveredFlags.AnyWindow); if (anyHovered || _lastUpdateHadFocus) { unsafe { - *(byte*)(m + 0x188) = 0; + *(byte*)(m + 0x108) &= 0xFE; // Combat. + *(byte*)(m + 0x188) &= 0xFE; // Menus. } _lastUpdateHadFocus = false; } From 0860094cba6ba2f067f837933d8bbabab381f5ce Mon Sep 17 00:00:00 2001 From: Andrew Opalach Date: Fri, 6 Feb 2026 19:47:46 -0500 Subject: [PATCH 3/3] Rework mouse handling --- SharpPluginLoader.Core/Rendering/Renderer.cs | 47 ++++++++++++-------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/SharpPluginLoader.Core/Rendering/Renderer.cs b/SharpPluginLoader.Core/Rendering/Renderer.cs index adef48d..48d608e 100644 --- a/SharpPluginLoader.Core/Rendering/Renderer.cs +++ b/SharpPluginLoader.Core/Rendering/Renderer.cs @@ -249,33 +249,44 @@ Initializing Renderer with { _mouseUpdateHook = Hook.Create(sMhMouse.GetVirtualFunction(6), m => { - // Prevent the game from doing any mouse updates if an ImGui window is focused. var anyFocused = ImGui.IsWindowFocused(ImGuiFocusedFlags.AnyWindow); - if (anyFocused) + var anyHovered = ImGui.IsWindowHovered(ImGuiHoveredFlags.AnyWindow); + + if (anyFocused || anyHovered) { - unsafe - { - // Zero mouse delta because it won't get updated. - *(int*)(m + 0xFC) = 0; - *(int*)(m + 0x100) = 0; - } - _lastUpdateHadFocus = true; - return; + // Tell the game to not reset the cursor to the middle of the screen + // when Camera Mouse Controls are on. + // MonsterHunterWorld.exe+4898F0 - cmp [rax+000147A8],r15b(0) + MemoryUtil.GetRef(Gui.SingletonInstance.Instance + 0x147A8) = 0x1; } _mouseUpdateHook.Original(m); - // Block mouse1 clicks if an ImGui window is hovered. Use _lastUpdateHadFocus - // to more consistently block a click used to unfocus the ImGui window. - var anyHovered = ImGui.IsWindowHovered(ImGuiHoveredFlags.AnyWindow); - if (anyHovered || _lastUpdateHadFocus) + if (anyFocused || anyHovered) + { + MemoryUtil.GetRef(m + 0x17C) = 0; // Scroll wheel. + } + + if (anyFocused) + { + // Zero mouse delta used for camera movement. + MemoryUtil.GetRef(m + 0xFC) = 0L; // dX(int), dY(int). + MemoryUtil.GetRef(m + 0x188) = 0x0; // Mouse down menu. + MemoryUtil.GetRef(m + 0x108) = 0x0; // Mouse down combat. + _lastUpdateHadFocus = true; + } + else if (anyHovered || _lastUpdateHadFocus) { - unsafe + // Block mouse1 clicks. Use _lastUpdateHadFocus to more consistently block + // a click used to unfocus the ImGui window. + ref byte downMenu = ref MemoryUtil.GetRef(m + 0x188); + ref byte downCombat = ref MemoryUtil.GetRef(m + 0x108); + if ((downMenu & 0x1) == 0 && (downCombat & 0x1) == 0) // Wait for release. { - *(byte*)(m + 0x108) &= 0xFE; // Combat. - *(byte*)(m + 0x188) &= 0xFE; // Menus. + _lastUpdateHadFocus = false; } - _lastUpdateHadFocus = false; + downMenu &= 0xFE; + downCombat &= 0xFE; } }); }