From 650aceb60933c5a81a778794dddb7d0faf6bbbb6 Mon Sep 17 00:00:00 2001 From: Kushagra Shukla Date: Sun, 15 Mar 2026 03:50:29 +0530 Subject: [PATCH] Backport 8BitDo HIDAPI Driver to SDL2 (#15207) --- Makefile.os2 | 2 +- Makefile.w32 | 2 +- VisualC-GDK/SDL/SDL.vcxproj | 1 + VisualC-GDK/SDL/SDL.vcxproj.filters | 3 + VisualC/SDL/SDL.vcxproj | 1 + VisualC/SDL/SDL.vcxproj.filters | 3 + include/SDL_hints.h | 13 + src/SDL_utils.c | 21 + src/SDL_utils_c.h | 3 + src/joystick/SDL_gamecontroller.c | 15 + src/joystick/hidapi/SDL_hidapi_8bitdo.c | 708 +++++++++++++++++++++ src/joystick/hidapi/SDL_hidapijoystick.c | 3 + src/joystick/hidapi/SDL_hidapijoystick_c.h | 2 + src/joystick/iphoneos/SDL_mfijoystick.m | 8 +- src/joystick/usb_ids.h | 8 + 15 files changed, 790 insertions(+), 3 deletions(-) create mode 100644 src/joystick/hidapi/SDL_hidapi_8bitdo.c diff --git a/Makefile.os2 b/Makefile.os2 index 113ae2ac74..5c60af1d0f 100644 --- a/Makefile.os2 +++ b/Makefile.os2 @@ -94,7 +94,7 @@ SRCS+= SDL_systimer.c SRCS+= SDL_sysloadso.c SRCS+= SDL_sysfilesystem.c SRCS+= SDL_os2joystick.c SDL_syshaptic.c SDL_sysjoystick.c SDL_virtualjoystick.c -SRCS+= SDL_hidapijoystick.c SDL_hidapi_rumble.c SDL_hidapi_combined.c SDL_hidapi_gamecube.c SDL_hidapi_luna.c SDL_hidapi_ps3.c SDL_hidapi_ps4.c SDL_hidapi_ps5.c SDL_hidapi_shield.c SDL_hidapi_stadia.c SDL_hidapi_switch.c SDL_hidapi_wii.c SDL_hidapi_xbox360.c SDL_hidapi_xbox360w.c SDL_hidapi_xboxone.c SDL_hidapi_steam.c SDL_hidapi_steamdeck.c SDL_steam_virtual_gamepad.c +SRCS+= SDL_hidapijoystick.c SDL_hidapi_rumble.c SDL_hidapi_8bitdo.c SDL_hidapi_combined.c SDL_hidapi_gamecube.c SDL_hidapi_luna.c SDL_hidapi_ps3.c SDL_hidapi_ps4.c SDL_hidapi_ps5.c SDL_hidapi_shield.c SDL_hidapi_stadia.c SDL_hidapi_switch.c SDL_hidapi_wii.c SDL_hidapi_xbox360.c SDL_hidapi_xbox360w.c SDL_hidapi_xboxone.c SDL_hidapi_steam.c SDL_hidapi_steamdeck.c SDL_steam_virtual_gamepad.c SRCS+= SDL_dummyaudio.c SDL_diskaudio.c SRCS+= SDL_nullvideo.c SDL_nullframebuffer.c SDL_nullevents.c SRCS+= SDL_dummysensor.c diff --git a/Makefile.w32 b/Makefile.w32 index a3901beb2b..f49ea2a797 100644 --- a/Makefile.w32 +++ b/Makefile.w32 @@ -73,7 +73,7 @@ SRCS+= SDL_systimer.c SRCS+= SDL_sysloadso.c SRCS+= SDL_sysfilesystem.c SRCS+= SDL_syshaptic.c SDL_sysjoystick.c SDL_virtualjoystick.c -SRCS+= SDL_hidapijoystick.c SDL_hidapi_rumble.c SDL_hidapi_combined.c SDL_hidapi_gamecube.c SDL_hidapi_luna.c SDL_hidapi_ps3.c SDL_hidapi_ps4.c SDL_hidapi_ps5.c SDL_hidapi_shield.c SDL_hidapi_stadia.c SDL_hidapi_switch.c SDL_hidapi_wii.c SDL_hidapi_xbox360.c SDL_hidapi_xbox360w.c SDL_hidapi_xboxone.c SDL_hidapi_steam.c SDL_hidapi_steamdeck.c +SRCS+= SDL_hidapijoystick.c SDL_hidapi_rumble.c SDL_hidapi_8bitdo.c SDL_hidapi_combined.c SDL_hidapi_gamecube.c SDL_hidapi_luna.c SDL_hidapi_ps3.c SDL_hidapi_ps4.c SDL_hidapi_ps5.c SDL_hidapi_shield.c SDL_hidapi_stadia.c SDL_hidapi_switch.c SDL_hidapi_wii.c SDL_hidapi_xbox360.c SDL_hidapi_xbox360w.c SDL_hidapi_xboxone.c SDL_hidapi_steam.c SDL_hidapi_steamdeck.c SRCS+= SDL_dummyaudio.c SDL_diskaudio.c SRCS+= SDL_nullvideo.c SDL_nullframebuffer.c SDL_nullevents.c SRCS+= SDL_dummysensor.c diff --git a/VisualC-GDK/SDL/SDL.vcxproj b/VisualC-GDK/SDL/SDL.vcxproj index 1a1676a4ee..a107d9a4ba 100644 --- a/VisualC-GDK/SDL/SDL.vcxproj +++ b/VisualC-GDK/SDL/SDL.vcxproj @@ -621,6 +621,7 @@ + diff --git a/VisualC-GDK/SDL/SDL.vcxproj.filters b/VisualC-GDK/SDL/SDL.vcxproj.filters index 56494d4831..5f359bce66 100644 --- a/VisualC-GDK/SDL/SDL.vcxproj.filters +++ b/VisualC-GDK/SDL/SDL.vcxproj.filters @@ -1054,6 +1054,9 @@ joystick\dummy + + joystick\hidapi + joystick\hidapi diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj index b068b3330c..6eee6e7602 100644 --- a/VisualC/SDL/SDL.vcxproj +++ b/VisualC/SDL/SDL.vcxproj @@ -473,6 +473,7 @@ + diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters index 60899f55c4..5a1a085c5f 100644 --- a/VisualC/SDL/SDL.vcxproj.filters +++ b/VisualC/SDL/SDL.vcxproj.filters @@ -1045,6 +1045,9 @@ joystick\dummy + + joystick\hidapi + joystick\hidapi diff --git a/include/SDL_hints.h b/include/SDL_hints.h index 0996407bcd..925ff22ccf 100644 --- a/include/SDL_hints.h +++ b/include/SDL_hints.h @@ -827,6 +827,19 @@ extern "C" { */ #define SDL_HINT_JOYSTICK_HIDAPI "SDL_JOYSTICK_HIDAPI" +/** + * A variable controlling whether the HIDAPI driver for 8BitDo 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. + */ +#define SDL_HINT_JOYSTICK_HIDAPI_8BITDO "SDL_JOYSTICK_HIDAPI_8BITDO" + /** * A variable controlling whether the HIDAPI driver for Nintendo GameCube * controllers should be used. diff --git a/src/SDL_utils.c b/src/SDL_utils.c index 2174777e36..b11e57aa52 100644 --- a/src/SDL_utils.c +++ b/src/SDL_utils.c @@ -49,4 +49,25 @@ int SDL_powerof2(int x) return value; } +SDL_bool SDL_startswith(const char *string, const char *prefix) +{ + if (SDL_strncmp(string, prefix, SDL_strlen(prefix)) == 0) { + return SDL_TRUE; + } + return SDL_FALSE; +} + +SDL_bool SDL_endswith(const char *string, const char *suffix) +{ + size_t string_length = string ? SDL_strlen(string) : 0; + size_t suffix_length = suffix ? SDL_strlen(suffix) : 0; + + if (suffix_length > 0 && suffix_length <= string_length) { + if (SDL_memcmp(string + string_length - suffix_length, suffix, suffix_length) == 0) { + return SDL_TRUE; + } + } + return SDL_FALSE; +} + /* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/SDL_utils_c.h b/src/SDL_utils_c.h index 64db40a109..6b46e71e05 100644 --- a/src/SDL_utils_c.h +++ b/src/SDL_utils_c.h @@ -27,6 +27,9 @@ /* Return the smallest power of 2 greater than or equal to 'x' */ extern int SDL_powerof2(int x); +extern SDL_bool SDL_startswith(const char *string, const char *prefix); +extern SDL_bool SDL_endswith(const char *string, const char *suffix); + #endif /* SDL_utils_h_ */ /* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/joystick/SDL_gamecontroller.c b/src/joystick/SDL_gamecontroller.c index 605889fba2..cb5bdd9bf1 100644 --- a/src/joystick/SDL_gamecontroller.c +++ b/src/joystick/SDL_gamecontroller.c @@ -593,6 +593,21 @@ static ControllerMapping_t *SDL_CreateMappingForHIDAPIController(SDL_JoystickGUI } break; } + } else if (vendor == USB_VENDOR_8BITDO && + (product == USB_PRODUCT_8BITDO_SF30_PRO || + product == USB_PRODUCT_8BITDO_SF30_PRO_BT || + product == USB_PRODUCT_8BITDO_SN30_PRO || + product == USB_PRODUCT_8BITDO_SN30_PRO_BT || + product == USB_PRODUCT_8BITDO_PRO_2 || + product == USB_PRODUCT_8BITDO_PRO_2_BT || + product == USB_PRODUCT_8BITDO_PRO_3)) { + SDL_strlcat(mapping_string, "a:b1,b:b0,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,", sizeof(mapping_string)); + + if (product == USB_PRODUCT_8BITDO_PRO_2 || product == USB_PRODUCT_8BITDO_PRO_2_BT) { + SDL_strlcat(mapping_string, "paddle1:b14,paddle2:b13,", sizeof(mapping_string)); + } else if (product == USB_PRODUCT_8BITDO_PRO_3) { + SDL_strlcat(mapping_string, "paddle1:b12,paddle2:b11,paddle3:b14,paddle4:b13,", sizeof(mapping_string)); + } } else { /* All other controllers have the standard set of 19 buttons and 6 axes */ SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", sizeof(mapping_string)); diff --git a/src/joystick/hidapi/SDL_hidapi_8bitdo.c b/src/joystick/hidapi/SDL_hidapi_8bitdo.c new file mode 100644 index 0000000000..0f6a1f2502 --- /dev/null +++ b/src/joystick/hidapi/SDL_hidapi_8bitdo.c @@ -0,0 +1,708 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2026 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_timer.h" +#include "../SDL_sysjoystick.h" +#include "SDL_hidapijoystick_c.h" +#include "SDL_power.h" +#include "SDL_hidapi_rumble.h" + +#ifdef SDL_JOYSTICK_HIDAPI_8BITDO + +// Define this if you want to log all packets from the controller +#if 0 +#define DEBUG_8BITDO_PROTOCOL +#endif + +enum +{ + SDL_CONTROLLER_BUTTON_8BITDO_L4 = 11, + SDL_CONTROLLER_BUTTON_8BITDO_R4, + SDL_CONTROLLER_BUTTON_8BITDO_PL, + SDL_CONTROLLER_BUTTON_8BITDO_PR, +}; + +#define SDL_8BITDO_FEATURE_REPORTID_ENABLE_SDL_REPORTID 0x06 +#define SDL_8BITDO_REPORTID_SDL_REPORTID 0x04 +#define SDL_8BITDO_REPORTID_NOT_SUPPORTED_SDL_REPORTID 0x03 +#define SDL_8BITDO_BT_REPORTID_SDL_REPORTID 0x01 + +#define SDL_8BITDO_SENSOR_TIMESTAMP_ENABLE 0xAA +#define ABITDO_ACCEL_SCALE 4096.f +#define ABITDO_GYRO_MAX_DEGREES_PER_SECOND 2000.f + +#define LOAD32(A, B, C, D) ((((Uint32)(A)) << 0) | \ + (((Uint32)(B)) << 8) | \ + (((Uint32)(C)) << 16) | \ + (((Uint32)(D)) << 24)) + +typedef struct +{ + SDL_bool sensors_supported; + SDL_bool sensors_enabled; + SDL_bool touchpad_01_supported; + SDL_bool touchpad_02_supported; + SDL_bool rumble_supported; + SDL_bool rumble_type; + SDL_bool rgb_supported; + SDL_bool player_led_supported; + SDL_bool powerstate_supported; + SDL_bool sensor_timestamp_supported; + Uint8 serial[6]; + Uint16 version; + Uint16 version_beta; + float accelScale; + float gyroScale; + Uint8 last_state[USB_PACKET_LENGTH]; + Uint64 sensor_timestamp; // Nanoseconds. Simulate onboard clock. Different models have different rates vs different connection styles. + Uint64 sensor_timestamp_interval; + Uint32 last_tick; +} SDL_Driver8BitDo_Context; + +#pragma pack(push, 1) +typedef struct +{ + SDL_bool sensors_supported; + SDL_bool touchpad_01_supported; + SDL_bool touchpad_02_supported; + SDL_bool rumble_supported; + SDL_bool rumble_type; + SDL_bool rgb_supported; + Uint8 device_type; + Uint8 serial[6]; + Uint16 version; + Uint16 version_beta; + Uint16 pid; +} ABITDO_DEVICE_INFO; + +typedef struct +{ + // Accelerometer values + short sAccelX; + short sAccelY; + short sAccelZ; + + // Gyroscope values + short sGyroX; + short sGyroY; + short sGyroZ; +} ABITDO_SENSORS; + +#pragma pack(pop) + +static void HIDAPI_Driver8BitDo_RegisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_8BITDO, callback, userdata); +} + +static void HIDAPI_Driver8BitDo_UnregisterHints(SDL_HintCallback callback, void *userdata) +{ + SDL_DelHintCallback(SDL_HINT_JOYSTICK_HIDAPI_8BITDO, callback, userdata); +} + +static SDL_bool HIDAPI_Driver8BitDo_IsEnabled(void) +{ + return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_8BITDO, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT)); +} + +static int ReadFeatureReport(SDL_hid_device *dev, Uint8 report_id, Uint8 *report, size_t length) +{ + SDL_memset(report, 0, length); + report[0] = report_id; + return SDL_hid_get_feature_report(dev, report, length); +} + +static SDL_bool HIDAPI_Driver8BitDo_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GameControllerType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) +{ + if (vendor_id == USB_VENDOR_8BITDO) { + switch (product_id) { + case USB_PRODUCT_8BITDO_SF30_PRO: + case USB_PRODUCT_8BITDO_SF30_PRO_BT: + case USB_PRODUCT_8BITDO_SN30_PRO: + case USB_PRODUCT_8BITDO_SN30_PRO_BT: + case USB_PRODUCT_8BITDO_PRO_2: + case USB_PRODUCT_8BITDO_PRO_2_BT: + case USB_PRODUCT_8BITDO_PRO_3: + case USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS: + return SDL_TRUE; + default: + break; + } + } + return SDL_FALSE; +} + +static SDL_bool HIDAPI_Driver8BitDo_InitDevice(SDL_HIDAPI_Device *device) +{ + SDL_Driver8BitDo_Context *ctx = (SDL_Driver8BitDo_Context *)SDL_calloc(1, sizeof(*ctx)); + int attempt; + if (!ctx) { + return SDL_FALSE; + } + device->context = ctx; + + if (device->product_id == USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS) { + // The Ultimate 2 Wireless v1.02 firmware has 12 byte reports, v1.03 firmware has 34 byte reports + const int ULTIMATE2_WIRELESS_V103_REPORT_SIZE = 34; + const int MAX_ATTEMPTS = 3; + + for (attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) { + Uint8 data[USB_PACKET_LENGTH]; + int size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 80); + if (size == 0) { + // Try again + continue; + } + if (size >= ULTIMATE2_WIRELESS_V103_REPORT_SIZE) { + ctx->sensors_supported = SDL_TRUE; + ctx->rumble_supported = SDL_TRUE; + ctx->powerstate_supported = SDL_TRUE; + } + break; + } + } else { + Uint8 data[USB_PACKET_LENGTH]; + const int MAX_ATTEMPTS = 5; + for (attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) { + int size = ReadFeatureReport(device->dev, SDL_8BITDO_FEATURE_REPORTID_ENABLE_SDL_REPORTID, data, sizeof(data)); + if (size > 0) { +#ifdef DEBUG_8BITDO_PROTOCOL + HIDAPI_DumpPacket("8BitDo features packet: size = %d", data, size); +#endif + ctx->sensors_supported = SDL_TRUE; + ctx->rumble_supported = SDL_TRUE; + ctx->powerstate_supported = SDL_TRUE; + + if (size >= 14 && data[13] == SDL_8BITDO_SENSOR_TIMESTAMP_ENABLE) { + ctx->sensor_timestamp_supported = SDL_TRUE; + } + + // Set the serial number to the Bluetooth MAC address + if (size >= 12 && data[10] != 0) { + char serial[18]; + (void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x", + data[10], data[9], data[8], data[7], data[6], data[5]); + HIDAPI_SetDeviceSerial(device, serial); + } + break; + } + + // Try again + SDL_Delay(10); + } + } + + if (device->product_id == USB_PRODUCT_8BITDO_SF30_PRO || device->product_id == USB_PRODUCT_8BITDO_SF30_PRO_BT) { + HIDAPI_SetDeviceName(device, "8BitDo SF30 Pro"); + } else if (device->product_id == USB_PRODUCT_8BITDO_SN30_PRO || device->product_id == USB_PRODUCT_8BITDO_SN30_PRO_BT) { + HIDAPI_SetDeviceName(device, "8BitDo SN30 Pro"); + } else if (device->product_id == USB_PRODUCT_8BITDO_PRO_2 || device->product_id == USB_PRODUCT_8BITDO_PRO_2_BT) { + HIDAPI_SetDeviceName(device, "8BitDo Pro 2"); + } else if (device->product_id == USB_PRODUCT_8BITDO_PRO_3) { + HIDAPI_SetDeviceName(device, "8BitDo Pro 3"); + } + + return HIDAPI_JoystickConnected(device, NULL); +} + +static int HIDAPI_Driver8BitDo_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id) +{ + return -1; +} + +static void HIDAPI_Driver8BitDo_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) +{ +} + +static Uint64 HIDAPI_Driver8BitDo_GetIMURateForProductID(SDL_HIDAPI_Device *device) +{ + SDL_Driver8BitDo_Context *ctx = (SDL_Driver8BitDo_Context *)device->context; + + // TODO: If sensor time stamp is sent, these fixed settings from observation can be replaced + switch (device->product_id) { + case USB_PRODUCT_8BITDO_SF30_PRO: + case USB_PRODUCT_8BITDO_SF30_PRO_BT: + case USB_PRODUCT_8BITDO_SN30_PRO: + case USB_PRODUCT_8BITDO_SN30_PRO_BT: + if (device->is_bluetooth) { + // Note, This is estimated by observation of Bluetooth packets received in the testcontroller tool + return 70; // Observed to be anywhere between 60-90 hz. Possibly lossy in current state + } else if (ctx->sensor_timestamp_supported) { + // This firmware appears to update at 200 Hz over USB + return 200; + } else { + // This firmware appears to update at 100 Hz over USB + return 100; + } + case USB_PRODUCT_8BITDO_PRO_2: + case USB_PRODUCT_8BITDO_PRO_2_BT: // Note, labeled as "BT" but appears this way when wired. + case USB_PRODUCT_8BITDO_PRO_3: + if (device->is_bluetooth) { + // Note, This is estimated by observation of Bluetooth packets received in the testcontroller tool + return 85; // Observed Bluetooth packet rate seems to be 80-90hz + } else if (ctx->sensor_timestamp_supported) { + // This firmware appears to update at 200 Hz over USB + return 200; + } else { + // This firmware appears to update at 100 Hz over USB + return 100; + } + case USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS: + if (device->is_bluetooth) { + // Note, This is estimated by observation of Bluetooth packets received in the testcontroller tool + return 120; // Observed Bluetooth packet rate seems to be 120hz + } else { + // This firmware appears to update at 1000 Hz over USB dongle + return 1000; + } + default: + return 120; + } +} + +#ifndef DEG2RAD +#define DEG2RAD(x) ((float)(x) * (float)(M_PI / 180.f)) +#endif + +static SDL_bool HIDAPI_Driver8BitDo_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_Driver8BitDo_Context *ctx = (SDL_Driver8BitDo_Context *)device->context; + + SDL_AssertJoysticksLocked(); + + SDL_zeroa(ctx->last_state); + + // Initialize the joystick capabilities + if (device->product_id == USB_PRODUCT_8BITDO_PRO_2 || + device->product_id == USB_PRODUCT_8BITDO_PRO_2_BT || + device->product_id == USB_PRODUCT_8BITDO_PRO_3 || + device->product_id == USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS) { + // This controller has additional buttons + joystick->nbuttons = 20; + } else { + joystick->nbuttons = 11; + } + joystick->naxes = SDL_CONTROLLER_AXIS_MAX; + joystick->nhats = 1; + + if (ctx->sensors_supported) { + + // Different 8Bitdo controllers in different connection modes have different polling rates. + const Uint64 imu_polling_rate = HIDAPI_Driver8BitDo_GetIMURateForProductID(device); + ctx->sensor_timestamp_interval = 1000000000LL / imu_polling_rate; + + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, (float)imu_polling_rate); + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, (float)imu_polling_rate); + + ctx->accelScale = SDL_STANDARD_GRAVITY / ABITDO_ACCEL_SCALE; + // Hardware senses +/- N Degrees per second mapped to +/- INT16_MAX + ctx->gyroScale = DEG2RAD(ABITDO_GYRO_MAX_DEGREES_PER_SECOND) / INT16_MAX; + } + + return SDL_TRUE; +} + +static int HIDAPI_Driver8BitDo_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) +{ + SDL_Driver8BitDo_Context *ctx = (SDL_Driver8BitDo_Context *)device->context; + if (ctx->rumble_supported) { + Uint8 rumble_packet[5] = { 0x05, 0x00, 0x00, 0x00, 0x00 }; + rumble_packet[1] = low_frequency_rumble >> 8; + rumble_packet[2] = high_frequency_rumble >> 8; + + if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) { + return SDL_SetError("Couldn't send rumble packet"); + } + return 0; + } else { + return SDL_Unsupported(); + } +} + +static int HIDAPI_Driver8BitDo_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) +{ + return SDL_Unsupported(); +} + +static Uint32 HIDAPI_Driver8BitDo_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_Driver8BitDo_Context *ctx = (SDL_Driver8BitDo_Context *)device->context; + Uint32 caps = 0; + if (ctx->rumble_supported) { + caps |= SDL_JOYCAP_RUMBLE; + } + if (ctx->rgb_supported) { + caps |= SDL_JOYCAP_LED; + } + return caps; +} + +static int HIDAPI_Driver8BitDo_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) +{ + return SDL_Unsupported(); +} + +static int HIDAPI_Driver8BitDo_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size) +{ + return SDL_Unsupported(); +} + +static int HIDAPI_Driver8BitDo_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, SDL_bool enabled) +{ + SDL_Driver8BitDo_Context *ctx = (SDL_Driver8BitDo_Context *)device->context; + if (ctx->sensors_supported) { + ctx->sensors_enabled = enabled; + return 0; + } + return SDL_Unsupported(); +} + +static void HIDAPI_Driver8BitDo_HandleOldStatePacket(SDL_Joystick *joystick, SDL_Driver8BitDo_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis; + + if (ctx->last_state[2] != data[2]) { + Uint8 hat; + + switch (data[2]) { + case 0: + hat = SDL_HAT_UP; + break; + case 1: + hat = SDL_HAT_RIGHTUP; + break; + case 2: + hat = SDL_HAT_RIGHT; + break; + case 3: + hat = SDL_HAT_RIGHTDOWN; + break; + case 4: + hat = SDL_HAT_DOWN; + break; + case 5: + hat = SDL_HAT_LEFTDOWN; + break; + case 6: + hat = SDL_HAT_LEFT; + break; + case 7: + hat = SDL_HAT_LEFTUP; + break; + default: + hat = SDL_HAT_CENTERED; + break; + } + + SDL_PrivateJoystickHat(joystick, 0, hat); + } + + if (ctx->last_state[0] != data[0]) { + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_A, ((data[0] & 0x01) != 0)); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_B, ((data[0] & 0x02) != 0)); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_X, ((data[0] & 0x08) != 0)); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_Y, ((data[0] & 0x10) != 0)); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSHOULDER, ((data[0] & 0x40) != 0)); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, ((data[0] & 0x80) != 0)); + } + + if (ctx->last_state[1] != data[1]) { + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_GUIDE, ((data[1] & 0x10) != 0)); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_BACK, ((data[1] & 0x04) != 0)); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_START, ((data[1] & 0x08) != 0)); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSTICK, ((data[1] & 0x20) != 0)); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSTICK, ((data[1] & 0x40) != 0)); + + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERLEFT, (data[1] & 0x01) ? SDL_MAX_SINT16 : SDL_MIN_SINT16); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, (data[1] & 0x02) ? SDL_MAX_SINT16 : SDL_MIN_SINT16); + } + +#define READ_STICK_AXIS(offset) \ + (data[offset] == 0x7f ? 0 : (Sint16)HIDAPI_RemapVal((float)((int)data[offset] - 0x7f), -0x7f, 0xff - 0x7f, SDL_MIN_SINT16, SDL_MAX_SINT16)) + { + axis = READ_STICK_AXIS(3); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTX, axis); + axis = READ_STICK_AXIS(4); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTY, axis); + axis = READ_STICK_AXIS(5); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTX, axis); + axis = READ_STICK_AXIS(6); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTY, axis); + } +#undef READ_STICK_AXIS + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} + +#ifndef SDL_US_TO_NS +#define SDL_US_TO_NS(US) (((Uint64)(US)) * 1000) // nanoseconds per microsecond +#endif + +static void HIDAPI_Driver8BitDo_HandleStatePacket(SDL_Joystick *joystick, SDL_Driver8BitDo_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis; + + switch (data[0]) { + case SDL_8BITDO_REPORTID_NOT_SUPPORTED_SDL_REPORTID: // Firmware without enhanced mode + case SDL_8BITDO_REPORTID_SDL_REPORTID: // Enhanced mode USB report + case SDL_8BITDO_BT_REPORTID_SDL_REPORTID: // Enhanced mode Bluetooth report + break; + default: + // We don't know how to handle this report + return; + } + + if (ctx->last_state[1] != data[1]) { + Uint8 hat; + + switch (data[1]) { + case 0: + hat = SDL_HAT_UP; + break; + case 1: + hat = SDL_HAT_RIGHTUP; + break; + case 2: + hat = SDL_HAT_RIGHT; + break; + case 3: + hat = SDL_HAT_RIGHTDOWN; + break; + case 4: + hat = SDL_HAT_DOWN; + break; + case 5: + hat = SDL_HAT_LEFTDOWN; + break; + case 6: + hat = SDL_HAT_LEFT; + break; + case 7: + hat = SDL_HAT_LEFTUP; + break; + default: + hat = SDL_HAT_CENTERED; + break; + } + + SDL_PrivateJoystickHat(joystick, 0, hat); + } + + if (ctx->last_state[8] != data[8]) { + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_A, ((data[8] & 0x01) != 0)); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_B, ((data[8] & 0x02) != 0)); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_X, ((data[8] & 0x08) != 0)); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_Y, ((data[8] & 0x10) != 0)); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSHOULDER, ((data[8] & 0x40) != 0)); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, ((data[8] & 0x80) != 0)); + + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_8BITDO_PL, ((data[8] & 0x20) != 0)); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_8BITDO_PR, ((data[8] & 0x04) != 0)); + } + + if (ctx->last_state[9] != data[9]) { + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_GUIDE, ((data[9] & 0x10) != 0)); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_BACK, ((data[9] & 0x04) != 0)); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_START, ((data[9] & 0x08) != 0)); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSTICK, ((data[9] & 0x20) != 0)); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSTICK, ((data[9] & 0x40) != 0)); + } + + if (size > 10 && ctx->last_state[10] != data[10]) { + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_8BITDO_L4, ((data[10] & 0x01) != 0)); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_8BITDO_R4, ((data[10] & 0x02) != 0)); + } + +#define READ_STICK_AXIS(offset) \ + (data[offset] == 0x7f ? 0 : (Sint16)HIDAPI_RemapVal((float)((int)data[offset] - 0x7f), -0x7f, 0xff - 0x7f, SDL_MIN_SINT16, SDL_MAX_SINT16)) + { + axis = READ_STICK_AXIS(2); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTX, axis); + axis = READ_STICK_AXIS(3); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTY, axis); + axis = READ_STICK_AXIS(4); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTX, axis); + axis = READ_STICK_AXIS(5); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTY, axis); + } +#undef READ_STICK_AXIS + +#define READ_TRIGGER_AXIS(offset) \ + (Sint16)(((int)data[offset] * 257) - 32768) + { + axis = READ_TRIGGER_AXIS(7); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERLEFT, axis); + axis = READ_TRIGGER_AXIS(6); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, axis); + } +#undef READ_TRIGGER_AXIS + + if (ctx->powerstate_supported) { + Uint8 status = data[14] >> 7; + Uint8 level = (data[14] & 0x7f); + + switch (status) { + case 0: + if (level == 0) { + SDL_PrivateJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_EMPTY); + } else if (level <= 20) { + SDL_PrivateJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_LOW); + } else if (level <= 70) { + SDL_PrivateJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_MEDIUM); + } else if (level <= 100) { + SDL_PrivateJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_FULL); + } + break; + case 1: + SDL_PrivateJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_WIRED); + break; + default: + SDL_PrivateJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_UNKNOWN); + } + } + + if (ctx->sensors_enabled) { + Uint64 sensor_timestamp; + float values[3]; + ABITDO_SENSORS *sensors = (ABITDO_SENSORS *)&data[15]; + + if (ctx->sensor_timestamp_supported) { + Uint32 delta; + Uint32 tick = LOAD32(data[27], data[28], data[29], data[30]); + + if (ctx->last_tick) { + if (ctx->last_tick < tick) { + delta = (tick - ctx->last_tick); + } else { + delta = (SDL_MAX_UINT32 - ctx->last_tick + tick + 1); + } + // Sanity check the delta value + if (delta < 100000) { + ctx->sensor_timestamp_interval = SDL_US_TO_NS(delta); + } + } + ctx->last_tick = tick; + } + + // Note: we cannot use the time stamp of the receiving computer due to packet delay creating "spiky" timings. + // The imu time stamp is intended to be the sample time of the on-board hardware. + // In the absence of time stamp data from the data[], we can simulate that by + // advancing a time stamp by the observed/known imu clock rate. This is 8ms = 125 Hz + sensor_timestamp = ctx->sensor_timestamp; + ctx->sensor_timestamp += ctx->sensor_timestamp_interval; + + // This device's IMU values are reported differently from SDL + // Thus we perform a rotation of the coordinate system to match the SDL standard. + + // By observation of this device: + // Hardware x is reporting roll (rotation about the power jack's axis) + // Hardware y is reporting pitch (rotation about the horizontal axis) + // Hardware z is reporting yaw (rotation about the joysticks' center axis) + values[0] = -sensors->sGyroY * ctx->gyroScale; // Rotation around pitch axis + values[1] = sensors->sGyroZ * ctx->gyroScale; // Rotation around yaw axis + values[2] = -sensors->sGyroX * ctx->gyroScale; // Rotation around roll axis + SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_GYRO, sensor_timestamp, values, 3); + + // By observation of this device: + // Accelerometer X is positive when front of the controller points toward the sky. + // Accelerometer y is positive when left side of the controller points toward the sky. + // Accelerometer Z is positive when sticks point toward the sky. + values[0] = -sensors->sAccelY * ctx->accelScale; // Acceleration along pitch axis + values[1] = sensors->sAccelZ * ctx->accelScale; // Acceleration along yaw axis + values[2] = -sensors->sAccelX * ctx->accelScale; // Acceleration along roll axis + SDL_PrivateJoystickSensor(joystick, SDL_SENSOR_ACCEL, sensor_timestamp, values, 3); + } + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} + +static SDL_bool HIDAPI_Driver8BitDo_UpdateDevice(SDL_HIDAPI_Device *device) +{ + SDL_Driver8BitDo_Context *ctx = (SDL_Driver8BitDo_Context *)device->context; + SDL_Joystick *joystick = NULL; + Uint8 data[USB_PACKET_LENGTH]; + int size = 0; + + if (device->num_joysticks > 0) { + joystick = SDL_JoystickFromInstanceID(device->joysticks[0]); + } else { + return SDL_FALSE; + } + + while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) { +#ifdef DEBUG_8BITDO_PROTOCOL + HIDAPI_DumpPacket("8BitDo packet: size = %d", data, size); +#endif + if (!joystick) { + continue; + } + + if (size == 9) { + // Old firmware USB report for the SF30 Pro and SN30 Pro controllers + HIDAPI_Driver8BitDo_HandleOldStatePacket(joystick, ctx, data, size); + } else { + HIDAPI_Driver8BitDo_HandleStatePacket(joystick, ctx, data, size); + } + } + + if (size < 0) { + // Read error, device is disconnected + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + return (size >= 0); +} + +static void HIDAPI_Driver8BitDo_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ +} + +static void HIDAPI_Driver8BitDo_FreeDevice(SDL_HIDAPI_Device *device) +{ +} + +SDL_HIDAPI_DeviceDriver SDL_HIDAPI_Driver8BitDo = { + SDL_HINT_JOYSTICK_HIDAPI_8BITDO, + SDL_TRUE, + HIDAPI_Driver8BitDo_RegisterHints, + HIDAPI_Driver8BitDo_UnregisterHints, + HIDAPI_Driver8BitDo_IsEnabled, + HIDAPI_Driver8BitDo_IsSupportedDevice, + HIDAPI_Driver8BitDo_InitDevice, + HIDAPI_Driver8BitDo_GetDevicePlayerIndex, + HIDAPI_Driver8BitDo_SetDevicePlayerIndex, + HIDAPI_Driver8BitDo_UpdateDevice, + HIDAPI_Driver8BitDo_OpenJoystick, + HIDAPI_Driver8BitDo_RumbleJoystick, + HIDAPI_Driver8BitDo_RumbleJoystickTriggers, + HIDAPI_Driver8BitDo_GetJoystickCapabilities, + HIDAPI_Driver8BitDo_SetJoystickLED, + HIDAPI_Driver8BitDo_SendJoystickEffect, + HIDAPI_Driver8BitDo_SetJoystickSensorsEnabled, + HIDAPI_Driver8BitDo_CloseJoystick, + HIDAPI_Driver8BitDo_FreeDevice, +}; + +#endif // SDL_JOYSTICK_HIDAPI_8BITDO + +#endif // SDL_JOYSTICK_HIDAPI diff --git a/src/joystick/hidapi/SDL_hidapijoystick.c b/src/joystick/hidapi/SDL_hidapijoystick.c index 5fb14e3786..77e152a0df 100644 --- a/src/joystick/hidapi/SDL_hidapijoystick.c +++ b/src/joystick/hidapi/SDL_hidapijoystick.c @@ -46,6 +46,9 @@ struct joystick_hwdata }; static SDL_HIDAPI_DeviceDriver *SDL_HIDAPI_drivers[] = { +#ifdef SDL_JOYSTICK_HIDAPI_8BITDO + &SDL_HIDAPI_Driver8BitDo, +#endif #ifdef SDL_JOYSTICK_HIDAPI_GAMECUBE &SDL_HIDAPI_DriverGameCube, #endif diff --git a/src/joystick/hidapi/SDL_hidapijoystick_c.h b/src/joystick/hidapi/SDL_hidapijoystick_c.h index 3ed1f820ff..de22ab8c0e 100644 --- a/src/joystick/hidapi/SDL_hidapijoystick_c.h +++ b/src/joystick/hidapi/SDL_hidapijoystick_c.h @@ -32,6 +32,7 @@ #include "../usb_ids.h" /* This is the full set of HIDAPI drivers available */ +#define SDL_JOYSTICK_HIDAPI_8BITDO #define SDL_JOYSTICK_HIDAPI_GAMECUBE #define SDL_JOYSTICK_HIDAPI_LUNA #define SDL_JOYSTICK_HIDAPI_PS3 @@ -131,6 +132,7 @@ typedef struct _SDL_HIDAPI_DeviceDriver } SDL_HIDAPI_DeviceDriver; /* HIDAPI device support */ +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_Driver8BitDo; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverCombined; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGameCube; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverJoyCons; diff --git a/src/joystick/iphoneos/SDL_mfijoystick.m b/src/joystick/iphoneos/SDL_mfijoystick.m index 4de11ac557..abb90e63da 100644 --- a/src/joystick/iphoneos/SDL_mfijoystick.m +++ b/src/joystick/iphoneos/SDL_mfijoystick.m @@ -19,6 +19,7 @@ 3. This notice may not be removed or altered from any source distribution. */ #include "../../SDL_internal.h" +#include "../../SDL_utils_c.h" /* This is the iOS implementation of the SDL joystick API */ #include "SDL_events.h" @@ -414,7 +415,12 @@ static BOOL IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle (device->is_switch_joycon_pair && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR, 0, "")) || (device->is_stadia && HIDAPI_IsDevicePresent(USB_VENDOR_GOOGLE, USB_PRODUCT_GOOGLE_STADIA_CONTROLLER, 0, "")) || (device->is_switch_joyconL && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT, 0, "")) || - (device->is_switch_joyconR && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT, 0, ""))) { + (device->is_switch_joyconR && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT, 0, "")) || + (SDL_strcmp(name, "8Bitdo SN30 Pro") == 0 && (HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_SN30_PRO, 0, "") || HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_SN30_PRO_BT, 0, ""))) || + (SDL_strcmp(name, "8BitDo Pro 2") == 0 && (HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_PRO_2, 0, "") || HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_PRO_2_BT, 0, ""))) || + (SDL_strcmp(name, "8BitDo Pro 3") == 0 && (HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_PRO_3, 0, ""))) || + (SDL_startswith(name, "8BitDo Ultimate 2 Wireless") && HIDAPI_IsDevicePresent(USB_VENDOR_8BITDO, USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS, 0, "")) + ) { /* The HIDAPI driver is taking care of this device */ return FALSE; } diff --git a/src/joystick/usb_ids.h b/src/joystick/usb_ids.h index dee9f5b13a..373dd1f420 100644 --- a/src/joystick/usb_ids.h +++ b/src/joystick/usb_ids.h @@ -58,6 +58,14 @@ #define USB_VENDOR_VALVE 0x28de #define USB_VENDOR_ZEROPLUS 0x0c12 +#define USB_PRODUCT_8BITDO_SF30_PRO 0x6000 /* B + START */ +#define USB_PRODUCT_8BITDO_SF30_PRO_BT 0x6100 /* B + START */ +#define USB_PRODUCT_8BITDO_SN30_PRO 0x6001 /* B + START */ +#define USB_PRODUCT_8BITDO_SN30_PRO_BT 0x6101 /* B + START */ +#define USB_PRODUCT_8BITDO_PRO_2 0x6003 /* mode switch to D */ +#define USB_PRODUCT_8BITDO_PRO_2_BT 0x6006 /* mode switch to D */ +#define USB_PRODUCT_8BITDO_PRO_3 0x6009 /* mode switch to D */ +#define USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS 0x6012 /* mode switch to BT */ #define USB_PRODUCT_8BITDO_XBOX_CONTROLLER1 0x2002 /* Ultimate Wired Controller for Xbox */ #define USB_PRODUCT_8BITDO_XBOX_CONTROLLER2 0x3106 /* Ultimate Wireless / Pro 2 Wired Controller */ #define USB_PRODUCT_AMAZON_LUNA_CONTROLLER 0x0419