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