From 00f3a82ada91625e0fe22eb039e8d926d1103913 Mon Sep 17 00:00:00 2001 From: Vicki Pfau 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 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