update clientstructs

- fix broken gamepad sig by delegating to CS
This commit is contained in:
Kaz Wolfe 2025-03-24 21:58:23 -07:00
parent 445fe09181
commit 919ca87bbe
No known key found for this signature in database
GPG key ID: 258813F53A16EBB4
5 changed files with 48 additions and 134 deletions

View file

@ -24,12 +24,6 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver
/// </summary> /// </summary>
public IntPtr ProcessPacketPlayerSetup { get; private set; } public IntPtr ProcessPacketPlayerSetup { get; private set; }
/// <summary>
/// Gets the address of the method which polls the gamepads for data.
/// Called every frame, even when `Enable Gamepad` is off in the settings.
/// </summary>
public IntPtr GamepadPoll { get; private set; }
/// <summary> /// <summary>
/// Scan for and setup any configured address pointers. /// Scan for and setup any configured address pointers.
/// </summary> /// </summary>
@ -43,7 +37,5 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver
// movzx edx, byte ptr [rbx+rsi+1D5E0E0h] KeyboardStateIndexArray // 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.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.KeyboardStateIndexArray = sig.ScanText("0F B6 94 33 ?? ?? ?? ?? 84 D2") + 0x4;
this.GamepadPoll = sig.ScanText("40 55 53 57 41 54 41 57 48 8D AC 24 ?? ?? ?? ?? 48 81 EC ?? ?? ?? ?? 44 0F 29 B4 24"); // unnamed in cs
} }
} }

View file

@ -1,75 +0,0 @@
using System.Runtime.InteropServices;
namespace Dalamud.Game.ClientState.GamePad;
/// <summary>
/// Struct which gets populated by polling the gamepads.
///
/// Has an array of gamepads, among many other things (here not mapped).
/// All we really care about is the final data which the game uses to determine input.
///
/// The size is definitely bigger than only the following fields but I do not know how big.
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public struct GamepadInput
{
/// <summary>
/// Left analogue stick's horizontal value, -99 for left, 99 for right.
/// </summary>
[FieldOffset(0x78)]
public int LeftStickX;
/// <summary>
/// Left analogue stick's vertical value, -99 for down, 99 for up.
/// </summary>
[FieldOffset(0x7C)]
public int LeftStickY;
/// <summary>
/// Right analogue stick's horizontal value, -99 for left, 99 for right.
/// </summary>
[FieldOffset(0x80)]
public int RightStickX;
/// <summary>
/// Right analogue stick's vertical value, -99 for down, 99 for up.
/// </summary>
[FieldOffset(0x84)]
public int RightStickY;
/// <summary>
/// Raw input, set the whole time while a button is held. See <see cref="GamepadButtons"/> for the mapping.
/// </summary>
/// <remarks>
/// This is a bitfield.
/// </remarks>
[FieldOffset(0x88)]
public ushort ButtonsRaw;
/// <summary>
/// Button pressed, set once when the button is pressed. See <see cref="GamepadButtons"/> for the mapping.
/// </summary>
/// <remarks>
/// This is a bitfield.
/// </remarks>
[FieldOffset(0x8C)]
public ushort ButtonsPressed;
/// <summary>
/// Button released input, set once right after the button is not hold anymore. See <see cref="GamepadButtons"/> for the mapping.
/// </summary>
/// <remarks>
/// This is a bitfield.
/// </remarks>
[FieldOffset(0x90)]
public ushort ButtonsReleased;
/// <summary>
/// Repeatedly emits the held button input in fixed intervals. See <see cref="GamepadButtons"/> for the mapping.
/// </summary>
/// <remarks>
/// This is a bitfield.
/// </remarks>
[FieldOffset(0x94)]
public ushort ButtonsRepeat;
}

View file

@ -4,7 +4,8 @@ using Dalamud.Hooking;
using Dalamud.IoC; using Dalamud.IoC;
using Dalamud.IoC.Internal; using Dalamud.IoC.Internal;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.System.Input;
using ImGuiNET; using ImGuiNET;
using Serilog; using Serilog;
@ -23,7 +24,7 @@ namespace Dalamud.Game.ClientState.GamePad;
#pragma warning restore SA1015 #pragma warning restore SA1015
internal unsafe class GamepadState : IInternalDisposableService, IGamepadState internal unsafe class GamepadState : IInternalDisposableService, IGamepadState
{ {
private readonly Hook<ControllerPoll>? gamepadPoll; private readonly Hook<PadDevice.Delegates.Poll>? gamepadPoll;
private bool isDisposed; private bool isDisposed;
@ -35,25 +36,21 @@ internal unsafe class GamepadState : IInternalDisposableService, IGamepadState
[ServiceManager.ServiceConstructor] [ServiceManager.ServiceConstructor]
private GamepadState(ClientState clientState) private GamepadState(ClientState clientState)
{ {
var resolver = clientState.AddressResolver; this.gamepadPoll = Hook<PadDevice.Delegates.Poll>.FromAddress((nint)PadDevice.StaticVirtualTablePointer->Poll, this.GamepadPollDetour);
Log.Verbose($"GamepadPoll address {Util.DescribeAddress(resolver.GamepadPoll)}");
this.gamepadPoll = Hook<ControllerPoll>.FromAddress(resolver.GamepadPoll, this.GamepadPollDetour);
this.gamepadPoll?.Enable(); this.gamepadPoll?.Enable();
} }
private delegate int ControllerPoll(IntPtr controllerInput);
/// <summary> /// <summary>
/// Gets the pointer to the current instance of the GamepadInput struct. /// Gets the pointer to the current instance of the GamepadInput struct.
/// </summary> /// </summary>
public IntPtr GamepadInputAddress { get; private set; } public IntPtr GamepadInputAddress { get; private set; }
/// <inheritdoc/> /// <inheritdoc/>
public Vector2 LeftStick => public Vector2 LeftStick =>
new(this.leftStickX, this.leftStickY); new(this.leftStickX, this.leftStickY);
/// <inheritdoc/> /// <inheritdoc/>
public Vector2 RightStick => public Vector2 RightStick =>
new(this.rightStickX, this.rightStickY); new(this.rightStickX, this.rightStickY);
/// <summary> /// <summary>
@ -61,28 +58,28 @@ internal unsafe class GamepadState : IInternalDisposableService, IGamepadState
/// ///
/// Exposed internally for Debug Data window. /// Exposed internally for Debug Data window.
/// </summary> /// </summary>
internal ushort ButtonsPressed { get; private set; } internal GamepadButtons ButtonsPressed { get; private set; }
/// <summary> /// <summary>
/// Gets raw button bitmask, set the whole time while a button is held. See <see cref="GamepadButtons"/> for the mapping. /// Gets raw button bitmask, set the whole time while a button is held. See <see cref="GamepadButtons"/> for the mapping.
/// ///
/// Exposed internally for Debug Data window. /// Exposed internally for Debug Data window.
/// </summary> /// </summary>
internal ushort ButtonsRaw { get; private set; } internal GamepadButtons ButtonsRaw { get; private set; }
/// <summary> /// <summary>
/// Gets button released bitmask, set once right after the button is not hold anymore. See <see cref="GamepadButtons"/> for the mapping. /// Gets button released bitmask, set once right after the button is not hold anymore. See <see cref="GamepadButtons"/> for the mapping.
/// ///
/// Exposed internally for Debug Data window. /// Exposed internally for Debug Data window.
/// </summary> /// </summary>
internal ushort ButtonsReleased { get; private set; } internal GamepadButtons ButtonsReleased { get; private set; }
/// <summary> /// <summary>
/// Gets button repeat bitmask, emits the held button input in fixed intervals. See <see cref="GamepadButtons"/> for the mapping. /// Gets button repeat bitmask, emits the held button input in fixed intervals. See <see cref="GamepadButtons"/> for the mapping.
/// ///
/// Exposed internally for Debug Data window. /// Exposed internally for Debug Data window.
/// </summary> /// </summary>
internal ushort ButtonsRepeat { get; private set; } internal GamepadButtons ButtonsRepeat { get; private set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether detour should block gamepad input for game. /// Gets or sets a value indicating whether detour should block gamepad input for game.
@ -95,16 +92,16 @@ internal unsafe class GamepadState : IInternalDisposableService, IGamepadState
internal bool NavEnableGamepad { get; set; } internal bool NavEnableGamepad { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public float Pressed(GamepadButtons button) => (this.ButtonsPressed & (ushort)button) > 0 ? 1 : 0; public float Pressed(GamepadButtons button) => (this.ButtonsPressed & button) > 0 ? 1 : 0;
/// <inheritdoc/> /// <inheritdoc/>
public float Repeat(GamepadButtons button) => (this.ButtonsRepeat & (ushort)button) > 0 ? 1 : 0; public float Repeat(GamepadButtons button) => (this.ButtonsRepeat & button) > 0 ? 1 : 0;
/// <inheritdoc/> /// <inheritdoc/>
public float Released(GamepadButtons button) => (this.ButtonsReleased & (ushort)button) > 0 ? 1 : 0; public float Released(GamepadButtons button) => (this.ButtonsReleased & button) > 0 ? 1 : 0;
/// <inheritdoc/> /// <inheritdoc/>
public float Raw(GamepadButtons button) => (this.ButtonsRaw & (ushort)button) > 0 ? 1 : 0; public float Raw(GamepadButtons button) => (this.ButtonsRaw & button) > 0 ? 1 : 0;
/// <summary> /// <summary>
/// Disposes this instance, alongside its hooks. /// Disposes this instance, alongside its hooks.
@ -115,28 +112,28 @@ internal unsafe class GamepadState : IInternalDisposableService, IGamepadState
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
private int GamepadPollDetour(IntPtr gamepadInput) private nint GamepadPollDetour(PadDevice* gamepadInput)
{ {
var original = this.gamepadPoll!.Original(gamepadInput); var original = this.gamepadPoll!.Original(gamepadInput);
try try
{ {
this.GamepadInputAddress = gamepadInput; this.GamepadInputAddress = (nint)gamepadInput;
var input = (GamepadInput*)gamepadInput;
this.leftStickX = input->LeftStickX; this.leftStickX = gamepadInput->GamepadInputData.LeftStickX;
this.leftStickY = input->LeftStickY; this.leftStickY = gamepadInput->GamepadInputData.LeftStickY;
this.rightStickX = input->RightStickX; this.rightStickX = gamepadInput->GamepadInputData.RightStickX;
this.rightStickY = input->RightStickY; this.rightStickY = gamepadInput->GamepadInputData.RightStickY;
this.ButtonsRaw = input->ButtonsRaw; this.ButtonsRaw = (GamepadButtons)gamepadInput->GamepadInputData.Buttons;
this.ButtonsPressed = input->ButtonsPressed; this.ButtonsPressed = (GamepadButtons)gamepadInput->GamepadInputData.ButtonsPressed;
this.ButtonsReleased = input->ButtonsReleased; this.ButtonsReleased = (GamepadButtons)gamepadInput->GamepadInputData.ButtonsReleased;
this.ButtonsRepeat = input->ButtonsRepeat; this.ButtonsRepeat = (GamepadButtons)gamepadInput->GamepadInputData.ButtonsRepeat;
if (this.NavEnableGamepad) if (this.NavEnableGamepad)
{ {
input->LeftStickX = 0; gamepadInput->GamepadInputData.LeftStickX = 0;
input->LeftStickY = 0; gamepadInput->GamepadInputData.LeftStickY = 0;
input->RightStickX = 0; gamepadInput->GamepadInputData.RightStickX = 0;
input->RightStickY = 0; gamepadInput->GamepadInputData.RightStickY = 0;
// NOTE (Chiv) Zeroing `ButtonsRaw` destroys `ButtonPressed`, `ButtonReleased` // NOTE (Chiv) Zeroing `ButtonsRaw` destroys `ButtonPressed`, `ButtonReleased`
// and `ButtonRepeat` as the game uses the RAW input to determine those (apparently). // and `ButtonRepeat` as the game uses the RAW input to determine those (apparently).
@ -153,16 +150,16 @@ internal unsafe class GamepadState : IInternalDisposableService, IGamepadState
// `ButtonPressed` while ImGuiConfigFlags.NavEnableGamepad is set. // `ButtonPressed` while ImGuiConfigFlags.NavEnableGamepad is set.
// This is debatable. // This is debatable.
// ImGui itself does not care either way as it uses the Raw values and does its own state handling. // ImGui itself does not care either way as it uses the Raw values and does its own state handling.
const ushort deletionMask = (ushort)(~GamepadButtons.L2 const GamepadButtonsFlags deletionMask = ~GamepadButtonsFlags.L2
& ~GamepadButtons.R2 & ~GamepadButtonsFlags.R2
& ~GamepadButtons.DpadDown & ~GamepadButtonsFlags.DPadDown
& ~GamepadButtons.DpadLeft & ~GamepadButtonsFlags.DPadLeft
& ~GamepadButtons.DpadUp & ~GamepadButtonsFlags.DPadUp
& ~GamepadButtons.DpadRight); & ~GamepadButtonsFlags.DPadRight;
input->ButtonsRaw &= deletionMask; gamepadInput->GamepadInputData.Buttons &= deletionMask;
input->ButtonsPressed = 0; gamepadInput->GamepadInputData.ButtonsPressed = 0;
input->ButtonsReleased = 0; gamepadInput->GamepadInputData.ButtonsReleased = 0;
input->ButtonsRepeat = 0; gamepadInput->GamepadInputData.ButtonsRepeat = 0;
return 0; return 0;
} }

View file

@ -12,9 +12,9 @@ internal class GamepadWidget : IDataWindowWidget
{ {
/// <inheritdoc/> /// <inheritdoc/>
public string[]? CommandShortcuts { get; init; } = { "gamepad", "controller" }; public string[]? CommandShortcuts { get; init; } = { "gamepad", "controller" };
/// <inheritdoc/> /// <inheritdoc/>
public string DisplayName { get; init; } = "Gamepad"; public string DisplayName { get; init; } = "Gamepad";
/// <inheritdoc/> /// <inheritdoc/>
public bool Ready { get; set; } public bool Ready { get; set; }
@ -42,24 +42,24 @@ internal class GamepadWidget : IDataWindowWidget
this.DrawHelper( this.DrawHelper(
"Buttons Raw", "Buttons Raw",
gamepadState.ButtonsRaw, (uint)gamepadState.ButtonsRaw,
gamepadState.Raw); gamepadState.Raw);
this.DrawHelper( this.DrawHelper(
"Buttons Pressed", "Buttons Pressed",
gamepadState.ButtonsPressed, (uint)gamepadState.ButtonsPressed,
gamepadState.Pressed); gamepadState.Pressed);
this.DrawHelper( this.DrawHelper(
"Buttons Repeat", "Buttons Repeat",
gamepadState.ButtonsRepeat, (uint)gamepadState.ButtonsRepeat,
gamepadState.Repeat); gamepadState.Repeat);
this.DrawHelper( this.DrawHelper(
"Buttons Released", "Buttons Released",
gamepadState.ButtonsReleased, (uint)gamepadState.ButtonsReleased,
gamepadState.Released); gamepadState.Released);
ImGui.Text($"LeftStick {gamepadState.LeftStick}"); ImGui.Text($"LeftStick {gamepadState.LeftStick}");
ImGui.Text($"RightStick {gamepadState.RightStick}"); ImGui.Text($"RightStick {gamepadState.RightStick}");
} }
private void DrawHelper(string text, uint mask, Func<GamepadButtons, float> resolve) private void DrawHelper(string text, uint mask, Func<GamepadButtons, float> resolve)
{ {
ImGui.Text($"{text} {mask:X4}"); ImGui.Text($"{text} {mask:X4}");

@ -1 +1 @@
Subproject commit 3c99b4f8f7f56ee4defd3ee75809c73312359f9e Subproject commit 48076a4ce750c8f008f3bedd04d0bced03147f56