diff --git a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs
index 334bf0198..11b77e2df 100644
--- a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs
+++ b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs
@@ -47,6 +47,11 @@ namespace Dalamud.Game.ClientState
///
public IntPtr KeyboardState { get; private set; }
+ ///
+ /// Gets the address of the keyboard state index array which translates the VK enumeration to the key state.
+ ///
+ public IntPtr KeyboardStateIndexArray { get; private set; }
+
///
/// Gets the address of the target manager.
///
@@ -93,9 +98,11 @@ namespace Dalamud.Game.ClientState
this.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. lea rcx, ds:1DB9F74h[rax*4]
+ // These resolve to fixed offsets only, without the base address added in, so GetStaticAddressFromSig() can't be used.
+ // lea rcx, ds:1DB9F74h[rax*4] KeyboardState
+ // movzx edx, byte ptr [rbx+rsi+1D5E0E0h] KeyboardStateIndexArray
this.KeyboardState = sig.ScanText("48 8D 0C 85 ?? ?? ?? ?? 8B 04 31 85 C2 0F 85") + 0x4;
+ this.KeyboardStateIndexArray = sig.ScanText("0F B6 94 33 ?? ?? ?? ?? 84 D2") + 0x4;
this.ConditionFlags = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? BA ?? ?? ?? ?? E8 ?? ?? ?? ?? B0 01 48 83 C4 30");
diff --git a/Dalamud/Game/ClientState/Keys/KeyState.cs b/Dalamud/Game/ClientState/Keys/KeyState.cs
index 974f4b01f..0c8956006 100644
--- a/Dalamud/Game/ClientState/Keys/KeyState.cs
+++ b/Dalamud/Game/ClientState/Keys/KeyState.cs
@@ -1,4 +1,5 @@
using System;
+using System.Linq;
using System.Runtime.InteropServices;
using Dalamud.IoC;
@@ -10,6 +11,15 @@ namespace Dalamud.Game.ClientState.Keys
///
/// Wrapper around the game keystate buffer, which contains the pressed state for all keyboard keys, indexed by virtual vkCode.
///
+ ///
+ /// The stored key state is actually a combination field, however the below ephemeral states are consumed each frame. Setting
+ /// the value may be mildly useful, however retrieving the value is largely pointless. In testing, it wasn't possible without
+ /// setting the statue manually.
+ /// index & 0 = key pressed.
+ /// index & 1 = key down (ephemeral).
+ /// index & 2 = key up (ephemeral).
+ /// index & 3 = short key press (ephemeral).
+ ///
[PluginInterface]
[InterfaceVersion("1.0")]
public class KeyState
@@ -18,7 +28,9 @@ namespace Dalamud.Game.ClientState.Keys
// 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;
- private IntPtr bufferBase;
+ private readonly IntPtr bufferBase;
+ private readonly IntPtr indexBase;
+ private VirtualKey[] validVirtualKeyCache = null;
///
/// Initializes a new instance of the class.
@@ -29,40 +41,81 @@ namespace Dalamud.Game.ClientState.Keys
var moduleBaseAddress = Service.Get().Module.BaseAddress;
this.bufferBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardState);
+ this.indexBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardStateIndexArray);
Log.Verbose($"Keyboard state buffer address 0x{this.bufferBase.ToInt64():X}");
}
///
- /// Get or set the keypressed state for a given vkCode.
+ /// Get or set the key-pressed state for a given vkCode.
///
/// The virtual key to change.
/// Whether the specified key is currently pressed.
- public bool this[int vkCode]
+ /// If the vkCode is not valid. Refer to or .
+ /// If the set value is non-zero.
+ public unsafe bool this[int vkCode]
{
- get
- {
- if (vkCode < 0 || vkCode > MaxKeyCodeIndex)
- throw new ArgumentException($"Keycode state only appears to be valid up to {MaxKeyCodeIndex}");
+ get => this.GetRawValue(vkCode) != 0;
+ set => this.SetRawValue(vkCode, value ? 1 : 0);
+ }
- 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);
- }
+ ///
+ public bool this[VirtualKey vkCode]
+ {
+ get => this[(int)vkCode];
+ set => this[(int)vkCode] = value;
}
///
- /// Get or set the keypressed state for a given VirtualKey enum.
+ /// Gets the value in the index array.
///
- /// The virtual key to change.
- /// Whether the specified key is currently pressed.
- public bool this[VirtualKey vk] => this[(int)vk];
+ /// The virtual key to change.
+ /// The raw value stored in the index array.
+ /// If the vkCode is not valid. Refer to or .
+ public int GetRawValue(int vkCode)
+ => this.GetRefValue(vkCode);
+
+ ///
+ public int GetRawValue(VirtualKey vkCode)
+ => this.GetRawValue((int)vkCode);
+
+ ///
+ /// Sets the value in the index array.
+ ///
+ /// The virtual key to change.
+ /// The raw value to set in the index array.
+ /// If the vkCode is not valid. Refer to or .
+ /// If the set value is non-zero.
+ public void SetRawValue(int vkCode, int value)
+ {
+ if (value != 0)
+ throw new ArgumentOutOfRangeException(nameof(value), "Dalamud does not support pressing keys, only preventing them via zero or False. If you have a valid use-case for this, please contact the dev team.");
+
+ this.GetRefValue(vkCode) = value;
+ }
+
+ ///
+ public void SetRawValue(VirtualKey vkCode, int value)
+ => this.SetRawValue((int)vkCode, value);
+
+ ///
+ /// Gets a value indicating whether the given VirtualKey code is regarded as valid input by the game.
+ ///
+ /// Virtual key code.
+ /// If the code is valid.
+ public bool IsVirtualKeyValid(int vkCode)
+ => vkCode > 0 && vkCode < MaxKeyCodeIndex && this.ConvertVirtualKey(vkCode) != 0;
+
+ ///
+ public bool IsVirtualKeyValid(VirtualKey vkCode)
+ => this.IsVirtualKeyValid((int)vkCode);
+
+ ///
+ /// Gets an array of virtual keys the game considers valid input.
+ ///
+ /// An array of valid virtual keys.
+ public VirtualKey[] GetValidVirtualKeys()
+ => this.validVirtualKeyCache ??= Enum.GetValues().Where(vk => this.IsVirtualKeyValid(vk)).ToArray();
///
/// Clears the pressed state for all keys.
@@ -71,8 +124,40 @@ namespace Dalamud.Game.ClientState.Keys
{
for (var i = 0; i < MaxKeyCodeIndex; i++)
{
- Marshal.WriteInt32(this.bufferBase + (i * 4), 0);
+ this.GetRefValue(i) = 0;
}
}
+
+ ///
+ /// Converts a virtual key into the equivalent value that the game uses.
+ /// Valid values are non-zero.
+ ///
+ /// Virtual key.
+ /// Converted value.
+ private unsafe byte ConvertVirtualKey(int vkCode)
+ {
+ if (vkCode <= 0 || vkCode >= 240)
+ return 0;
+
+ return *(byte*)(this.indexBase + vkCode);
+ }
+
+ ///
+ /// Gets the raw value from the key state array.
+ ///
+ /// Virtual key code.
+ /// A reference to the indexed array.
+ private unsafe ref int GetRefValue(int vkCode)
+ {
+ if (vkCode < 0 || vkCode > MaxKeyCodeIndex)
+ throw new ArgumentException($"Keycode state is only valid up to {MaxKeyCodeIndex}");
+
+ vkCode = this.ConvertVirtualKey(vkCode);
+
+ if (vkCode == 0)
+ throw new ArgumentException($"Keycode state is only valid for certain values. Reference GetValidVirtualKeys for help.");
+
+ return ref *(int*)(this.bufferBase + (4 * vkCode));
+ }
}
}
diff --git a/Dalamud/Interface/Colors/ImGuiColors.cs b/Dalamud/Interface/Colors/ImGuiColors.cs
index b540c288a..0e6ac69f5 100644
--- a/Dalamud/Interface/Colors/ImGuiColors.cs
+++ b/Dalamud/Interface/Colors/ImGuiColors.cs
@@ -15,17 +15,17 @@ namespace Dalamud.Interface.Colors
///
/// Gets grey used in dalamud.
///
- public static Vector4 DalamudGrey { get; } = new Vector4(0.70f, 0.70f, 0.70f, 1.00f);
+ public static Vector4 DalamudGrey { get; } = new Vector4(0.7f, 0.7f, 0.7f, 1f);
///
/// Gets grey used in dalamud.
///
- public static Vector4 DalamudGrey2 { get; } = new Vector4(0.7f, 0.7f, 0.7f, 1.0f);
+ public static Vector4 DalamudGrey2 { get; } = new Vector4(0.7f, 0.7f, 0.7f, 1f);
///
/// Gets grey used in dalamud.
///
- public static Vector4 DalamudGrey3 { get; } = new Vector4(0.5f, 0.5f, 0.5f, 1.0f);
+ public static Vector4 DalamudGrey3 { get; } = new Vector4(0.5f, 0.5f, 0.5f, 1f);
///
/// Gets white used in dalamud.
@@ -45,16 +45,16 @@ namespace Dalamud.Interface.Colors
///
/// Gets tank blue (UIColor37).
///
- public static Vector4 TankBlue { get; } = new Vector4(0, 0.6f, 1, 1);
+ public static Vector4 TankBlue { get; } = new Vector4(0f, 0.6f, 1f, 1f);
///
/// Gets healer green (UIColor504).
///
- public static Vector4 HealerGreen { get; } = new Vector4(0, 0.8f, 0.1333333f, 1);
+ public static Vector4 HealerGreen { get; } = new Vector4(0f, 0.8f, 0.1333333f, 1f);
///
/// Gets dps red (UIColor545).
///
- public static Vector4 DPSRed { get; } = new Vector4(0.7058824f, 0, 0, 1);
+ public static Vector4 DPSRed { get; } = new Vector4(0.7058824f, 0f, 0f, 1f);
}
}
diff --git a/Dalamud/Interface/Internal/Windows/DataWindow.cs b/Dalamud/Interface/Internal/Windows/DataWindow.cs
index a831511b1..d4146953f 100644
--- a/Dalamud/Interface/Internal/Windows/DataWindow.cs
+++ b/Dalamud/Interface/Internal/Windows/DataWindow.cs
@@ -13,6 +13,7 @@ using Dalamud.Game.ClientState.GamePad;
using Dalamud.Game.ClientState.JobGauge;
using Dalamud.Game.ClientState.JobGauge.Enums;
using Dalamud.Game.ClientState.JobGauge.Types;
+using Dalamud.Game.ClientState.Keys;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
@@ -22,6 +23,7 @@ using Dalamud.Game.Gui;
using Dalamud.Game.Gui.FlyText;
using Dalamud.Game.Gui.Toast;
using Dalamud.Game.Text;
+using Dalamud.Interface.Colors;
using Dalamud.Interface.Internal.Notifications;
using Dalamud.Interface.Windowing;
using Dalamud.Memory;
@@ -131,6 +133,7 @@ namespace Dalamud.Interface.Internal.Windows
FlyText,
ImGui,
Tex,
+ KeyState,
Gamepad,
}
@@ -288,6 +291,10 @@ namespace Dalamud.Interface.Internal.Windows
this.DrawTex();
break;
+ case DataKind.KeyState:
+ this.DrawKeyState();
+ break;
+
case DataKind.Gamepad:
this.DrawGamepad();
break;
@@ -1150,6 +1157,32 @@ namespace Dalamud.Interface.Internal.Windows
}
}
+ private void DrawKeyState()
+ {
+ var keyState = Service.Get();
+
+ ImGui.Columns(4);
+
+ var i = 0;
+ foreach (var vkCode in keyState.GetValidVirtualKeys())
+ {
+ var code = (int)vkCode;
+ var value = keyState[code];
+
+ ImGui.PushStyleColor(ImGuiCol.Text, value ? ImGuiColors.HealerGreen : ImGuiColors.DPSRed);
+
+ ImGui.Text($"{vkCode} ({code})");
+
+ ImGui.PopStyleColor();
+
+ i++;
+ if (i % 24 == 0)
+ ImGui.NextColumn();
+ }
+
+ ImGui.Columns(1);
+ }
+
private void DrawGamepad()
{
var gamepadState = Service.Get();