diff --git a/VisualC-GDK/SDL/SDL.vcxproj b/VisualC-GDK/SDL/SDL.vcxproj
index 8c9b2db0f3..6cd9632bc8 100644
--- a/VisualC-GDK/SDL/SDL.vcxproj
+++ b/VisualC-GDK/SDL/SDL.vcxproj
@@ -715,6 +715,7 @@
+
diff --git a/VisualC-GDK/SDL/SDL.vcxproj.filters b/VisualC-GDK/SDL/SDL.vcxproj.filters
index 26f228826c..7aa7cba153 100644
--- a/VisualC-GDK/SDL/SDL.vcxproj.filters
+++ b/VisualC-GDK/SDL/SDL.vcxproj.filters
@@ -66,6 +66,7 @@
+
diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj
index c0a4d86436..4f6c5d95bc 100644
--- a/VisualC/SDL/SDL.vcxproj
+++ b/VisualC/SDL/SDL.vcxproj
@@ -605,6 +605,7 @@
+
diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters
index 2583c9f379..e4ef251f9f 100644
--- a/VisualC/SDL/SDL.vcxproj.filters
+++ b/VisualC/SDL/SDL.vcxproj.filters
@@ -1191,6 +1191,9 @@
joystick\hidapi
+
+ joystick\hidapi
+
joystick\hidapi
diff --git a/Xcode/SDL/SDL.xcodeproj/project.pbxproj b/Xcode/SDL/SDL.xcodeproj/project.pbxproj
index 8913d5a532..a98fc779a7 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 = ""; };
F3B439542C937DAB00792030 /* SDL_process.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_process.c; sourceTree = ""; };
F3B439552C937DAB00792030 /* SDL_sysprocess.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_sysprocess.h; sourceTree = ""; };
+ F3B6B8092DC3EA54004954FD /* SDL_hidapi_gip.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_gip.c; sourceTree = ""; };
F3C1BD732D1F1A3000846529 /* SDL_tray_utils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_tray_utils.h; sourceTree = ""; };
F3C1BD742D1F1A3000846529 /* SDL_tray_utils.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_tray_utils.c; sourceTree = ""; };
F3C2CB202C5DDDB2004D7998 /* SDL_categories_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_categories_c.h; sourceTree = ""; };
@@ -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 642a97676d..f7c56c44e2 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 @@ public class HIDDeviceManager {
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 9f1685de76..38acc8901b 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 f4b1ccb392..13cd1c7903 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 0000000000..76bf5684ac
--- /dev/null
+++ b/src/joystick/hidapi/SDL_hidapi_gip.c
@@ -0,0 +1,2405 @@
+/*
+ Simple DirectMedia Layer
+ Copyright (C) 1997-2025 Sam Lantinga
+
+ 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,
+ NULL);
+}
+
+static bool GIP_SendVendorMessage(
+ GIP_Device *device,
+ Uint8 message_type,
+ Uint8 flags,
+ const Uint8 *bytes,
+ int num_bytes)
+{
+ return GIP_SendRawMessage(device,
+ message_type,
+ flags,
+ GIP_SequenceNext(device, message_type, false),
+ bytes,
+ num_bytes,
+ true,
+ NULL,
+ NULL);
+}
+
+static void GIP_MetadataFree(GIP_Metadata *metadata)
+{
+ if (metadata->device.audio_formats) {
+ SDL_free(metadata->device.audio_formats);
+ }
+ if (metadata->device.preferred_types) {
+ int i;
+ for (i = 0; i < metadata->device.num_preferred_types; i++) {
+ if (metadata->device.preferred_types[i]) {
+ SDL_free(metadata->device.preferred_types[i]);
+ }
+ }
+ SDL_free(metadata->device.preferred_types);
+ }
+ if (metadata->device.supported_interfaces) {
+ SDL_free(metadata->device.supported_interfaces);
+ }
+ if (metadata->device.hid_descriptor) {
+ SDL_free(metadata->device.hid_descriptor);
+ }
+
+ if (metadata->message_metadata) {
+ SDL_free(metadata->message_metadata);
+ }
+ SDL_memset(metadata, 0, sizeof(*metadata));
+}
+
+static bool GIP_ParseDeviceMetadata(GIP_Metadata *metadata, const Uint8 *bytes, int num_bytes, int* offset)
+{
+ GIP_DeviceMetadata *device = &metadata->device;
+ int buffer_offset;
+ int count;
+ int length;
+ int i;
+
+ bytes = &bytes[*offset];
+ num_bytes -= *offset;
+ if (num_bytes < 16) {
+ return false;
+ }
+
+ length = bytes[0];
+ length |= bytes[1] << 8;
+ if (num_bytes < length) {
+ return false;
+ }
+
+ /* Skip supported firmware versions for now */
+
+ buffer_offset = bytes[4];
+ buffer_offset |= bytes[5] << 8;
+ if (buffer_offset >= length) {
+ return false;
+ }
+ if (buffer_offset > 0) {
+ device->num_audio_formats = bytes[buffer_offset];
+ if (buffer_offset + device->num_audio_formats + 1 > length) {
+ return false;
+ }
+ device->audio_formats = SDL_malloc(device->num_audio_formats);
+ SDL_memcpy(device->audio_formats, &bytes[buffer_offset + 1], device->num_audio_formats);
+ }
+
+ buffer_offset = bytes[6];
+ buffer_offset |= bytes[7] << 8;
+ if (buffer_offset >= length) {
+ return false;
+ }
+ if (buffer_offset > 0) {
+ count = bytes[buffer_offset];
+ if (buffer_offset + count + 1 > length) {
+ return false;
+ }
+
+ for (i = 0; i < count; i++) {
+ Uint8 message = bytes[buffer_offset + 1 + i];
+ device->in_system_messages[message >> 5] |= 1u << (message & 0x1F);
+ }
+ }
+
+ buffer_offset = bytes[8];
+ buffer_offset |= bytes[9] << 8;
+ if (buffer_offset >= length) {
+ return false;
+ }
+ if (buffer_offset > 0) {
+ count = bytes[buffer_offset];
+ if (buffer_offset + count + 1 > length) {
+ return false;
+ }
+
+ for (i = 0; i < count; i++) {
+ Uint8 message = bytes[buffer_offset + 1 + i];
+ device->out_system_messages[message >> 5] |= 1u << (message & 0x1F);
+ }
+ }
+
+ buffer_offset = bytes[10];
+ buffer_offset |= bytes[11] << 8;
+ if (buffer_offset >= length) {
+ return false;
+ }
+ if (buffer_offset > 0) {
+ device->num_preferred_types = bytes[buffer_offset];
+ device->preferred_types = SDL_calloc(device->num_preferred_types, sizeof(char*));
+ buffer_offset++;
+ for (i = 0; i < device->num_preferred_types; i++) {
+ if (buffer_offset + 2 >= length) {
+ return false;
+ }
+
+ count = bytes[buffer_offset];
+ count |= bytes[buffer_offset];
+ buffer_offset += 2;
+ if (buffer_offset + count > length) {
+ return false;
+ }
+
+ device->preferred_types[i] = SDL_calloc(count + 1, sizeof(char));
+ SDL_memcpy(device->preferred_types[i], &bytes[buffer_offset], count);
+ buffer_offset += count;
+ }
+ }
+
+ buffer_offset = bytes[12];
+ buffer_offset |= bytes[13] << 8;
+ if (buffer_offset >= length) {
+ return false;
+ }
+ if (buffer_offset > 0) {
+ device->num_supported_interfaces = bytes[buffer_offset];
+ if (buffer_offset + 1 + (Sint32) (device->num_supported_interfaces * sizeof(GUID)) > length) {
+ return false;
+ }
+ device->supported_interfaces = SDL_calloc(device->num_supported_interfaces, sizeof(GUID));
+ SDL_memcpy(device->supported_interfaces,
+ &bytes[buffer_offset + 1],
+ sizeof(GUID) * device->num_supported_interfaces);
+ }
+
+ if (metadata->version_major > 1 || metadata->version_minor >= 1) {
+ /* HID descriptor support added in metadata version 1.1 */
+ buffer_offset = bytes[14];
+ buffer_offset |= bytes[15] << 8;
+ if (buffer_offset >= length) {
+ return false;
+ }
+ if (buffer_offset > 0) {
+ device->hid_descriptor_size = bytes[buffer_offset];
+ if (buffer_offset + 1 + device->hid_descriptor_size > length) {
+ return false;
+ }
+ device->hid_descriptor = SDL_malloc(device->hid_descriptor_size);
+ SDL_memcpy(device->hid_descriptor, &bytes[buffer_offset + 1], device->hid_descriptor_size);
+ }
+ }
+
+ *offset += length;
+ return true;
+}
+
+static bool GIP_ParseMessageMetadata(GIP_MessageMetadata *metadata, const Uint8 *bytes, int num_bytes, int* offset)
+{
+ Uint16 length;
+
+ bytes = &bytes[*offset];
+ num_bytes -= *offset;
+
+ if (num_bytes < 2) {
+ return false;
+ }
+ length = bytes[0];
+ length |= bytes[1] << 8;
+ if (num_bytes < length) {
+ return false;
+ }
+
+ if (length < 15) {
+ return false;
+ }
+
+ metadata->type = bytes[2];
+ metadata->length = bytes[3];
+ metadata->length |= bytes[4] << 8;
+ metadata->data_type = bytes[5];
+ metadata->data_type |= bytes[6] << 8;
+ metadata->flags = bytes[7];
+ metadata->flags |= bytes[8] << 8;
+ metadata->flags |= bytes[9] << 16;
+ metadata->flags |= bytes[10] << 24;
+ metadata->period = bytes[11];
+ metadata->period |= bytes[12] << 8;
+ metadata->persistence_timeout = bytes[13];
+ metadata->persistence_timeout |= bytes[14] << 8;
+
+#ifdef DEBUG_XBOX_PROTOCOL
+ SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
+ "GIP: Supported vendor message type %02x of length %d",
+ metadata->type,
+ metadata->length);
+#endif
+
+ *offset += length;
+ return true;
+}
+
+bool GIP_ParseMetadata(GIP_Metadata *metadata, const Uint8* bytes, int num_bytes)
+{
+ int header_size;
+ int metadata_size;
+ int offset = 0;
+ int i;
+
+ if (num_bytes < 16) {
+ return false;
+ }
+
+#ifdef DEBUG_XBOX_PROTOCOL
+ HIDAPI_DumpPacket("GIP received metadata: size = %d", bytes, num_bytes);
+#endif
+
+ header_size = bytes[0];
+ header_size |= bytes[1] << 8;
+ if (num_bytes < header_size || header_size < 16) {
+ return false;
+ }
+ metadata->version_major = bytes[2];
+ metadata->version_major |= bytes[3] << 8;
+ metadata->version_minor = bytes[4];
+ metadata->version_minor |= bytes[5] << 8;
+ /* Middle bytes are reserved */
+ metadata_size = bytes[14];
+ metadata_size |= bytes[15] << 8;
+
+ if (num_bytes < metadata_size || metadata_size < header_size) {
+ return false;
+ }
+ offset = header_size;
+
+ if (!GIP_ParseDeviceMetadata(metadata, bytes, num_bytes, &offset)) {
+ goto err;
+ }
+
+ if (offset >= num_bytes) {
+ goto err;
+ }
+ metadata->num_messages = bytes[offset];
+ offset++;
+ if (metadata->num_messages > 0) {
+ metadata->message_metadata = SDL_calloc(metadata->num_messages, sizeof(*metadata->message_metadata));
+ for (i = 0; i < metadata->num_messages; i++) {
+ if (!GIP_ParseMessageMetadata(&metadata->message_metadata[i], bytes, num_bytes, &offset)) {
+ goto err;
+ }
+ }
+ }
+
+ return true;
+
+err:
+ GIP_MetadataFree(metadata);
+ return false;
+}
+
+static bool GIP_Acknowledge(
+ GIP_Device *device,
+ const GIP_Header *header,
+ Uint32 fragment_offset,
+ Uint16 bytes_remaining)
+{
+ Uint8 buffer[] = {
+ GIP_CONTROL_CODE_ACK,
+ header->message_type,
+ header->flags & GIP_FLAG_SYSTEM,
+ (Uint8) fragment_offset,
+ (Uint8) (fragment_offset >> 8),
+ (Uint8) (fragment_offset >> 16),
+ fragment_offset >> 24,
+ (Uint8) bytes_remaining,
+ bytes_remaining >> 8,
+ };
+
+ return GIP_SendRawMessage(device,
+ GIP_CMD_PROTO_CONTROL,
+ GIP_FLAG_SYSTEM | (header->flags & GIP_FLAG_ATTACHMENT_MASK),
+ header->sequence_id,
+ buffer,
+ sizeof(buffer),
+ false,
+ NULL,
+ NULL);
+}
+static bool GIP_FragmentFailed(GIP_Device *device, const GIP_Header *header) {
+ device->fragment_retries++;
+ if (device->fragment_retries > 8) {
+ if (device->fragment_data) {
+ SDL_free(device->fragment_data);
+ device->fragment_data = NULL;
+ }
+ device->fragment_message = 0;
+ }
+ return GIP_Acknowledge(device,
+ header,
+ device->fragment_offset,
+ (Uint16) (device->total_length - device->fragment_offset));
+}
+
+static bool GIP_EnableEliteButtons(GIP_Device *device) {
+ if (device->paddle_format == GIP_PADDLES_XBE2_RAW ||
+ (device->firmware_major_version != 4 && device->firmware_minor_version < 17))
+ {
+ /*
+ * The meaning of this packet is unknown and not documented, but it's
+ * needed for the Elite 2 controller to send raw reports
+ */
+ static const Uint8 enable_raw_report[] = { 7, 0 };
+
+ if (!GIP_SendVendorMessage(device,
+ GIP_SL_ELITE_CONFIG,
+ 0,
+ enable_raw_report,
+ sizeof(enable_raw_report)))
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool GIP_SendGuideButtonLED(GIP_Device *device, Uint8 pattern, Uint8 intensity)
+{
+ Uint8 buffer[] = {
+ GIP_LED_GUIDE,
+ pattern,
+ intensity,
+ };
+
+ return GIP_SendSystemMessage(device, GIP_CMD_LED, 0, buffer, sizeof(buffer));
+}
+
+static bool GIP_SendQueryFirmware(GIP_Device *device, Uint8 slot)
+{
+ /* The "slot" variable might not be correct; the packet format is still unclear */
+ Uint8 buffer[] = { 0x1, slot, 0, 0, 0 };
+
+ return GIP_SendSystemMessage(device, GIP_CMD_FIRMWARE, 0, buffer, sizeof(buffer));
+}
+
+static bool GIP_SendSetDeviceState(GIP_Device *device, Uint8 state, Uint8 attachment)
+{
+ Uint8 buffer[] = { state };
+ attachment &= GIP_FLAG_ATTACHMENT_MASK;
+ return GIP_SendSystemMessage(device, GIP_CMD_SET_DEVICE_STATE, attachment, buffer, sizeof(buffer));
+}
+
+static bool GIP_SendInitSequence(GIP_Device *device)
+{
+ if (device->device->vendor_id == USB_VENDOR_MICROSOFT &&
+ device->device->product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2)
+ {
+ /*
+ * The meaning of this packet is unknown and not documented, but it's
+ * needed for the Elite 2 controller to start up on older firmwares
+ */
+ static const Uint8 set_device_state[] = { GIP_STATE_UNK6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x55, 0x53, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 };
+
+ if (!GIP_SendSystemMessage(device,
+ GIP_CMD_SET_DEVICE_STATE,
+ 0,
+ set_device_state,
+ sizeof(set_device_state)))
+ {
+ return false;
+ }
+
+ if (!GIP_EnableEliteButtons(device)) {
+ return false;
+ }
+ }
+ if (!GIP_SendSetDeviceState(device, GIP_STATE_START, 0)) {
+ return false;
+ }
+ device->device_state = GIP_STATE_START;
+
+ if (!GIP_SendGuideButtonLED(device, GIP_LED_GUIDE_ON, 20)) {
+ return false;
+ }
+
+ if (GIP_SupportsSystemMessage(device, GIP_CMD_SECURITY, false) &&
+ !(device->features & GIP_FEATURE_SECURITY_OPT_OUT))
+ {
+ /* TODO: Implement Security command property */
+ Uint8 buffer[] = { 0x1, 0x0 };
+ GIP_SendSystemMessage(device, GIP_CMD_SECURITY, 0, buffer, sizeof(buffer));
+ }
+
+ if (GIP_SupportsVendorMessage(device, GIP_CMD_INITIAL_REPORTS_REQUEST, false)) {
+ GIP_InitialReportsRequest request = { 0 };
+ GIP_SendVendorMessage(device, GIP_CMD_INITIAL_REPORTS_REQUEST, 0, (const Uint8 *)&request, sizeof(request));
+ }
+ return HIDAPI_JoystickConnected(device->device, NULL);
+}
+
+static bool GIP_EnsureMetadata(GIP_Device *device)
+{
+
+ switch (device->got_metadata) {
+ case GIP_METADATA_GOT:
+ case GIP_METADATA_FAKED:
+ return true;
+ case GIP_METADATA_NONE:
+ if (device->quirks & GIP_QUIRK_BROKEN_METADATA) {
+ GIP_SendSystemMessage(device, GIP_CMD_METADATA, 0, NULL, 0);
+ GIP_SetMetadataDefaults(device);
+ return GIP_SendInitSequence(device);
+ } else if (device->got_hello) {
+ device->got_metadata = GIP_METADATA_PENDING;
+ device->metadata_next = SDL_GetTicks() + 500;
+ device->metadata_retries = 0;
+ return GIP_SendSystemMessage(device, GIP_CMD_METADATA, 0, NULL, 0);
+ } else {
+ return GIP_SetMetadataDefaults(device);
+ }
+ default:
+ return true;
+ }
+}
+
+static bool GIP_SetMetadataDefaults(GIP_Device *device)
+{
+ int seq;
+
+ /* Some decent default settings */
+ device->features |= GIP_FEATURE_MOTOR_CONTROL;
+ device->device_type = GIP_TYPE_GAMEPAD;
+ device->metadata.device.in_system_messages[0] |= (1u << GIP_CMD_GUIDE_BUTTON);
+
+ if (SDL_IsJoystickXboxSeriesX(device->device->vendor_id, device->device->product_id)) {
+ device->features |= GIP_FEATURE_CONSOLE_FUNCTION_MAP;
+ }
+
+ GIP_HandleQuirks(device);
+
+ if (GIP_SupportsSystemMessage(device, GIP_CMD_FIRMWARE, false)) {
+ GIP_SendQueryFirmware(device, 2);
+ }
+
+ if (device->features & GIP_FEATURE_MOTOR_CONTROL) {
+ for (seq = 1; seq < 0x100; seq++) {
+ Uint8 message[9] = {0};
+
+ /* Try all sequence numbers to reset it to 1 */
+ GIP_SendRawMessage(device,
+ GIP_CMD_DIRECT_MOTOR,
+ 0,
+ (Uint8) seq,
+ message,
+ sizeof(message),
+ true,
+ NULL,
+ NULL);
+ }
+ }
+
+ device->got_metadata = GIP_METADATA_FAKED;
+ device->hello_deadline = 0;
+ return HIDAPI_JoystickConnected(device->device, NULL);
+}
+
+static bool GIP_HandleCommandProtocolControl(
+ GIP_Device *device,
+ const GIP_Header *header,
+ const Uint8 *bytes,
+ int num_bytes)
+{
+ // TODO
+ SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Protocol Control message");
+ return false;
+}
+
+static bool GIP_HandleCommandHelloDevice(
+ GIP_Device *device,
+ const GIP_Header *header,
+ const Uint8 *bytes,
+ int num_bytes)
+{
+ GIP_HelloDevice message = {0};
+
+ if (num_bytes != 28) {
+ return false;
+ }
+
+ message.device_id = (Uint64) bytes[0];
+ message.device_id |= (Uint64) bytes[1] << 8;
+ message.device_id |= (Uint64) bytes[2] << 16;
+ message.device_id |= (Uint64) bytes[3] << 24;
+ message.device_id |= (Uint64) bytes[4] << 32;
+ message.device_id |= (Uint64) bytes[5] << 40;
+ message.device_id |= (Uint64) bytes[6] << 48;
+ message.device_id |= (Uint64) bytes[7] << 56;
+
+ message.vendor_id = bytes[8];
+ message.vendor_id |= bytes[9] << 8;
+
+ message.product_id = bytes[10];
+ message.product_id |= bytes[11] << 8;
+
+ message.firmware_major_version = bytes[12];
+ message.firmware_major_version |= bytes[13] << 8;
+
+ message.firmware_minor_version = bytes[14];
+ message.firmware_minor_version |= bytes[15] << 8;
+
+ message.firmware_build_version = bytes[16];
+ message.firmware_build_version |= bytes[17] << 8;
+
+ message.firmware_revision = bytes[18];
+ message.firmware_revision |= bytes[19] << 8;
+
+ message.hardware_major_version = bytes[20];
+ message.hardware_minor_version = bytes[21];
+
+ message.rf_proto_major_version = bytes[22];
+ message.rf_proto_minor_version = bytes[23];
+
+ message.security_major_version = bytes[24];
+ message.security_minor_version = bytes[25];
+
+ message.gip_major_version = bytes[26];
+ message.gip_minor_version = bytes[27];
+
+ SDL_LogInfo(SDL_LOG_CATEGORY_INPUT,
+ "GIP: Device hello from %" SDL_PRIx64 " (%04x:%04x)",
+ message.device_id, message.vendor_id, message.product_id);
+ SDL_LogInfo(SDL_LOG_CATEGORY_INPUT,
+ "GIP: Firmware version %d.%d.%d rev %d",
+ message.firmware_major_version,
+ message.firmware_minor_version,
+ message.firmware_build_version,
+ message.firmware_revision);
+
+ /*
+ * The GIP spec specifies that the host should reject the device if any of these are wrong.
+ * I don't know if Windows or an Xbox do, however, so let's just log warnings instead.
+ */
+ if (message.rf_proto_major_version != 1 && message.rf_proto_minor_version != 0) {
+ SDL_LogWarn(SDL_LOG_CATEGORY_INPUT,
+ "GIP: Invalid RF protocol version %d.%d, expected 1.0",
+ message.rf_proto_major_version, message.rf_proto_minor_version);
+ }
+
+ if (message.security_major_version != 1 && message.security_minor_version != 0) {
+ SDL_LogWarn(SDL_LOG_CATEGORY_INPUT,
+ "GIP: Invalid security protocol version %d.%d, expected 1.0",
+ message.security_major_version, message.security_minor_version);
+ }
+
+ if (message.gip_major_version != 1 && message.gip_minor_version != 0) {
+ SDL_LogWarn(SDL_LOG_CATEGORY_INPUT,
+ "GIP: Invalid GIP version %d.%d, expected 1.0",
+ message.gip_major_version, message.gip_minor_version);
+ }
+
+ if (header->flags & GIP_FLAG_ATTACHMENT_MASK) {
+ return GIP_SendSystemMessage(device, GIP_CMD_METADATA, header->flags & GIP_FLAG_ATTACHMENT_MASK, NULL, 0);
+ } else {
+ device->firmware_major_version = message.firmware_major_version;
+ device->firmware_minor_version = message.firmware_minor_version;
+
+ device->hello_deadline = 0;
+ device->got_hello = true;
+ if (device->got_metadata == GIP_METADATA_FAKED) {
+ device->got_metadata = GIP_METADATA_NONE;
+ }
+ GIP_EnsureMetadata(device);
+ }
+ return true;
+}
+
+static bool GIP_HandleCommandStatusDevice(
+ GIP_Device *device,
+ const GIP_Header *header,
+ const Uint8 *bytes,
+ int num_bytes)
+{
+ GIP_ExtendedStatus status = {0};
+ int i;
+
+ if (num_bytes < 1) {
+ return false;
+ }
+ status.base.battery_level = bytes[0] & 3;
+ status.base.battery_type = (bytes[0] >> 2) & 3;
+ status.base.charge = (bytes[0] >> 4) & 3;
+ status.base.power_level = (bytes[0] >> 6) & 3;
+
+ if (num_bytes >= 4) {
+ status.device_active = bytes[1] & 1;
+ if (bytes[1] & 2) {
+ /* Events present */
+ if (num_bytes < 5) {
+ return false;
+ }
+ status.num_events = bytes[4];
+ if (status.num_events > 5) {
+ SDL_LogWarn(SDL_LOG_CATEGORY_INPUT,
+ "GIP: Device reported too many events, %d > 5",
+ status.num_events);
+ return false;
+ }
+ if (5 + status.num_events * 10 > num_bytes) {
+ return false;
+ }
+ for (i = 0; i < status.num_events; i++) {
+ status.events[i].event_type = bytes[i * 10 + 5];
+ status.events[i].event_type |= bytes[i * 10 + 6] << 8;
+ status.events[i].fault_tag = bytes[i * 10 + 7];
+ status.events[i].fault_tag |= bytes[i * 10 + 8] << 8;
+ status.events[i].fault_tag |= bytes[i * 10 + 9] << 16;
+ status.events[i].fault_tag |= bytes[i * 10 + 10] << 24;
+ status.events[i].fault_tag = bytes[i * 10 + 11];
+ status.events[i].fault_tag |= bytes[i * 10 + 12] << 8;
+ status.events[i].fault_tag |= bytes[i * 10 + 13] << 16;
+ status.events[i].fault_tag |= bytes[i * 10 + 14] << 24;
+ }
+ }
+ }
+
+ GIP_EnsureMetadata(device);
+ return true;
+}
+
+static bool GIP_HandleCommandMetadataRespose(
+ GIP_Device *device,
+ const GIP_Header *header,
+ const Uint8 *bytes,
+ int num_bytes)
+{
+ GIP_Metadata metadata = {0};
+ const GUID *expected_guid = NULL;
+ bool found_expected_guid = false;
+ bool found_controller_guid = false;
+ int i;
+
+ if (header->flags & GIP_FLAG_ATTACHMENT_MASK) {
+ /* TODO: Parse properly */
+ return true;
+ }
+
+ if (!GIP_ParseMetadata(&metadata, bytes, num_bytes)) {
+ return false;
+ }
+
+ if (device->got_metadata == GIP_METADATA_GOT) {
+ GIP_MetadataFree(&device->metadata);
+ }
+ device->metadata = metadata;
+ device->got_metadata = GIP_METADATA_GOT;
+ device->features = 0;
+
+ device->device_type = GIP_TYPE_UNKNOWN;
+ for (i = 0; i < metadata.device.num_preferred_types; i++) {
+ const char *type = metadata.device.preferred_types[i];
+#ifdef DEBUG_XBOX_PROTOCOL
+ SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Device preferred type: %s", type);
+#endif
+ if (SDL_strcmp(type, "Windows.Xbox.Input.Gamepad") == 0) {
+ device->device_type = GIP_TYPE_GAMEPAD;
+ expected_guid = &GUID_IGamepad;
+ break;
+ }
+ if (SDL_strcmp(type, "Microsoft.Xbox.Input.ArcadeStick") == 0) {
+ device->device_type = GIP_TYPE_ARCADE_STICK;
+ expected_guid = &GUID_ArcadeStick;
+ break;
+ }
+ if (SDL_strcmp(type, "Windows.Xbox.Input.ArcadeStick") == 0) {
+ device->device_type = GIP_TYPE_ARCADE_STICK;
+ expected_guid = &GUID_ArcadeStick;
+ break;
+ }
+ if (SDL_strcmp(type, "Microsoft.Xbox.Input.FlightStick") == 0) {
+ device->device_type = GIP_TYPE_FLIGHT_STICK;
+ expected_guid = &GUID_ArcadeStick;
+ break;
+ }
+ if (SDL_strcmp(type, "Windows.Xbox.Input.FlightStick") == 0) {
+ device->device_type = GIP_TYPE_FLIGHT_STICK;
+ expected_guid = &GUID_ArcadeStick;
+ break;
+ }
+ if (SDL_strcmp(type, "Microsoft.Xbox.Input.Wheel") == 0) {
+ device->device_type = GIP_TYPE_WHEEL;
+ expected_guid = &GUID_Wheel;
+ break;
+ }
+ if (SDL_strcmp(type, "Windows.Xbox.Input.Wheel") == 0) {
+ device->device_type = GIP_TYPE_WHEEL;
+ expected_guid = &GUID_Wheel;
+ break;
+ }
+ if (SDL_strcmp(type, "Windows.Xbox.Input.NavigationController") == 0) {
+ device->device_type = GIP_TYPE_NAVIGATION_CONTROLLER;
+ expected_guid = &GUID_NavigationController;
+ break;
+ }
+ }
+
+ for (i = 0; i < metadata.device.num_supported_interfaces; i++) {
+ const GUID* guid = &metadata.device.supported_interfaces[i];
+#ifdef DEBUG_XBOX_PROTOCOL
+ SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
+ "GIP: Supported interface: %08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
+ guid->a, guid->b, guid->c, guid->d[0], guid->d[1],
+ guid->d[2], guid->d[3], guid->d[4], guid->d[5], guid->d[6], guid->d[7]);
+#endif
+ if (expected_guid && SDL_memcmp(expected_guid, guid, sizeof(GUID)) == 0) {
+ found_expected_guid = true;
+ }
+ if (SDL_memcmp(&GUID_IController, guid, sizeof(GUID)) == 0) {
+ found_controller_guid = true;
+ continue;
+ }
+ if (SDL_memcmp(&GUID_IDevAuthPCOptOut, guid, sizeof(GUID)) == 0) {
+ device->features |= GIP_FEATURE_SECURITY_OPT_OUT;
+ continue;
+ }
+ if (SDL_memcmp(&GUID_IConsoleFunctionMap_InputReport, guid, sizeof(GUID)) == 0) {
+ device->features |= GIP_FEATURE_CONSOLE_FUNCTION_MAP;
+ continue;
+ }
+ if (SDL_memcmp(&GUID_IConsoleFunctionMap_OverflowInputReport, guid, sizeof(GUID)) == 0) {
+ device->features |= GIP_FEATURE_CONSOLE_FUNCTION_MAP_OVERFLOW;
+ continue;
+ }
+ if (SDL_memcmp(&GUID_IEliteButtons, guid, sizeof(GUID)) == 0) {
+ device->features |= GIP_FEATURE_ELITE_BUTTONS;
+ continue;
+ }
+ if (SDL_memcmp(&GUID_DynamicLatencyInput, guid, sizeof(GUID)) == 0) {
+ device->features |= GIP_FEATURE_DYNAMIC_LATENCY_INPUT;
+ continue;
+ }
+ }
+
+ for (i = 0; i < metadata.num_messages; i++) {
+ GIP_MessageMetadata *message = &metadata.message_metadata[i];
+ if (message->type == GIP_CMD_DIRECT_MOTOR && message->length >= 9 &&
+ (message->flags & GIP_MESSAGE_FLAG_DOWNSTREAM)) {
+ device->features |= GIP_FEATURE_MOTOR_CONTROL;
+ }
+ }
+
+ if (!found_expected_guid || !found_controller_guid) {
+ SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
+ "GIP: Controller was missing expected GUID. This controller probably won't work on an actual Xbox.");
+ }
+
+ if ((device->features & GIP_CMD_GUIDE_COLOR) && !GIP_SupportsVendorMessage(device, GIP_CMD_GUIDE_COLOR, false)) {
+ device->features &= ~GIP_CMD_GUIDE_COLOR;
+ }
+
+ GIP_HandleQuirks(device);
+
+ return GIP_SendInitSequence(device);
+}
+
+static bool GIP_HandleCommandSecurity(
+ GIP_Device *device,
+ const GIP_Header *header,
+ const Uint8 *bytes,
+ int num_bytes)
+{
+ // TODO
+ SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Security message");
+ return false;
+}
+
+static bool GIP_HandleCommandGuideButtonStatus(
+ GIP_Device *device,
+ const GIP_Header *header,
+ const Uint8 *bytes,
+ int num_bytes)
+{
+ Uint64 timestamp = SDL_GetTicksNS();
+ SDL_Joystick *joystick = NULL;
+
+ if (device->device->num_joysticks < 1) {
+ return true;
+ }
+
+ joystick = SDL_GetJoystickFromID(device->device->joysticks[0]);
+ if (!joystick) {
+ return false;
+ }
+ if (bytes[1] == VK_LWIN) {
+ SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, (bytes[0] & 0x01) != 0);
+ }
+
+ return true;
+}
+
+static bool GIP_HandleCommandAudioControl(
+ GIP_Device *device,
+ const GIP_Header *header,
+ const Uint8 *bytes,
+ int num_bytes)
+{
+ // TODO
+ SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Audio Control message");
+ return false;
+}
+
+static bool GIP_HandleCommandFirmware(
+ GIP_Device *device,
+ const GIP_Header *header,
+ const Uint8 *bytes,
+ int num_bytes)
+{
+ if (num_bytes < 1) {
+ return false;
+ }
+ if (bytes[0] == 1) {
+ Uint16 major, minor, build, rev;
+
+ if (num_bytes < 14) {
+ SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Discarding too-short firmware message");
+
+ return false;
+ }
+ major = bytes[6];
+ major |= bytes[7] << 8;
+ minor = bytes[8];
+ minor |= bytes[9] << 8;
+ build = bytes[10];
+ build |= bytes[11] << 8;
+ rev = bytes[12];
+ rev |= bytes[13] << 8;
+
+ SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Firmware version: %d.%d.%d rev %d", major, minor, build, rev);
+
+ device->firmware_major_version = major;
+ device->firmware_minor_version = minor;
+
+ if (device->device->vendor_id == USB_VENDOR_MICROSOFT &&
+ device->device->product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2)
+ {
+ if (device->firmware_major_version == 5 && device->firmware_minor_version < 17) {
+ device->paddle_format = GIP_PADDLES_XBE2_RAW;
+ } else {
+ device->paddle_format = GIP_PADDLES_XBE2;
+ }
+ }
+
+ return GIP_EnableEliteButtons(device);
+ } else {
+ SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Firmware message");
+
+ return false;
+ }
+}
+
+static bool GIP_HandleCommandRawReport(
+ GIP_Device *device,
+ const GIP_Header *header,
+ const Uint8 *bytes,
+ int num_bytes)
+{
+ Uint64 timestamp = SDL_GetTicksNS();
+ SDL_Joystick *joystick = NULL;
+
+ if (device->device->num_joysticks < 1) {
+ return true;
+ }
+
+ joystick = SDL_GetJoystickFromID(device->device->joysticks[0]);
+ if (!joystick) {
+ return false;
+ }
+
+ if (num_bytes < 17 || num_bytes <= device->paddle_offset) {
+ SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Discarding too-short raw report");
+ return false;
+ }
+
+ if ((device->features & GIP_FEATURE_ELITE_BUTTONS) && device->paddle_format == GIP_PADDLES_XBE2_RAW) {
+ SDL_SendJoystickButton(timestamp,
+ joystick,
+ device->paddle_idx,
+ (bytes[device->paddle_offset] & 0x01) != 0);
+ SDL_SendJoystickButton(timestamp,
+ joystick,
+ device->paddle_idx + 1,
+ (bytes[device->paddle_offset] & 0x02) != 0);
+ SDL_SendJoystickButton(timestamp,
+ joystick,
+ device->paddle_idx + 2,
+ (bytes[device->paddle_offset] & 0x04) != 0);
+ SDL_SendJoystickButton(timestamp,
+ joystick,
+ device->paddle_idx + 3,
+ (bytes[device->paddle_offset] & 0x08) != 0);
+ }
+ return true;
+}
+
+
+static bool GIP_HandleCommandHidReport(
+ GIP_Device *device,
+ const GIP_Header *header,
+ const Uint8 *bytes,
+ int num_bytes)
+{
+ // TODO
+ SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented HID Report message");
+ return false;
+}
+
+static bool GIP_HandleCommandExtended(
+ GIP_Device *device,
+ const GIP_Header *header,
+ const Uint8 *bytes,
+ int num_bytes)
+{
+ char serial[33] = {0};
+
+ if (num_bytes < 2) {
+ return false;
+ }
+
+ switch (bytes[0]) {
+ case GIP_EXTCMD_GET_SERIAL_NUMBER:
+ if (bytes[1] != GIP_EXTENDED_STATUS_OK) {
+ return true;
+ }
+ SDL_memcpy(serial, &bytes[2], SDL_min(sizeof(serial) - 1, num_bytes - 2));
+ HIDAPI_SetDeviceSerial(device->device, serial);
+ break;
+ default:
+ // TODO
+ SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Extended message type %02x", bytes[0]);
+ return false;
+ }
+
+ return true;
+}
+
+static bool GIP_HandleLLInputReport(
+ GIP_Device *device,
+ const GIP_Header *header,
+ const Uint8 *bytes,
+ int num_bytes)
+{
+ Sint16 axis;
+ Uint64 timestamp = SDL_GetTicksNS();
+ SDL_Joystick *joystick = NULL;
+
+ if (device->device->num_joysticks < 1) {
+ GIP_EnsureMetadata(device);
+ if (device->got_metadata != GIP_METADATA_GOT && device->got_metadata != GIP_METADATA_FAKED) {
+ return true;
+ }
+ }
+
+ joystick = SDL_GetJoystickFromID(device->device->joysticks[0]);
+ if (!joystick) {
+ return false;
+ }
+
+ if (device->device_state != GIP_STATE_START) {
+ SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Discarding early input report");
+ device->device_state = GIP_STATE_START;
+ return true;
+ }
+
+ if (num_bytes < 14) {
+ SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Discarding too-short input report");
+ return false;
+ }
+ if (device->last_input[0] != bytes[0]) {
+ SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((bytes[0] & 0x04) != 0));
+ SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((bytes[0] & 0x08) != 0));
+ SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((bytes[0] & 0x10) != 0));
+ SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((bytes[0] & 0x20) != 0));
+ SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((bytes[0] & 0x40) != 0));
+ SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((bytes[0] & 0x80) != 0));
+ }
+
+ if (device->last_input[1] != bytes[1]) {
+ Uint8 hat = 0;
+
+ if (bytes[1] & 0x01) {
+ hat |= SDL_HAT_UP;
+ }
+ if (bytes[1] & 0x02) {
+ hat |= SDL_HAT_DOWN;
+ }
+ if (bytes[1] & 0x04) {
+ hat |= SDL_HAT_LEFT;
+ }
+ if (bytes[1] & 0x08) {
+ hat |= SDL_HAT_RIGHT;
+ }
+ SDL_SendJoystickHat(timestamp, joystick, 0, hat);
+
+ if (device->device_type == GIP_TYPE_ARCADE_STICK) {
+ /* Previous */
+ SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((bytes[1] & 0x10) != 0));
+ /* Next */
+ SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((bytes[1] & 0x20) != 0));
+ } else {
+ SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((bytes[1] & 0x10) != 0));
+ SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((bytes[1] & 0x20) != 0));
+ SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((bytes[1] & 0x40) != 0));
+ SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((bytes[1] & 0x80) != 0));
+ }
+ }
+
+ axis = bytes[2];
+ axis |= bytes[3] << 8;
+ axis = SDL_clamp(axis, 0, 1023);
+ axis = (axis - 512) * 64;
+ if (axis == 32704) {
+ axis = 32767;
+ }
+ SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
+
+ axis = bytes[4];
+ axis |= bytes[5] << 8;
+ axis = SDL_clamp(axis, 0, 1023);
+ axis = (axis - 512) * 64;
+ if (axis == 32704) {
+ axis = 32767;
+ }
+ SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
+
+ if (device->device_type == GIP_TYPE_ARCADE_STICK) {
+ /* Extra button 6 */
+ SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, (bytes[18] & 0x40) ? 32767 : -32768);
+ /* Extra button 7 */
+ SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, (bytes[18] & 0x80) ? 32767 : -32768);
+ } else {
+ axis = bytes[6];
+ axis |= bytes[7] << 8;
+ SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
+ axis = bytes[8];
+ axis |= bytes[9] << 8;
+ SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, ~axis);
+ axis = bytes[10];
+ axis |= bytes[11] << 8;
+ SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
+ axis = bytes[12];
+ axis |= bytes[13] << 8;
+ SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, ~axis);
+ }
+
+ if ((device->features & GIP_FEATURE_ELITE_BUTTONS) &&
+ num_bytes > device->paddle_offset &&
+ device->last_input[device->paddle_offset] != bytes[device->paddle_offset])
+ {
+ if (device->paddle_format == GIP_PADDLES_XBE1) {
+ if (bytes[device->paddle_offset] & 0x10) {
+ SDL_SendJoystickButton(timestamp,
+ joystick,
+ device->paddle_idx,
+ (bytes[device->paddle_offset] & 0x02) != 0);
+ SDL_SendJoystickButton(timestamp,
+ joystick,
+ device->paddle_idx + 1,
+ (bytes[device->paddle_offset] & 0x08) != 0);
+ SDL_SendJoystickButton(timestamp,
+ joystick,
+ device->paddle_idx + 2,
+ (bytes[device->paddle_offset] & 0x01) != 0);
+ SDL_SendJoystickButton(timestamp,
+ joystick,
+ device->paddle_idx + 3,
+ (bytes[device->paddle_offset] & 0x04) != 0);
+ }
+ } else if (device->paddle_format == GIP_PADDLES_XBE2) {
+ SDL_SendJoystickButton(timestamp,
+ joystick,
+ device->paddle_idx,
+ (bytes[device->paddle_offset] & 0x01) != 0);
+ SDL_SendJoystickButton(timestamp,
+ joystick,
+ device->paddle_idx + 1,
+ (bytes[device->paddle_offset] & 0x02) != 0);
+ SDL_SendJoystickButton(timestamp,
+ joystick,
+ device->paddle_idx + 2,
+ (bytes[device->paddle_offset] & 0x04) != 0);
+ SDL_SendJoystickButton(timestamp,
+ joystick,
+ device->paddle_idx + 3,
+ (bytes[device->paddle_offset] & 0x08) != 0);
+ }
+ }
+
+ if ((device->features & GIP_FEATURE_CONSOLE_FUNCTION_MAP) && num_bytes >= 32) {
+ int function_map_offset = -1;
+ if (device->features & GIP_FEATURE_DYNAMIC_LATENCY_INPUT) {
+ /* The dynamic latency input bytes are after the console function map */
+ if (num_bytes >= 40) {
+ function_map_offset = num_bytes - 26;
+ }
+ } else {
+ function_map_offset = num_bytes - 18;
+ }
+ if (function_map_offset >= 14) {
+ if (device->last_input[function_map_offset] != bytes[function_map_offset]) {
+ SDL_SendJoystickButton(timestamp,
+ joystick,
+ device->share_button_idx,
+ (bytes[function_map_offset] & 0x01) != 0);
+ }
+ }
+ }
+
+ SDL_memcpy(device->last_input, bytes, SDL_min(num_bytes, sizeof(device->last_input)));
+
+ return true;
+}
+
+static bool GIP_HandleLLStaticConfiguration(
+ GIP_Device *device,
+ const GIP_Header *header,
+ const Uint8 *bytes,
+ int num_bytes)
+{
+ // TODO
+ SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Static Configuration message");
+ return false;
+}
+
+static bool GIP_HandleLLButtonInfoReport(
+ GIP_Device *device,
+ const GIP_Header *header,
+ const Uint8 *bytes,
+ int num_bytes)
+{
+ // TODO
+ SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Button Info Report message");
+ return false;
+}
+
+static bool GIP_HandleLLOverflowInputReport(
+ GIP_Device *device,
+ const GIP_Header *header,
+ const Uint8 *bytes,
+ int num_bytes)
+{
+ // TODO
+ SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Overflow Input Report message");
+ return false;
+}
+
+static bool GIP_HandleAudioData(
+ GIP_Device *device,
+ const GIP_Header *header,
+ const Uint8 *bytes,
+ int num_bytes)
+{
+ // TODO
+ SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Unimplemented Audio Data message");
+ return false;
+}
+
+static bool GIP_HandleSystemMessage(
+ GIP_Device *device,
+ const GIP_Header *header,
+ const Uint8 *bytes,
+ int num_bytes)
+{
+ if (!GIP_SupportsSystemMessage(device, header->message_type, true)) {
+ SDL_LogWarn(SDL_LOG_CATEGORY_INPUT,
+ "GIP: Received claimed-unsupported system message type %02x",
+ header->message_type);
+ return false;
+ }
+ switch (header->message_type) {
+ case GIP_CMD_PROTO_CONTROL:
+ return GIP_HandleCommandProtocolControl(device, header, bytes, num_bytes);
+ case GIP_CMD_HELLO_DEVICE:
+ return GIP_HandleCommandHelloDevice(device, header, bytes, num_bytes);
+ case GIP_CMD_STATUS_DEVICE:
+ return GIP_HandleCommandStatusDevice(device, header, bytes, num_bytes);
+ case GIP_CMD_METADATA:
+ return GIP_HandleCommandMetadataRespose(device, header, bytes, num_bytes);
+ case GIP_CMD_SECURITY:
+ return GIP_HandleCommandSecurity(device, header, bytes, num_bytes);
+ case GIP_CMD_GUIDE_BUTTON:
+ return GIP_HandleCommandGuideButtonStatus(device, header, bytes, num_bytes);
+ case GIP_CMD_AUDIO_CONTROL:
+ return GIP_HandleCommandAudioControl(device, header, bytes, num_bytes);
+ case GIP_CMD_FIRMWARE:
+ return GIP_HandleCommandFirmware(device, header, bytes, num_bytes);
+ case GIP_CMD_HID_REPORT:
+ return GIP_HandleCommandHidReport(device, header, bytes, num_bytes);
+ case GIP_CMD_EXTENDED:
+ return GIP_HandleCommandExtended(device, header, bytes, num_bytes);
+ case GIP_AUDIO_DATA:
+ return GIP_HandleAudioData(device, header, bytes, num_bytes);
+ default:
+ SDL_LogWarn(SDL_LOG_CATEGORY_INPUT,
+ "GIP: Received unknown system message type %02x",
+ header->message_type);
+ return false;
+ }
+}
+
+static bool GIP_HandleMessage(
+ GIP_Device *device,
+ const GIP_Header *header,
+ const Uint8 *bytes,
+ int num_bytes)
+{
+ if (header->flags & GIP_FLAG_SYSTEM) {
+ return GIP_HandleSystemMessage(device, header, bytes, num_bytes);
+ } else {
+ switch (header->message_type) {
+ case GIP_CMD_RAW_REPORT:
+ if (device->features & GIP_FEATURE_ELITE_BUTTONS) {
+ return GIP_HandleCommandRawReport(device, header, bytes, num_bytes);
+ }
+ break;
+ case GIP_LL_INPUT_REPORT:
+ return GIP_HandleLLInputReport(device, header, bytes, num_bytes);
+ case GIP_LL_STATIC_CONFIGURATION:
+ return GIP_HandleLLStaticConfiguration(device, header, bytes, num_bytes);
+ case GIP_LL_BUTTON_INFO_REPORT:
+ return GIP_HandleLLButtonInfoReport(device, header, bytes, num_bytes);
+ case GIP_LL_OVERFLOW_INPUT_REPORT:
+ return GIP_HandleLLOverflowInputReport(device, header, bytes, num_bytes);
+ }
+ }
+ SDL_LogWarn(SDL_LOG_CATEGORY_INPUT,
+ "GIP: Received unknown vendor message type %02x",
+ header->message_type);
+ return false;
+}
+
+static int GIP_ReceivePacket(GIP_Device *device, const Uint8 *bytes, int num_bytes)
+{
+ GIP_Header header;
+ int offset = 3;
+ bool ok = true;
+ Uint64 fragment_offset = 0;
+ Uint16 bytes_remaining = 0;
+ bool is_fragment;
+
+ if (num_bytes < 5) {
+ return -1;
+ }
+
+ header.message_type = bytes[0];
+ header.flags = bytes[1];
+ header.sequence_id = bytes[2];
+ offset += GIP_DecodeLength(&header.length, &bytes[offset], num_bytes - offset);
+
+ is_fragment = header.flags & GIP_FLAG_FRAGMENT;
+
+#ifdef DEBUG_XBOX_PROTOCOL
+ HIDAPI_DumpPacket("GIP received message: size = %d", bytes, num_bytes);
+#endif
+
+ /* Handle coalescing fragmented messages */
+ if (is_fragment) {
+ if (header.flags & GIP_FLAG_INIT_FRAG) {
+ Uint64 total_length;
+ if (device->fragment_message) {
+ /*
+ * Reset fragment buffer if we get a new initial
+ * fragment before finishing the last message.
+ * TODO: Is this the correct behavior?
+ */
+ if (device->fragment_data) {
+ SDL_free(device->fragment_data);
+ device->fragment_data = NULL;
+ }
+ }
+ offset += GIP_DecodeLength(&total_length, &bytes[offset], num_bytes - offset);
+ if (total_length > MAX_MESSAGE_LENGTH) {
+ return -1;
+ }
+ device->total_length = (Uint16) total_length;
+ device->fragment_message = header.message_type;
+ if (header.length > num_bytes - offset) {
+ SDL_LogWarn(SDL_LOG_CATEGORY_INPUT,
+ "GIP: Received fragment that claims to be %" SDL_PRIu64 " bytes, expected %i",
+ header.length, num_bytes - offset);
+ return -1;
+ }
+ if (header.length > total_length) {
+ SDL_LogWarn(SDL_LOG_CATEGORY_INPUT,
+ "GIP: Received too long fragment, %" SDL_PRIu64 " bytes, exceeds %d",
+ header.length, device->total_length);
+ return -1;
+ }
+ device->fragment_data = SDL_malloc(device->total_length);
+ SDL_memcpy(device->fragment_data, &bytes[offset], (size_t) header.length);
+ fragment_offset = header.length;
+ device->fragment_offset = (Uint32) fragment_offset;
+ bytes_remaining = (Uint16) (device->total_length - fragment_offset);
+ } else {
+ if (header.message_type != device->fragment_message) {
+ SDL_LogWarn(SDL_LOG_CATEGORY_INPUT,
+ "GIP: Received out of sequence message type %02x, expected %02x",
+ header.message_type, device->fragment_message);
+ GIP_FragmentFailed(device, &header);
+ return -1;
+ }
+
+ offset += GIP_DecodeLength(&fragment_offset, &bytes[offset], num_bytes - offset);
+ if (fragment_offset != device->fragment_offset) {
+ SDL_LogWarn(SDL_LOG_CATEGORY_INPUT,
+ "GIP: Received out of sequence fragment, (claimed %" SDL_PRIu64 ", expected %d)",
+ fragment_offset, device->fragment_offset);
+ return GIP_Acknowledge(device,
+ &header,
+ device->fragment_offset,
+ (Uint16) (device->total_length - device->fragment_offset));
+ } else if (fragment_offset + header.length > device->total_length) {
+ SDL_LogWarn(SDL_LOG_CATEGORY_INPUT,
+ "GIP: Received too long fragment, %" SDL_PRIu64 " exceeds %d",
+ fragment_offset + header.length, device->total_length);
+ GIP_FragmentFailed(device, &header);
+ return -1;
+ }
+
+ bytes_remaining = device->total_length - (Uint16) (fragment_offset + header.length);
+ if (header.length != 0) {
+ SDL_memcpy(&device->fragment_data[fragment_offset], &bytes[offset], (size_t) header.length);
+ } else {
+ ok = GIP_HandleMessage(device, &header, device->fragment_data, device->total_length);
+ if (device->fragment_data) {
+ SDL_free(device->fragment_data);
+ device->fragment_data = NULL;
+ }
+ device->fragment_message = 0;
+ }
+ fragment_offset += header.length;
+ device->fragment_offset = (Uint16) fragment_offset;
+ }
+ device->fragment_timer = SDL_GetTicks();
+ } else if (header.length + offset > num_bytes) {
+ SDL_LogWarn(SDL_LOG_CATEGORY_INPUT,
+ "GIP: Received message with erroneous length (claimed %" SDL_PRIu64 ", actual %d), discarding",
+ header.length + offset, num_bytes);
+ return -1;
+ } else {
+ num_bytes -= offset;
+ bytes += offset;
+ fragment_offset = header.length;
+ ok = GIP_HandleMessage(device, &header, bytes, num_bytes);
+ }
+
+ if (ok && (header.flags & GIP_FLAG_ACME)) {
+ GIP_Acknowledge(device, &header, (Uint32) fragment_offset, bytes_remaining);
+ }
+ return offset + (Uint16) header.length;
+}
+
+static void HIDAPI_DriverGIP_RumbleSent(void *userdata)
+{
+ GIP_Device *ctx = (GIP_Device *)userdata;
+ ctx->rumble_time = SDL_GetTicks();
+}
+
+static bool HIDAPI_DriverGIP_UpdateRumble(GIP_Device *device)
+{
+ GIP_DirectMotor motor;
+
+ if (device->rumble_state == GIP_RUMBLE_STATE_QUEUED && device->rumble_time) {
+ device->rumble_state = GIP_RUMBLE_STATE_BUSY;
+ }
+
+ if (device->rumble_state == GIP_RUMBLE_STATE_BUSY) {
+ const int RUMBLE_BUSY_TIME_MS = 10;
+ if (SDL_GetTicks() >= (device->rumble_time + RUMBLE_BUSY_TIME_MS)) {
+ device->rumble_time = 0;
+ device->rumble_state = GIP_RUMBLE_STATE_IDLE;
+ }
+ }
+
+ if (!device->rumble_pending) {
+ return true;
+ }
+
+ if (device->rumble_state != GIP_RUMBLE_STATE_IDLE) {
+ return true;
+ }
+
+ // We're no longer pending, even if we fail to send the rumble below
+ device->rumble_pending = false;
+
+ motor.motor_bitmap = GIP_MOTOR_ALL;
+ motor.left_impulse_level = device->left_impulse_level;
+ motor.right_impulse_level = device->right_impulse_level;
+ motor.left_vibration_level = device->left_vibration_level;
+ motor.right_vibration_level = device->right_vibration_level;
+ motor.duration = SDL_RUMBLE_RESEND_MS / 10 + 5; // Add a 50ms leniency, just in case
+ motor.delay = 0;
+ motor.repeat = 0;
+
+ Uint8 message[9] = {0};
+ SDL_memcpy(&message[1], &motor, sizeof(motor));
+ if (!GIP_SendRawMessage(device,
+ GIP_CMD_DIRECT_MOTOR,
+ 0,
+ GIP_SequenceNext(device, GIP_CMD_DIRECT_MOTOR, false),
+ message,
+ sizeof(message),
+ true,
+ HIDAPI_DriverGIP_RumbleSent,
+ device))
+ {
+ return SDL_SetError("Couldn't send rumble packet");
+ }
+
+ device->rumble_state = GIP_RUMBLE_STATE_QUEUED;
+
+ return true;
+}
+
+static void HIDAPI_DriverGIP_RegisterHints(SDL_HintCallback callback, void *userdata)
+{
+ SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GIP, callback, userdata);
+ SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GIP_RESET_FOR_METADATA, callback, userdata);
+}
+
+static void HIDAPI_DriverGIP_UnregisterHints(SDL_HintCallback callback, void *userdata)
+{
+ SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GIP, callback, userdata);
+ SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_GIP_RESET_FOR_METADATA, callback, userdata);
+}
+
+static bool HIDAPI_DriverGIP_IsEnabled(void)
+{
+ return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_GIP,
+ SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_ONE,
+ SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX,
+ SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT))));
+}
+
+static bool HIDAPI_DriverGIP_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
+{
+ // Xbox One controllers speak HID over bluetooth instead of GIP
+ if (device && device->is_bluetooth) {
+ return false;
+ }
+#if defined(SDL_PLATFORM_MACOS) && defined(SDL_JOYSTICK_MFI)
+ if (!SDL_IsJoystickBluetoothXboxOne(vendor_id, product_id)) {
+ // On macOS we get a shortened version of the real report and
+ // you can't write output reports for wired controllers, so
+ // we'll just use the GCController support instead.
+ return false;
+ }
+#endif
+ return (type == SDL_GAMEPAD_TYPE_XBOXONE);
+}
+
+static bool HIDAPI_DriverGIP_InitDevice(SDL_HIDAPI_Device *device)
+{
+ GIP_Device *ctx;
+
+ ctx = (GIP_Device *)SDL_calloc(1, sizeof(*ctx));
+ if (!ctx) {
+ return false;
+ }
+ ctx->device = device;
+ ctx->metadata.device.in_system_messages[0] = GIP_DEFAULT_IN_SYSTEM_MESSAGES;
+ ctx->metadata.device.out_system_messages[0] = GIP_DEFAULT_OUT_SYSTEM_MESSAGES;
+ ctx->reset_for_metadata = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_GIP_RESET_FOR_METADATA, false);
+ if (device->vendor_id == USB_VENDOR_MICROSOFT && device->product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_1) {
+ ctx->paddle_format = GIP_PADDLES_XBE1;
+ }
+ GIP_HandleQuirks(ctx);
+
+ if (ctx->quirks & GIP_QUIRK_NO_HELLO) {
+ ctx->got_hello = true;
+ GIP_EnsureMetadata(ctx);
+ } else {
+ ctx->hello_deadline = SDL_GetTicks() + GIP_HELLO_TIMEOUT;
+ }
+
+ device->context = ctx;
+ device->type = SDL_GAMEPAD_TYPE_XBOXONE;
+
+ return true;
+}
+
+static int HIDAPI_DriverGIP_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
+{
+ return -1;
+}
+
+static void HIDAPI_DriverGIP_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
+{
+}
+
+static bool HIDAPI_DriverGIP_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
+{
+ GIP_Device *ctx = (GIP_Device *)device->context;
+
+ SDL_AssertJoysticksLocked();
+
+ ctx->left_impulse_level = 0;
+ ctx->right_impulse_level = 0;
+ ctx->left_vibration_level = 0;
+ ctx->right_vibration_level = 0;
+ ctx->rumble_state = GIP_RUMBLE_STATE_IDLE;
+ ctx->rumble_time = 0;
+ ctx->rumble_pending = false;
+ SDL_zeroa(ctx->last_input);
+
+ // Initialize the joystick capabilities
+ joystick->nbuttons = 11;
+ if (device->vendor_id == USB_VENDOR_MICROSOFT && device->product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2) {
+ ctx->paddle_offset = 14;
+ ctx->paddle_format = GIP_PADDLES_XBE2;
+ if (ctx->firmware_major_version == 5 && ctx->firmware_minor_version < 17) {
+ ctx->paddle_format = GIP_PADDLES_XBE2_RAW;
+ }
+ ctx->paddle_idx = (Uint8) joystick->nbuttons;
+ joystick->nbuttons += 4;
+ }
+ if (ctx->features & GIP_FEATURE_CONSOLE_FUNCTION_MAP) {
+ ctx->share_button_idx = (Uint8) joystick->nbuttons;
+ joystick->nbuttons++;
+ }
+ joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
+ joystick->nhats = 1;
+
+ return true;
+}
+
+static bool HIDAPI_DriverGIP_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
+{
+ GIP_Device *ctx = (GIP_Device *)device->context;
+
+ // Magnitude is 1..100 so scale the 16-bit input here
+ ctx->left_vibration_level = (Uint8)(low_frequency_rumble / 655);
+ ctx->right_vibration_level = (Uint8)(high_frequency_rumble / 655);
+ ctx->rumble_pending = true;
+
+ return HIDAPI_DriverGIP_UpdateRumble(ctx);
+}
+
+static bool HIDAPI_DriverGIP_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
+{
+ GIP_Device *ctx = (GIP_Device *)device->context;
+
+ // Magnitude is 1..100 so scale the 16-bit input here
+ ctx->left_impulse_level = (Uint8)(left_rumble / 655);
+ ctx->right_impulse_level = (Uint8)(right_rumble / 655);
+ ctx->rumble_pending = true;
+
+ return HIDAPI_DriverGIP_UpdateRumble(ctx);
+}
+
+static Uint32 HIDAPI_DriverGIP_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
+{
+ GIP_Device *ctx = (GIP_Device *)device->context;
+ Uint32 result = 0;
+
+ if (ctx->features & GIP_FEATURE_MOTOR_CONTROL) {
+ result |= SDL_JOYSTICK_CAP_RUMBLE | SDL_JOYSTICK_CAP_TRIGGER_RUMBLE;
+ }
+
+ if (ctx->features & GIP_FEATURE_GUIDE_COLOR) {
+ result |= SDL_JOYSTICK_CAP_RGB_LED;
+ }
+
+ return result;
+}
+
+static bool HIDAPI_DriverGIP_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
+{
+ GIP_Device *ctx = (GIP_Device *)device->context;
+ Uint8 buffer[] = { 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+ if (!(ctx->features & GIP_FEATURE_GUIDE_COLOR)) {
+ return SDL_Unsupported();
+ }
+
+ buffer[1] = 0x00; // Whiteness? Sets white intensity when RGB is 0, seems additive
+ buffer[2] = red;
+ buffer[3] = green;
+ buffer[4] = blue;
+
+ if (!GIP_SendVendorMessage(ctx, GIP_CMD_GUIDE_COLOR, 0, buffer, sizeof(buffer))) {
+ return SDL_SetError("Couldn't send LED packet");
+ }
+ return true;
+}
+
+static bool HIDAPI_DriverGIP_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
+{
+ return SDL_Unsupported();
+}
+
+
+static bool HIDAPI_DriverGIP_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
+{
+ return SDL_Unsupported();
+}
+
+static bool HIDAPI_DriverGIP_UpdateDevice(SDL_HIDAPI_Device *device)
+{
+ GIP_Device *ctx = (GIP_Device *)device->context;
+ Uint8 bytes[USB_PACKET_LENGTH];
+ int num_bytes;
+ bool perform_reset = false;
+ Uint64 timestamp;
+
+ while ((num_bytes = SDL_hid_read_timeout(device->dev, bytes, sizeof(bytes), 0)) > 0) {
+ int parsed = GIP_ReceivePacket(ctx, bytes, num_bytes);
+ if (parsed <= 0) {
+ break;
+ }
+ }
+
+ timestamp = SDL_GetTicks();
+ if (ctx->fragment_message && timestamp >= ctx->fragment_timer + 1000) {
+ SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "GIP: Reliable message transfer failed");
+ ctx->fragment_message = 0;
+ }
+ if (ctx->hello_deadline && timestamp >= ctx->hello_deadline) {
+ ctx->hello_deadline = 0;
+ perform_reset = true;
+ } else if (ctx->got_metadata == GIP_METADATA_PENDING && timestamp >= ctx->metadata_next && ctx->fragment_message != GIP_CMD_METADATA) {
+ if (ctx->metadata_retries < 5) {
+ SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "GIP: Retrying metadata request");
+ ctx->metadata_retries++;
+ ctx->metadata_next = timestamp + 500;
+ GIP_SendSystemMessage(ctx, GIP_CMD_METADATA, 0, NULL, 0);
+ } else {
+ perform_reset = true;
+ }
+ }
+ if (perform_reset) {
+ if (ctx->reset_for_metadata) {
+ GIP_SendSetDeviceState(ctx, GIP_STATE_RESET, 0);
+ } else {
+ GIP_SetMetadataDefaults(ctx);
+ }
+ }
+ HIDAPI_DriverGIP_UpdateRumble(ctx);
+
+ if (num_bytes < 0 && device->num_joysticks > 0) {
+ // Read error, device is disconnected
+ HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
+ }
+ return (num_bytes >= 0);
+}
+static void HIDAPI_DriverGIP_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
+{
+}
+
+static void HIDAPI_DriverGIP_FreeDevice(SDL_HIDAPI_Device *device)
+{
+ GIP_Device *context = (GIP_Device *)device->context;
+
+ GIP_MetadataFree(&context->metadata);
+}
+
+SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGIP = {
+ SDL_HINT_JOYSTICK_HIDAPI_GIP,
+ true,
+ HIDAPI_DriverGIP_RegisterHints,
+ HIDAPI_DriverGIP_UnregisterHints,
+ HIDAPI_DriverGIP_IsEnabled,
+ HIDAPI_DriverGIP_IsSupportedDevice,
+ HIDAPI_DriverGIP_InitDevice,
+ HIDAPI_DriverGIP_GetDevicePlayerIndex,
+ HIDAPI_DriverGIP_SetDevicePlayerIndex,
+ HIDAPI_DriverGIP_UpdateDevice,
+ HIDAPI_DriverGIP_OpenJoystick,
+ HIDAPI_DriverGIP_RumbleJoystick,
+ HIDAPI_DriverGIP_RumbleJoystickTriggers,
+ HIDAPI_DriverGIP_GetJoystickCapabilities,
+ HIDAPI_DriverGIP_SetJoystickLED,
+ HIDAPI_DriverGIP_SendJoystickEffect,
+ HIDAPI_DriverGIP_SetJoystickSensorsEnabled,
+ HIDAPI_DriverGIP_CloseJoystick,
+ HIDAPI_DriverGIP_FreeDevice,
+};
+
+#endif // SDL_JOYSTICK_HIDAPI_GIP
+
+#endif // SDL_JOYSTICK_HIDAPI
diff --git a/src/joystick/hidapi/SDL_hidapijoystick.c b/src/joystick/hidapi/SDL_hidapijoystick.c
index 48f1a4b3ea..119177bbcd 100644
--- a/src/joystick/hidapi/SDL_hidapijoystick.c
+++ b/src/joystick/hidapi/SDL_hidapijoystick.c
@@ -82,6 +82,9 @@ static SDL_HIDAPI_DeviceDriver *SDL_HIDAPI_drivers[] = {
&SDL_HIDAPI_DriverXbox360,
&SDL_HIDAPI_DriverXbox360W,
#endif
+#ifdef SDL_JOYSTICK_HIDAPI_GIP
+ &SDL_HIDAPI_DriverGIP,
+#endif
#ifdef SDL_JOYSTICK_HIDAPI_XBOXONE
&SDL_HIDAPI_DriverXboxOne,
#endif
@@ -294,9 +297,13 @@ static SDL_GamepadType SDL_GetJoystickGameControllerProtocol(const char *name, U
0x1532, // Razer
0x20d6, // PowerA
0x24c6, // PowerA
+ 0x294b, // Snakebyte
0x2dc8, // 8BitDo
0x2e24, // Hyperkin
+ 0x2e95, // SCUF
+ 0x3285, // Nacon
0x3537, // GameSir
+ 0x366c, // ByoWave
};
int i;
diff --git a/src/joystick/hidapi/SDL_hidapijoystick_c.h b/src/joystick/hidapi/SDL_hidapijoystick_c.h
index bc027bcea1..b4ba03c115 100644
--- a/src/joystick/hidapi/SDL_hidapijoystick_c.h
+++ b/src/joystick/hidapi/SDL_hidapijoystick_c.h
@@ -42,6 +42,7 @@
#define SDL_JOYSTICK_HIDAPI_STEAM_HORI
#define SDL_JOYSTICK_HIDAPI_LG4FF
#define SDL_JOYSTICK_HIDAPI_8BITDO
+#define SDL_JOYSTICK_HIDAPI_GIP
// Joystick capability definitions
#define SDL_JOYSTICK_CAP_MONO_LED 0x00000001
@@ -141,6 +142,7 @@ typedef struct SDL_HIDAPI_DeviceDriver
// HIDAPI device support
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverCombined;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGameCube;
+extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGIP;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverJoyCons;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverLuna;
extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverNintendoClassic;
diff --git a/src/joystick/usb_ids.h b/src/joystick/usb_ids.h
index 747072139c..6692cd412d 100644
--- a/src/joystick/usb_ids.h
+++ b/src/joystick/usb_ids.h
@@ -64,6 +64,8 @@
#define USB_PRODUCT_ASTRO_C40_XBOX360 0x0024
#define USB_PRODUCT_BACKBONE_ONE_IOS 0x0103
#define USB_PRODUCT_BACKBONE_ONE_IOS_PS5 0x0104
+#define USB_PRODUCT_BDA_XB1_CLASSIC 0x581a
+#define USB_PRODUCT_BDA_XB1_FIGHTPAD 0x791a
#define USB_PRODUCT_GOOGLE_STADIA_CONTROLLER 0x9400
#define USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER1 0x1843
#define USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER2 0x1844
@@ -93,6 +95,7 @@
#define USB_PRODUCT_NINTENDO_WII_REMOTE2 0x0330
#define USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V103 0x7210
#define USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V104 0x7214
+#define USB_PRODUCT_PDP_ROCK_CANDY 0x0246
#define USB_PRODUCT_RAZER_ATROX 0x0a00
#define USB_PRODUCT_RAZER_KITSUNE 0x1012
#define USB_PRODUCT_RAZER_PANTHERA 0x0401
@@ -118,6 +121,7 @@
#define USB_PRODUCT_SONY_DS4_STRIKEPAD 0x05c5
#define USB_PRODUCT_SONY_DS5 0x0ce6
#define USB_PRODUCT_SONY_DS5_EDGE 0x0df2
+#define USB_PRODUCT_STEALTH_ULTRA_WIRED 0x7073
#define USB_PRODUCT_SWITCH_RETROBIT_CONTROLLER 0x0575
#define USB_PRODUCT_THRUSTMASTER_ESWAPX_PRO_PS4 0xd00e
#define USB_PRODUCT_VALVE_STEAM_CONTROLLER_DONGLE 0x1142