diff --git a/Dalamud/Game/ClientState/ClientState.cs b/Dalamud/Game/ClientState/ClientState.cs index 0aed29e98..ca2572fc3 100644 --- a/Dalamud/Game/ClientState/ClientState.cs +++ b/Dalamud/Game/ClientState/ClientState.cs @@ -80,6 +80,11 @@ namespace Dalamud.Game.ClientState /// public JobGauges JobGauges; + /// + /// Provides access to the keypress state of keyboard keys in game. + /// + public KeyState KeyState; + /// /// Set up client state access. /// @@ -98,6 +103,8 @@ namespace Dalamud.Game.ClientState this.JobGauges = new JobGauges(Address); + this.KeyState = new KeyState(Address, scanner.Module.BaseAddress); + Log.Verbose("SetupTerritoryType address {SetupTerritoryType}", Address.SetupTerritoryType); this.setupTerritoryTypeHook = new Hook(Address.SetupTerritoryType, diff --git a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs index 8d6bab2de..fa29d53b9 100644 --- a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs +++ b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs @@ -8,6 +8,7 @@ namespace Dalamud.Game.ClientState public IntPtr ActorTable { get; private set; } public IntPtr LocalContentId { get; private set; } public IntPtr JobGaugeData { get; private set; } + public IntPtr KeyboardState { get; private set; } // Functions public IntPtr SetupTerritoryType { get; private set; } @@ -18,6 +19,9 @@ namespace Dalamud.Game.ClientState JobGaugeData = sig.GetStaticAddressFromSig("E8 ?? ?? ?? ?? FF C6 48 8D 5B 0C", 0xB9) + 0x10; SetupTerritoryType = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B F9 66 89 91 ?? ?? ?? ??"); + + // This resolves to a fixed offset only, without the base address added in, so GetStaticAddressFromSig() can't be used + KeyboardState = sig.ScanText("48 8D 0C 85 ?? ?? ?? ?? 8B 04 31 85 C2 0F 85") + 0x4; } } } diff --git a/Dalamud/Game/ClientState/KeyState.cs b/Dalamud/Game/ClientState/KeyState.cs new file mode 100644 index 000000000..5ae92c20c --- /dev/null +++ b/Dalamud/Game/ClientState/KeyState.cs @@ -0,0 +1,62 @@ +using Serilog; +using System; +using System.Runtime.InteropServices; + +namespace Dalamud.Game.ClientState +{ + /// + /// Wrapper around the game keystate buffer, which contains the pressed state for + /// all keyboard keys, indexed by virtual vkCode + /// + public class KeyState + { + private IntPtr bufferBase; + + // The array is accessed in a way that this limit doesn't appear to exist + // but there is other state data past this point, and keys beyond here aren't + // generally valid for most things anyway + private const int MaxKeyCodeIndex = 0xA0; + + public KeyState(ClientStateAddressResolver addressResolver, IntPtr moduleBaseAddress) + { + this.bufferBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardState); + + Log.Verbose($"Keyboard state buffer address {this.bufferBase}"); + } + + /// + /// Get or set the keypressed state for a given vkCode. + /// + /// The virtual key to change. + /// Whether the specified key is currently pressed. + public bool this[int vkCode] + { + get + { + if (vkCode< 0 || vkCode > MaxKeyCodeIndex) + throw new ArgumentException($"Keycode state only appears to be valid up to {MaxKeyCodeIndex}"); + + return (Marshal.ReadInt32(this.bufferBase + (4 * vkCode)) != 0); + } + + set + { + if (vkCode < 0 || vkCode > MaxKeyCodeIndex) + throw new ArgumentException($"Keycode state only appears to be valid up to {MaxKeyCodeIndex}"); + + Marshal.WriteInt32(this.bufferBase + (4 * vkCode), value ? 1 : 0); + } + } + + /// + /// Clears the pressed state for all keys. + /// + public void ClearAll() + { + for (var i = 0; i < MaxKeyCodeIndex; i++) + { + Marshal.WriteInt32(this.bufferBase + (i * 4), 0); + } + } + } +} diff --git a/Dalamud/Interface/InterfaceManager.cs b/Dalamud/Interface/InterfaceManager.cs index 2252c5d53..54b3548ac 100644 --- a/Dalamud/Interface/InterfaceManager.cs +++ b/Dalamud/Interface/InterfaceManager.cs @@ -155,6 +155,7 @@ namespace Dalamud.Interface this.scene = new RawDX11Scene(swapChain); this.scene.ImGuiIniPath = Path.Combine(Path.GetDirectoryName(this.dalamud.StartInfo.ConfigurationPath), "dalamudUI.ini"); this.scene.OnBuildUI += Display; + this.scene.OnNewInputFrame += OnNewInputFrame; var fontPathJp = Path.Combine(this.dalamud.StartInfo.WorkingDirectory, "UIRes", "NotoSansCJKjp-Medium.otf"); ImGui.GetIO().Fonts.AddFontFromFileTTF(fontPathJp, 17.0f, null, ImGui.GetIO().Fonts.GetGlyphRangesJapanese()); @@ -222,6 +223,20 @@ namespace Dalamud.Interface return this.setCursorHook.Original(hCursor); } + private void OnNewInputFrame() + { + // fix for keys in game getting stuck, if you were holding a game key (like run) + // and then clicked on an imgui textbox - imgui would swallow the keyup event, + // so the game would think the key remained pressed continuously until you left + // imgui and pressed and released the key again + if (ImGui.GetIO().WantTextInput) + { + this.dalamud.ClientState.KeyState.ClearAll(); + } + + // TODO: mouse state? + } + private void Display() { // this is more or less part of what reshade/etc do to avoid having to manually diff --git a/lib/ImGuiScene b/lib/ImGuiScene index dda60b7fa..d0c03cd31 160000 --- a/lib/ImGuiScene +++ b/lib/ImGuiScene @@ -1 +1 @@ -Subproject commit dda60b7faa1b12225d165d9e5d239e7e66648aae +Subproject commit d0c03cd31dac7a3eede52a467ef81def69f87ef9