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..48d608e 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; @@ -247,14 +249,119 @@ 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) - return; + var anyFocused = ImGui.IsWindowFocused(ImGuiFocusedFlags.AnyWindow); + var anyHovered = ImGui.IsWindowHovered(ImGuiHoveredFlags.AnyWindow); + + if (anyFocused || anyHovered) + { + // 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); + + 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) + { + // 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. + { + _lastUpdateHadFocus = false; + } + downMenu &= 0xFE; + downCombat &= 0xFE; + } + }); + } + + 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 +377,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 +395,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 +456,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 +588,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;