From 00f3a82ada91625e0fe22eb039e8d926d1103913 Mon Sep 17 00:00:00 2001
From: Vicki Pfau <[EMAIL REDACTED]>
Date: Fri, 25 Apr 2025 17:35:25 -0700
Subject: [PATCH] Joystick: Add new GIP driver to replace old Xbox One wired
driver
This new driver is based on official documentation released by Microsoft in
September, though it still lacks several important features, notably the
Security handshake for wireless dongles and audio support. It is, however, more
reliable and extensible than the old driver.
---
VisualC-GDK/SDL/SDL.vcxproj | 1 +
VisualC-GDK/SDL/SDL.vcxproj.filters | 1 +
VisualC/SDL/SDL.vcxproj | 1 +
VisualC/SDL/SDL.vcxproj.filters | 3 +
Xcode/SDL/SDL.xcodeproj/project.pbxproj | 4 +
.../java/org/libsdl/app/HIDDeviceManager.java | 4 +
include/SDL3/SDL_hints.h | 35 +
src/hidapi/libusb/hid.c | 4 +
src/joystick/hidapi/SDL_hidapi_gip.c | 2405 +++++++++++++++++
src/joystick/hidapi/SDL_hidapijoystick.c | 7 +
src/joystick/hidapi/SDL_hidapijoystick_c.h | 2 +
src/joystick/usb_ids.h | 4 +
12 files changed, 2471 insertions(+)
create mode 100644 src/joystick/hidapi/SDL_hidapi_gip.c
diff --git a/VisualC-GDK/SDL/SDL.vcxproj b/VisualC-GDK/SDL/SDL.vcxproj
index 8c9b2db0f3986..6cd9632bc8375 100644
--- a/VisualC-GDK/SDL/SDL.vcxproj
+++ b/VisualC-GDK/SDL/SDL.vcxproj
@@ -715,6 +715,7 @@
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_8bitdo.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_combined.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_gamecube.c" />
+ <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_gip.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_luna.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_ps3.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_ps4.c" />
diff --git a/VisualC-GDK/SDL/SDL.vcxproj.filters b/VisualC-GDK/SDL/SDL.vcxproj.filters
index 26f228826c966..7aa7cba153133 100644
--- a/VisualC-GDK/SDL/SDL.vcxproj.filters
+++ b/VisualC-GDK/SDL/SDL.vcxproj.filters
@@ -66,6 +66,7 @@
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_8bitdo.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_combined.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_gamecube.c" />
+ <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_gip.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_luna.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_ps3.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_ps4.c" />
diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj
index c0a4d8643665d..4f6c5d95bcf2e 100644
--- a/VisualC/SDL/SDL.vcxproj
+++ b/VisualC/SDL/SDL.vcxproj
@@ -605,6 +605,7 @@
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_8bitdo.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_combined.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_gamecube.c" />
+ <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_gip.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_luna.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_ps3.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_ps4.c" />
diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters
index 2583c9f3791ea..e4ef251f9f7d8 100644
--- a/VisualC/SDL/SDL.vcxproj.filters
+++ b/VisualC/SDL/SDL.vcxproj.filters
@@ -1191,6 +1191,9 @@
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_gamecube.c">
<Filter>joystick\hidapi</Filter>
</ClCompile>
+ <ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_gip.c">
+ <Filter>joystick\hidapi</Filter>
+ </ClCompile>
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_luna.c">
<Filter>joystick\hidapi</Filter>
</ClCompile>
diff --git a/Xcode/SDL/SDL.xcodeproj/project.pbxproj b/Xcode/SDL/SDL.xcodeproj/project.pbxproj
index 8913d5a532bb0..a98fc779a7a90 100644
--- a/Xcode/SDL/SDL.xcodeproj/project.pbxproj
+++ b/Xcode/SDL/SDL.xcodeproj/project.pbxproj
@@ -438,6 +438,7 @@
F3B439532C935C2C00792030 /* SDL_posixprocess.c in Sources */ = {isa = PBXBuildFile; fileRef = F3B439522C935C2C00792030 /* SDL_posixprocess.c */; };
F3B439562C937DAB00792030 /* SDL_process.c in Sources */ = {isa = PBXBuildFile; fileRef = F3B439542C937DAB00792030 /* SDL_process.c */; };
F3B439572C937DAB00792030 /* SDL_sysprocess.h in Headers */ = {isa = PBXBuildFile; fileRef = F3B439552C937DAB00792030 /* SDL_sysprocess.h */; };
+ F3B6B80A2DC3EA54004954FD /* SDL_hidapi_gip.c in Sources */ = {isa = PBXBuildFile; fileRef = F3B6B8092DC3EA54004954FD /* SDL_hidapi_gip.c */; };
F3C1BD752D1F1A3000846529 /* SDL_tray_utils.c in Sources */ = {isa = PBXBuildFile; fileRef = F3C1BD742D1F1A3000846529 /* SDL_tray_utils.c */; };
F3C1BD762D1F1A3000846529 /* SDL_tray_utils.h in Headers */ = {isa = PBXBuildFile; fileRef = F3C1BD732D1F1A3000846529 /* SDL_tray_utils.h */; };
F3C2CB222C5DDDB2004D7998 /* SDL_categories_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F3C2CB202C5DDDB2004D7998 /* SDL_categories_c.h */; };
@@ -1012,6 +1013,7 @@
F3B439522C935C2C00792030 /* SDL_posixprocess.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_posixprocess.c; sourceTree = "<group>"; };
F3B439542C937DAB00792030 /* SDL_process.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_process.c; sourceTree = "<group>"; };
F3B439552C937DAB00792030 /* SDL_sysprocess.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_sysprocess.h; sourceTree = "<group>"; };
+ F3B6B8092DC3EA54004954FD /* SDL_hidapi_gip.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_gip.c; sourceTree = "<group>"; };
F3C1BD732D1F1A3000846529 /* SDL_tray_utils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_tray_utils.h; sourceTree = "<group>"; };
F3C1BD742D1F1A3000846529 /* SDL_tray_utils.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_tray_utils.c; sourceTree = "<group>"; };
F3C2CB202C5DDDB2004D7998 /* SDL_categories_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_categories_c.h; sourceTree = "<group>"; };
@@ -1928,6 +1930,7 @@
F3395BA72D9A5971007246C8 /* SDL_hidapi_8bitdo.c */,
F32305FE28939F6400E66D30 /* SDL_hidapi_combined.c */,
A7D8A7C923E2513E00DCD162 /* SDL_hidapi_gamecube.c */,
+ F3B6B8092DC3EA54004954FD /* SDL_hidapi_gip.c */,
89E5801D2D03602200DAF6D3 /* SDL_hidapi_lg4ff.c */,
F3F07D59269640160074468B /* SDL_hidapi_luna.c */,
F3FD042C2C9B755700824C4C /* SDL_hidapi_nintendo.h */,
@@ -3056,6 +3059,7 @@
A7D8BA5B23E2514400DCD162 /* SDL_shaders_gles2.c in Sources */,
A7D8B14023E2514200DCD162 /* SDL_blit_1.c in Sources */,
A7D8BBDB23E2574800DCD162 /* SDL_uikitmetalview.m in Sources */,
+ F3B6B80A2DC3EA54004954FD /* SDL_hidapi_gip.c in Sources */,
A7D8BB1523E2514500DCD162 /* SDL_mouse.c in Sources */,
F395C19C2569C68F00942BFF /* SDL_iokitjoystick.c in Sources */,
A7D8B4B223E2514300DCD162 /* SDL_sysjoystick.c in Sources */,
diff --git a/android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java b/android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java
index 642a97676def2..f7c56c44e2288 100644
--- a/android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java
+++ b/android-project/app/src/main/java/org/libsdl/app/HIDDeviceManager.java
@@ -288,9 +288,13 @@ private boolean isXboxOneController(UsbDevice usbDevice, UsbInterface usbInterfa
0x1532, // Razer Wildcat
0x20d6, // PowerA
0x24c6, // PowerA
+ 0x294b, // Snakebyte
0x2dc8, // 8BitDo
0x2e24, // Hyperkin
+ 0x2e95, // SCUF
+ 0x3285, // Nacon
0x3537, // GameSir
+ 0x366c, // ByoWave
};
if (usbInterface.getId() == 0 &&
diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h
index 9f1685de76993..38acc8901bd35 100644
--- a/include/SDL3/SDL_hints.h
+++ b/include/SDL3/SDL_hints.h
@@ -1949,6 +1949,41 @@ extern "C" {
*/
#define SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE_HOME_LED "SDL_JOYSTICK_HIDAPI_XBOX_ONE_HOME_LED"
+/**
+ * A variable controlling whether the new HIDAPI driver for wired Xbox One
+ * (GIP) controllers should be used.
+ *
+ * The variable can be set to the following values:
+ *
+ * - "0": HIDAPI driver is not used.
+ * - "1": HIDAPI driver is used.
+ *
+ * The default is the value of SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE.
+ *
+ * This hint should be set before initializing joysticks and gamepads.
+ *
+ * \since This hint is available since SDL 3.4.0.
+ */
+#define SDL_HINT_JOYSTICK_HIDAPI_GIP "SDL_JOYSTICK_HIDAPI_GIP"
+
+/**
+ * A variable controlling whether the new HIDAPI driver for wired Xbox One
+ * (GIP) controllers should reset the controller if it can't get the
+ * metadata from the controller.
+ *
+ * The variable can be set to the following values:
+ *
+ * - "0": Assume this is a generic controller.
+ * - "1": Reset the controller to get metadata.
+ *
+ * By default the controller is not reset.
+ *
+ * This hint should be set before initializing joysticks and gamepads.
+ *
+ * \since This hint is available since SDL 3.4.0.
+ */
+#define SDL_HINT_JOYSTICK_HIDAPI_GIP_RESET_FOR_METADATA "SDL_JOYSTICK_HIDAPI_GIP_RESET_FOR_METADATA"
+
/**
* A variable controlling whether IOKit should be used for controller
* handling.
diff --git a/src/hidapi/libusb/hid.c b/src/hidapi/libusb/hid.c
index f4b1ccb39294b..13cd1c7903deb 100644
--- a/src/hidapi/libusb/hid.c
+++ b/src/hidapi/libusb/hid.c
@@ -879,9 +879,13 @@ static int is_xboxone(unsigned short vendor_id, const struct libusb_interface_de
0x1532, /* Razer Wildcat */
0x20d6, /* PowerA */
0x24c6, /* PowerA */
+ 0x294b, /* Snakebyte */
0x2dc8, /* 8BitDo */
0x2e24, /* Hyperkin */
+ 0x2e95, /* SCUF */
+ 0x3285, /* Nacon */
0x3537, /* GameSir */
+ 0x366c, /* ByoWave */
};
if (intf_desc->bInterfaceNumber == 0 &&
diff --git a/src/joystick/hidapi/SDL_hidapi_gip.c b/src/joystick/hidapi/SDL_hidapi_gip.c
new file mode 100644
index 0000000000000..76bf5684ac8f4
--- /dev/null
+++ b/src/joystick/hidapi/SDL_hidapi_gip.c
@@ -0,0 +1,2405 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+*/
+#include "SDL_internal.h"
+
+#ifdef SDL_JOYSTICK_HIDAPI
+
+#include "../SDL_sysjoystick.h"
+#include "SDL_hidapijoystick_c.h"
+#include "SDL_hidapi_rumble.h"
+
+#ifdef SDL_JOYSTICK_HIDAPI_GIP
+
+// Define this if you want to log all packets from the controller
+//#define DEBUG_XBOX_PROTOCOL
+
+#define MAX_MESSAGE_LENGTH 0x4000
+
+#define GIP_DATA_CLASS_COMMAND (0u << 5)
+#define GIP_DATA_CLASS_LOW_LATENCY (1u << 5)
+#define GIP_DATA_CLASS_STANDARD_LATENCY (2u << 5)
+#define GIP_DATA_CLASS_AUDIO (3u << 5)
+
+#define GIP_DATA_CLASS_SHIFT 5
+#define GIP_DATA_CLASS_MASK (7u << 5)
+
+/* System messages */
+#define GIP_CMD_PROTO_CONTROL 0x01
+#define GIP_CMD_HELLO_DEVICE 0x02
+#define GIP_CMD_STATUS_DEVICE 0x03
+#define GIP_CMD_METADATA 0x04
+#define GIP_CMD_SET_DEVICE_STATE 0x05
+#define GIP_CMD_SECURITY 0x06
+#define GIP_CMD_GUIDE_BUTTON 0x07
+#define GIP_CMD_AUDIO_CONTROL 0x08
+#define GIP_CMD_LED 0x0a
+#define GIP_CMD_HID_REPORT 0x0b
+#define GIP_CMD_FIRMWARE 0x0c
+#define GIP_CMD_EXTENDED 0x1e
+#define GIP_CMD_DEBUG 0x1f
+#define GIP_AUDIO_DATA 0x60
+
+/* Vendor messages */
+#define GIP_CMD_DIRECT_MOTOR 0x09
+#define GIP_CMD_INITIAL_REPORTS_REQUEST 0x0a
+#define GIP_CMD_SET_APPLICATION_MEMORY 0x0b
+#define GIP_LL_INPUT_REPORT 0x20
+#define GIP_LL_STATIC_CONFIGURATION 0x21
+#define GIP_LL_BUTTON_INFO_REPORT 0x22
+#define GIP_LL_OVERFLOW_INPUT_REPORT 0x26
+
+/* Undocumented Elite 2 vendor messages */
+#define GIP_CMD_RAW_REPORT 0x0c
+#define GIP_CMD_GUIDE_COLOR 0x0e
+#define GIP_SL_ELITE_CONFIG 0x4d
+
+#define GIP_FLAG_FRAGMENT (1u << 7)
+#define GIP_FLAG_INIT_FRAG (1u << 6)
+#define GIP_FLAG_SYSTEM (1u << 5)
+#define GIP_FLAG_ACME (1u << 4)
+#define GIP_FLAG_ATTACHMENT_MASK 0x7
+
+#define GIP_AUDIO_FORMAT_NULL 0
+#define GIP_AUDIO_FORMAT_8000HZ_1CH 1
+#define GIP_AUDIO_FORMAT_8000HZ_2CH 2
+#define GIP_AUDIO_FORMAT_12000HZ_1CH 3
+#define GIP_AUDIO_FORMAT_12000HZ_2CH 4
+#define GIP_AUDIO_FORMAT_16000HZ_1CH 5
+#define GIP_AUDIO_FORMAT_16000HZ_2CH 6
+#define GIP_AUDIO_FORMAT_20000HZ_1CH 7
+#define GIP_AUDIO_FORMAT_20000HZ_2CH 8
+#define GIP_AUDIO_FORMAT_24000HZ_1CH 9
+#define GIP_AUDIO_FORMAT_24000HZ_2CH 10
+#define GIP_AUDIO_FORMAT_32000HZ_1CH 11
+#define GIP_AUDIO_FORMAT_32000HZ_2CH 12
+#define GIP_AUDIO_FORMAT_40000HZ_1CH 13
+#define GIP_AUDIO_FORMAT_40000HZ_2CH 14
+#define GIP_AUDIO_FORMAT_48000HZ_1CH 15
+#define GIP_AUDIO_FORMAT_48000HZ_2CH 16
+#define GIP_AUDIO_FORMAT_48000HZ_6CH 32
+#define GIP_AUDIO_FORMAT_48000HZ_8CH 33
+
+/* Protocol Control constants */
+#define GIP_CONTROL_CODE_ACK 0
+#define GIP_CONTROL_CODE_NACK 1 /* obsolete */
+#define GIP_CONTROL_CODE_UNK 2 /* obsolete */
+#define GIP_CONTROL_CODE_AB 3 /* obsolete */
+#define GIP_CONTROL_CODE_MPER 4 /* obsolete */
+#define GIP_CONTROL_CODE_STOP 5 /* obsolete */
+#define GIP_CONTROL_CODE_START 6 /* obsolete */
+#define GIP_CONTROL_CODE_ERR 7 /* obsolete */
+
+/* Status Device constants */
+#define GIP_POWER_LEVEL_OFF 0
+#define GIP_POWER_LEVEL_STANDBY 1 /* obsolete */
+#define GIP_POWER_LEVEL_FULL 2
+
+#define GIP_NOT_CHARGING 0
+#define GIP_CHARGING 1
+#define GIP_CHARGE_ERROR 2
+
+#define GIP_BATTERY_ABSENT 0
+#define GIP_BATTERY_STANDARD 1
+#define GIP_BATTERY_RECHARGEABLE 2
+
+#define GIP_BATTERY_CRITICAL 0
+#define GIP_BATTERY_LOW 1
+#define GIP_BATTERY_MEDIUM 2
+#define GIP_BATTERY_FULL 3
+
+#define GIP_EVENT_FAULT 0x0002
+
+#define GIP_FAULT_UNKNOWN 0
+#define GIP_FAULT_HARD 1
+#define GIP_FAULT_NMI 2
+#define GIP_FAULT_SVC 3
+#define GIP_FAULT_PEND_SV 4
+#define GIP_FAULT_SMART_PTR 5
+#define GIP_FAULT_MCU 6
+#define GIP_FAULT_BUS 7
+#define GIP_FAULT_USAGE 8
+#define GIP_FAULT_RADIO_HANG 9
+#define GIP_FAULT_WATCHDOG 10
+#define GIP_FAULT_LINK_STALL 11
+#define GIP_FAULT_ASSERTION 12
+
+/* Metadata constants */
+#define GIP_MESSAGE_FLAG_BIG_ENDIAN (1u << 0)
+#define GIP_MESSAGE_FLAG_RELIABLE (1u << 1)
+#define GIP_MESSAGE_FLAG_SEQUENCED (1u << 2)
+#define GIP_MESSAGE_FLAG_DOWNSTREAM (1u << 3)
+#define GIP_MESSAGE_FLAG_UPSTREAM (1u << 4)
+#define GIP_MESSAGE_FLAG_DS_REQUEST_RESPONSE (1u << 5)
+
+#define GIP_DATA_TYPE_CUSTOM 1
+#define GIP_DATA_TYPE_AUDIO 2
+#define GIP_DATA_TYPE_SECURITY 3
+#define GIP_DATA_TYPE_GIP 4
+
+/* Set Device State constants */
+#define GIP_STATE_START 0
+#define GIP_STATE_STOP 1
+#define GIP_STATE_STANDBY 2 /* obsolete */
+#define GIP_STATE_FULL_POWER 3
+#define GIP_STATE_OFF 4
+#define GIP_STATE_QUIESCE 5
+#define GIP_STATE_UNK6 6
+#define GIP_STATE_RESET 7
+
+/* Guide Button Status constants */
+#define GIP_LED_GUIDE 0
+#define GIP_LID_IR 1 /* deprecated */
+
+#define GIP_LED_GUIDE_OFF 0
+#define GIP_LED_GUIDE_ON 1
+#define GIP_LED_GUIDE_FAST_BLINK 2
+#define GIP_LED_GUIDE_SLOW_BLINK 3
+#define GIP_LED_GUIDE_CHARGING_BLINK 4
+#define GIP_LED_GUIDE_RAMP_TO_LEVEL 0xd
+
+#define GIP_LED_IR_OFF 0
+#define GIP_LED_IR_ON_100MS 1
+#define GIP_LED_IR_PATTERN 4
+
+/* Direct Motor Command constants */
+#define GIP_MOTOR_RIGHT_VIBRATION (1u << 0)
+#define GIP_MOTOR_LEFT_VIBRATION (1u << 1)
+#define GIP_MOTOR_RIGHT_IMPULSE (1u << 2)
+#define GIP_MOTOR_LEFT_IMPULSE (1u << 3)
+#define GIP_MOTOR_ALL 0xF
+
+/* Extended Comand constants */
+#define GIP_EXTCMD_GET_CAPABILITIES 0x00
+#define GIP_EXTCMD_GET_TELEMETRY_DATA 0x01
+#define GIP_EXTCMD_GET_SERIAL_NUMBER 0x04
+
+#define GIP_EXTENDED_STATUS_OK 0
+#define GIP_EXTENDED_STATUS_NOT_SUPPORTED 1
+#define GIP_EXTENDED_STATUS_NOT_READY 2
+#define GIP_EXTENDED_STATUS_ACCESS_DENIED 3
+#define GIP_EXTENDED_STATUS_FAILED 4
+
+/* Internal constants, not part of protocol */
+#define GIP_HELLO_TIMEOUT 2000
+
+#define GIP_DEFAULT_IN_SYSTEM_MESSAGES 0x5e
+#define GIP_DEFAULT_OUT_SYSTEM_MESSAGES 0x72
+
+#define GIP_FEATURE_CONSOLE_FUNCTION_MAP (1u << 0)
+#define GIP_FEATURE_CONSOLE_FUNCTION_MAP_OVERFLOW (1u << 1)
+#define GIP_FEATURE_ELITE_BUTTONS (1u << 2)
+#define GIP_FEATURE_DYNAMIC_LATENCY_INPUT (1u << 3)
+#define GIP_FEATURE_SECURITY_OPT_OUT (1u << 4)
+#define GIP_FEATURE_MOTOR_CONTROL (1u << 5)
+#define GIP_FEATURE_GUIDE_COLOR (1u << 6)
+
+#define GIP_QUIRK_NO_HELLO (1u << 0)
+#define GIP_QUIRK_BROKEN_METADATA (1u << 1)
+
+typedef enum
+{
+ GIP_METADATA_NONE = 0,
+ GIP_METADATA_GOT = 1,
+ GIP_METADATA_FAKED = 2,
+ GIP_METADATA_PENDING = 3,
+} GIP_MetadataStatus;
+
+#ifndef VK_LWIN
+#define VK_LWIN 0x5b
+#endif
+
+typedef enum
+{
+ GIP_TYPE_UNKNOWN = -1,
+ GIP_TYPE_GAMEPAD = 0,
+ GIP_TYPE_ARCADE_STICK = 1,
+ GIP_TYPE_WHEEL = 2,
+ GIP_TYPE_FLIGHT_STICK = 3,
+ GIP_TYPE_NAVIGATION_CONTROLLER = 4,
+} GIP_DeviceType;
+
+typedef enum
+{
+ GIP_RUMBLE_STATE_IDLE,
+ GIP_RUMBLE_STATE_QUEUED,
+ GIP_RUMBLE_STATE_BUSY,
+} GIP_RumbleState;
+
+typedef enum
+{
+ GIP_PADDLES_UNKNOWN,
+ GIP_PADDLES_XBE1,
+ GIP_PADDLES_XBE2_RAW,
+ GIP_PADDLES_XBE2,
+} GIP_PaddleFormat;
+
+/* These come across the wire as little-endian, so let's store them in-memory as such so we can memcmp */
+#define MAKE_GUID(NAME, A, B, C, D0, D1, D2, D3, D4, D5, D6, D7) \
+ static const GUID NAME = { SDL_Swap32LE(A), SDL_Swap16LE(B), SDL_Swap16LE(C), { D0, D1, D2, D3, D4, D5, D6, D7 } }
+
+typedef struct GUID
+{
+ Uint32 a;
+ Uint16 b;
+ Uint16 c;
+ Uint8 d[8];
+} GUID;
+SDL_COMPILE_TIME_ASSERT(GUID, sizeof(GUID) == 16);
+
+MAKE_GUID(GUID_ArcadeStick, 0x332054cc, 0xa34b, 0x41d5, 0xa3, 0x4a, 0xa6, 0xa6, 0x71, 0x1e, 0xc4, 0xb3);
+MAKE_GUID(GUID_DynamicLatencyInput, 0x87f2e56b, 0xc3bb, 0x49b1, 0x82, 0x65, 0xff, 0xff, 0xf3, 0x77, 0x99, 0xee);
+MAKE_GUID(GUID_IConsoleFunctionMap_InputReport, 0xecddd2fe, 0xd387, 0x4294, 0xbd, 0x96, 0x1a, 0x71, 0x2e, 0x3d, 0xc7, 0x7d);
+MAKE_GUID(GUID_IConsoleFunctionMap_OverflowInputReport, 0x137d4bd0, 0x9347, 0x4472, 0xaa, 0x26, 0x8c, 0x34, 0xa0, 0x8f, 0xf9, 0xbd);
+MAKE_GUID(GUID_IController, 0x9776ff56, 0x9bfd, 0x4581, 0xad, 0x45, 0xb6, 0x45, 0xbb, 0xa5, 0x26, 0xd6);
+MAKE_GUID(GUID_IDevAuthPCOptOut, 0x7a34ce77, 0x7de2, 0x45c6, 0x8c, 0xa4, 0x00, 0x42, 0xc0, 0x8b, 0xd9, 0x4a);
+MAKE_GUID(GUID_IEliteButtons, 0x37d19ff7, 0xb5c6, 0x49d1, 0xa7, 0x5e, 0x03, 0xb2, 0x4b, 0xef, 0x8c, 0x89);
+MAKE_GUID(GUID_IGamepad, 0x082e402c, 0x07df, 0x45e1, 0xa5, 0xab, 0xa3, 0x12, 0x7a, 0xf1, 0x97, 0xb5);
+MAKE_GUID(GUID_NavigationController, 0xb8f31fe7, 0x7386, 0x40e9, 0xa9, 0xf8, 0x2f, 0x21, 0x26, 0x3a, 0xcf, 0xb7);
+MAKE_GUID(GUID_Wheel, 0x646979cf, 0x6b71, 0x4e96, 0x8d, 0xf9, 0x59, 0xe3, 0x98, 0xd7, 0x42, 0x0c);
+
+/*
+ * The following GUIDs are observed, but the exact meanings aren't known, so
+ * for now we document them but don't use them anywhere.
+ *
+ * MAKE_GUID(GUID_GamepadEmu, 0xe2e5f1bc, 0xa6e6, 0x41a2, 0x8f, 0x43, 0x33, 0xcf, 0xa2, 0x51, 0x09, 0x81);
+ * MAKE_GUID(GUID_IAudioOnly, 0x92844cd1, 0xf7c8, 0x49ef, 0x97, 0x77, 0x46, 0x7d, 0xa7, 0x08, 0xad, 0x10);
+ * MAKE_GUID(GUID_IControllerProfileModeState, 0xf758dc66, 0x022c, 0x48b8, 0xa4, 0xf6, 0x45, 0x7b, 0xa8, 0x0e, 0x2a, 0x5b);
+ * MAKE_GUID(GUID_ICustomAudio, 0x63fd9cc9, 0x94ee, 0x4b5d, 0x9c, 0x4d, 0x8b, 0x86, 0x4c, 0x14, 0x9c, 0xac);
+ * MAKE_GUID(GUID_IExtendedDeviceFlags, 0x34ad9b1e, 0x36ad, 0x4fb5, 0x8a, 0xc7, 0x17, 0x23, 0x4c, 0x9f, 0x54, 0x6f);
+ * MAKE_GUID(GUID_IHeadset, 0xbc25d1a3, 0xc24e, 0x4992, 0x9d, 0xda, 0xef, 0x4f, 0x12, 0x3e, 0xf5, 0xdc);
+ * MAKE_GUID(GUID_IProgrammableGamepad, 0x31c1034d, 0xb5b7, 0x4551, 0x98, 0x13, 0x87, 0x69, 0xd4, 0xa0, 0xe4, 0xf9);
+ * MAKE_GUID(GUID_IVirtualDevice, 0xdfd26825, 0x110a, 0x4e94, 0xb9, 0x37, 0xb2, 0x7c, 0xe4, 0x7b, 0x25, 0x40);
+ * MAKE_GUID(GUID_OnlineDevAuth, 0x632b1fd1, 0xa3e9, 0x44f9, 0x84, 0x20, 0x5c, 0xe3, 0x44, 0xa0, 0x64, 0x04);
+ */
+
+static const int GIP_DataClassMtu[8] = { 64, 64, 64, 2048, 0, 0, 0, 0 };
+
+typedef struct GIP_Quirks
+{
+ Uint16 vendor_id;
+ Uint16 product_id;
+ Uint32 added_features;
+ Uint32 filtered_features;
+ Uint32 quirks;
+ Uint32 extra_in_system[8];
+ Uint32 extra_out_system[8];
+ GIP_DeviceType device_type;
+} GIP_Quirks;
+
+static const GIP_Quirks quirks[] = {
+ { USB_VENDOR_MICROSOFT, USB_PRODUCT_XBOX_ONE_ELITE_SERIES_1,
+ .added_features = GIP_FEATURE_ELITE_BUTTONS,
+ .filtered_features = GIP_FEATURE_CONSOLE_FUNCTION_MAP },
+
+ { USB_VENDOR_MICROSOFT, USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2,
+ .added_features = GIP_FEATURE_ELITE_BUTTONS | GIP_FEATURE_DYNAMIC_LATENCY_INPUT | GIP_FEATURE_CONSOLE_FUNCTION_MAP | GIP_FEATURE_GUIDE_COLOR,
+ .extra_in_system = { 1 << GIP_CMD_FIRMWARE },
+ .extra_out_system = { 1 << GIP_CMD_FIRMWARE } },
+
+ { USB_VENDOR_MICROSOFT, USB_PRODUCT_XBOX_SERIES_X,
+ .added_features = GIP_FEATURE_DYNAMIC_LATENCY_INPUT },
+
+ { USB_VENDOR_PDP, USB_PRODUCT_PDP_ROCK_CANDY,
+ .quirks = GIP_QUIRK_NO_HELLO },
+
+ { USB_VENDOR_POWERA, USB_PRODUCT_BDA_XB1_FIGHTPAD,
+ .filtered_features = GIP_FEATURE_MOTOR_CONTROL },
+
+ /*
+ * The controller can attempt to resend the metadata too quickly, but has
+ * bugs handling reliable message handling if things get out of sync.
+ * However, since it just lets us bypass the metadata exchange, let's just
+ * do that instead of having an unreliable init
+ */
+ { USB_VENDOR_POWERA, USB_PRODUCT_BDA_XB1_CLASSIC,
+ .quirks = GIP_QUIRK_BROKEN_METADATA },
+
+ { USB_VENDOR_RAZER, USB_PRODUCT_RAZER_ATROX,
+ .filtered_features = GIP_FEATURE_MOTOR_CONTROL,
+ .device_type = GIP_TYPE_ARCADE_STICK },
+
+ {0},
+};
+
+typedef struct GIP_Header
+{
+ Uint8 message_type;
+ Uint8 flags;
+ Uint8 sequence_id;
+ Uint64 length;
+} GIP_Header;
+
+typedef struct GIP_AudioFormat
+{
+ Uint8 inbound;
+ Uint8 outbound;
+} GIP_AudioFormat;
+
+typedef struct GIP_DeviceMetadata
+{
+ Uint8 num_audio_formats;
+ Uint8 num_preferred_types;
+ Uint8 num_supported_interfaces;
+ Uint8 hid_descriptor_size;
+
+ Uint32 in_system_messages[8];
+ Uint32 out_system_messages[8];
+
+ GIP_AudioFormat *audio_formats;
+ char **preferred_types;
+ GUID *supported_interfaces;
+ Uint8 *hid_descriptor;
+
+ GIP_DeviceType device_type;
+} GIP_DeviceMetadata;
+
+typedef struct GIP_MessageMetadata
+{
+ Uint8 type;
+ Uint16 length;
+ Uint16 data_type;
+ Uint32 flags;
+ Uint16 period;
+ Uint16 persistence_timeout;
+} GIP_MessageMetadata;
+
+typedef struct GIP_Metadata
+{
+ Uint16 version_major;
+ Uint16 version_minor;
+
+ GIP_DeviceMetadata device;
+
+ Uint8 num_messages;
+ GIP_MessageMetadata *message_metadata;
+} GIP_Metadata;
+
+typedef struct GIP_Device
+{
+ SDL_HIDAPI_Device *device;
+
+ Uint8 fragment_message;
+ Uint16 total_length;
+ Uint8 *fragment_data;
+ Uint32 fragment_offset;
+ Uint64 fragment_timer;
+ int fragment_retries;
+
+ Uint16 firmware_major_version;
+ Uint16 firmware_minor_version;
+
+ Uint64 hello_deadline;
+ bool got_hello;
+
+ GIP_MetadataStatus got_metadata;
+ Uint64 metadata_next;
+ int metadata_retries;
+ GIP_Metadata metadata;
+
+ Uint8 seq_system;
+ Uint8 seq_security;
+ Uint8 seq_extended;
+ Uint8 seq_audio;
+ Uint8 seq_vendor;
+
+ int device_state;
+
+ GIP_RumbleState rumble_state;
+ Uint64 rumble_time;
+ bool rumble_pending;
+ Uint8 left_impulse_level;
+ Uint8 right_impulse_level;
+ Uint8 left_vibration_level;
+ Uint8 right_vibration_level;
+
+ Uint8 last_input[64];
+
+ bool reset_for_metadata;
+ GIP_DeviceType device_type;
+ GIP_PaddleFormat paddle_format;
+ Uint32 features;
+ Uint32 quirks;
+ Uint8 share_button_idx;
+ Uint8 paddle_idx;
+ int paddle_offset;
+} GIP_Device;
+
+typedef struct GIP_HelloDevice
+{
+ Uint64 device_id;
+ Uint16 vendor_id;
+ Uint16 product_id;
+ Uint16 firmware_major_version;
+ Uint16 firmware_minor_version;
+ Uint16 firmware_build_version;
+ Uint16 firmware_revision;
+ Uint8 hardware_major_version;
+ Uint8 hardware_minor_version;
+ Uint8 rf_proto_major_version;
+ Uint8 rf_proto_minor_version;
+ Uint8 security_major_version;
+ Uint8 security_minor_version;
+ Uint8 gip_major_version;
+ Uint8 gip_minor_version;
+} GIP_HelloDevice;
+
+typedef struct GIP_Status
+{
+ int power_level;
+ int charge;
+ int battery_type;
+ int battery_level;
+} GIP_Status;
+
+typedef struct GIP_StatusEvent
+{
+ Uint16 event_type;
+ Uint32 fault_tag;
+ Uint32 fault_address;
+} GIP_StatusEvent;
+
+typedef struct GIP_ExtendedStatus
+{
+ GIP_Status base;
+ bool device_active;
+
+ int num_events;
+ GIP_StatusEvent events[5];
+} GIP_ExtendedStatus;
+
+typedef struct GIP_DirectMotor
+{
+ Uint8 motor_bitmap;
+ Uint8 left_impulse_level;
+ Uint8 right_impulse_level;
+ Uint8 left_vibration_level;
+ Uint8 right_vibration_level;
+ Uint8 duration;
+ Uint8 delay;
+ Uint8 repeat;
+} GIP_DirectMotor;
+
+typedef struct GIP_InitialReportsRequest
+{
+ Uint8 type;
+ Uint8 data[2];
+} GIP_InitialReportsRequest;
+
+static bool GIP_SetMetadataDefaults(GIP_Device *device);
+
+static int GIP_DecodeLength(Uint64 *length, const Uint8 *bytes, int num_bytes)
+{
+ *length = 0;
+ int offset;
+
+ for (offset = 0; offset < num_bytes; offset++) {
+ Uint8 byte = bytes[offset];
+ *length |= (byte & 0x7full) << (offset * 7);
+ if (!(byte & 0x80)) {
+ offset++;
+ break;
+ }
+ }
+ return offset;
+}
+
+static int GIP_EncodeLength(Uint64 length, Uint8 *bytes, int num_bytes)
+{
+ int offset;
+
+ for (offset = 0; offset < num_bytes; offset++) {
+ Uint8 byte = length & 0x7f;
+ length >>= 7;
+ if (length) {
+ byte |= 0x80;
+ }
+ bytes[offset] = byte;
+ if (!length) {
+ offset++;
+ break;
+ }
+ }
+ return offset;
+}
+
+static bool GIP_SupportsSystemMessage(GIP_Device *device, Uint8 command, bool upstream)
+{
+ if (upstream) {
+ return device->metadata.device.in_system_messages[command >> 5] & (1u << command);
+ } else {
+ return device->metadata.device.out_system_messages[command >> 5] & (1u << command);
+ }
+}
+
+static bool GIP_SupportsVendorMessage(GIP_Device *device, Uint8 command, bool upstream)
+{
+ size_t i;
+ for (i = 0; i < device->metadata.num_messages; i++) {
+ GIP_MessageMetadata *metadata = &device->metadata.message_metadata[i];
+ if (metadata->type != command) {
+ continue;
+ }
+ if (upstream) {
+ return metadata->flags & GIP_MESSAGE_FLAG_UPSTREAM;
+ } else {
+ return metadata->flags & GIP_MESSAGE_FLAG_DOWNSTREAM;
+ }
+ }
+ return false;
+}
+
+static Uint8 GIP_SequenceNext(GIP_Device *device, Uint8 command, bool system)
+{
+ Uint8 seq;
+ if (system) {
+ switch (command) {
+ case GIP_CMD_SECURITY:
+ seq = device->seq_security++;
+ if (!seq) {
+ seq = device->seq_security++;
+ }
+ break;
+ case GIP_CMD_EXTENDED:
+ seq = device->seq_extended++;
+ if (!seq) {
+ seq = device->seq_extended++;
+ }
+ break;
+ case GIP_AUDIO_DATA:
+ seq = device->seq_audio++;
+ if (!seq) {
+ seq = device->seq_audio++;
+ }
+ break;
+ default:
+ seq = device->seq_system++;
+ if (!seq) {
+ seq = device->seq_system++;
+ }
+ break;
+ }
+ } else {
+ seq = device->seq_vendor++;
+ if (!seq) {
+ seq = device->seq_vendor++;
+ }
+ }
+ return seq;
+}
+
+static void GIP_HandleQuirks(GIP_Device *device)
+{
+ size_t i, j;
+ for (i = 0; quirks[i].vendor_id; i++) {
+ if (quirks[i].vendor_id != device->device->vendor_id) {
+ continue;
+ }
+ if (quirks[i].product_id != device->device->product_id) {
+ continue;
+ }
+ device->features |= quirks[i].added_features;
+ device->features &= ~quirks[i].filtered_features;
+ device->quirks = quirks[i].quirks;
+ device->device_type = quirks[i].device_type;
+
+ for (j = 0; j < 8; ++j) {
+ device->metadata.device.in_system_messages[j] |= quirks[i].extra_in_system[j];
+ device->metadata.device.out_system_messages[j] |= quirks[i].extra_out_system[j];
+ }
+ break;
+ }
+}
+
+static bool GIP_SendRawMessage(
+ GIP_Device *device,
+ Uint8 message_type,
+ Uint8 flags,
+ Uint8 seq,
+ const Uint8 *bytes,
+ int num_bytes,
+ bool async,
+ SDL_HIDAPI_RumbleSentCallback callback,
+ void *userdata)
+{
+ Uint8 buffer[2054] = { message_type, flags, seq };
+ int offset = 3;
+
+ if (num_bytes < 0) {
+ SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "GIP: Invalid message length %d", num_bytes);
+ return false;
+ }
+
+ if (num_bytes > GIP_DataClassMtu[message_type >> GIP_DATA_CLASS_SHIFT]) {
+ SDL_LogError(SDL_LOG_CATEGORY_INPUT,
+ "Attempted to send a message that requires fragmenting, which is not yet supported.");
+ return false;
+ }
+
+ offset += GIP_EncodeLength(num_bytes, &buffer[offset], sizeof(buffer) - offset);
+
+ if (num_bytes > 0) {
+ SDL_memcpy(&buffer[offset], bytes, num_bytes);
+ }
+ num_bytes += offset;
+#ifdef DEBUG_XBOX_PROTOCOL
+ HIDAPI_DumpPacket("GIP sending message: size = %d", buffer, num_bytes);
+#endif
+
+ if (async) {
+ if (!SDL_HIDAPI_LockRumble()) {
+ return false;
+ }
+
+ return SDL_HIDAPI_SendRumbleWithCallbackAndUnlock(device->device, buffer, num_bytes, callback, userdata) == num_bytes;
+ } else {
+ return SDL_hid_write(device->device->dev, buffer, num_bytes) == num_bytes;
+ }
+}
+
+static bool GIP_SendSystemMessage(
+ GIP_Device *device,
+ Uint8 message_type,
+ Uint8 flags,
+ const Uint8 *bytes,
+ int num_bytes)
+{
+ return GIP_SendRawMessage(device,
+ message_type,
+ GIP_FLAG_SYSTEM | flags,
+ GIP_SequenceNext(device, message_type, true),
+ bytes,
+ num_bytes,
+ true,
+ NULL,
+
(Patch may be truncated, please check the link at the top of this post.)