From 7340b327a6e7f9da939cb122b731509466149ce2 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Mon, 5 Oct 2026 08:46:44 -0700
Subject: [PATCH] Added support for the 8BitDo Ultimate 2C Wireless controller
via HIDAPI
Fixes https://github.com/libsdl-org/SDL/issues/16441
---
src/joystick/SDL_gamepad.c | 2 ++
src/joystick/hidapi/SDL_hidapi_8bitdo.c | 19 +++++++++++++++----
src/joystick/usb_ids.h | 1 +
3 files changed, 18 insertions(+), 4 deletions(-)
diff --git a/src/joystick/SDL_gamepad.c b/src/joystick/SDL_gamepad.c
index 4c5c178fe5202..71dfc07df0e9a 100644
--- a/src/joystick/SDL_gamepad.c
+++ b/src/joystick/SDL_gamepad.c
@@ -1313,6 +1313,8 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid)
}
} else if (vendor == USB_VENDOR_8BITDO && product == USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS) {
SDL_strlcat(mapping_string, "paddle1:b12,paddle2:b11,paddle3:b14,paddle4:b13,", sizeof(mapping_string));
+ } else if (vendor == USB_VENDOR_8BITDO && product == USB_PRODUCT_8BITDO_ULTIMATE2C_WIRELESS) {
+ SDL_strlcat(mapping_string, "paddle1:b12,paddle2:b11,", sizeof(mapping_string));
} else {
switch (SDL_GetGamepadTypeFromGUID(guid, NULL)) {
case SDL_GAMEPAD_TYPE_PS4:
diff --git a/src/joystick/hidapi/SDL_hidapi_8bitdo.c b/src/joystick/hidapi/SDL_hidapi_8bitdo.c
index 8c607c2fb3a2b..a89d2f3503c6d 100644
--- a/src/joystick/hidapi/SDL_hidapi_8bitdo.c
+++ b/src/joystick/hidapi/SDL_hidapi_8bitdo.c
@@ -48,6 +48,8 @@ enum
SDL_GAMEPAD_NUM_8BITDO_ULTIMATE3_BUTTONS,
};
+#define SDL_GAMEPAD_NUM_8BITDO_ULTIMATE2C_BUTTONS (SDL_GAMEPAD_BUTTON_8BITDO_R4 + 1)
+
#define SDL_8BITDO_FEATURE_REPORTID 0x30
#define SDL_8BITDO_FEATURE_REPORTID_ENABLE_SDL_REPORTID 0x06
#define SDL_8BITDO_REPORTID_SDL_REPORTID 0x04
@@ -159,6 +161,7 @@ static bool HIDAPI_Driver8BitDo_IsSupportedDevice(SDL_HIDAPI_Device *device, con
case USB_PRODUCT_8BITDO_PRO_2_BT:
case USB_PRODUCT_8BITDO_PRO_3:
case USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS:
+ case USB_PRODUCT_8BITDO_ULTIMATE2C_WIRELESS:
case USB_PRODUCT_8BITDO_ULTIMATE3:
return true;
default:
@@ -195,6 +198,8 @@ static bool HIDAPI_Driver8BitDo_InitDevice(SDL_HIDAPI_Device *device)
}
break;
}
+ } else if (device->product_id == USB_PRODUCT_8BITDO_ULTIMATE2C_WIRELESS) {
+ // The Ultimate 2C Wireless controller has an 11 byte report with no sensor or rumble support
} else if (device->product_id == USB_PRODUCT_8BITDO_ULTIMATE3) {
// Supported by default
ctx->sensors_supported = true;
@@ -228,8 +233,7 @@ static bool HIDAPI_Driver8BitDo_InitDevice(SDL_HIDAPI_Device *device)
}
SDL_Delay(10);
}
- }
- else {
+ } else {
Uint8 data[USB_PACKET_LENGTH];
const int MAX_ATTEMPTS = 5;
for (int attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) {
@@ -350,6 +354,8 @@ static bool HIDAPI_Driver8BitDo_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joys
device->product_id == USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS) {
// This controller has additional buttons
joystick->nbuttons = SDL_GAMEPAD_NUM_8BITDO_BUTTONS;
+ } else if (device->product_id == USB_PRODUCT_8BITDO_ULTIMATE2C_WIRELESS) {
+ joystick->nbuttons = SDL_GAMEPAD_NUM_8BITDO_ULTIMATE2C_BUTTONS;
} else if (device->product_id == USB_PRODUCT_8BITDO_ULTIMATE3) {
joystick->nbuttons = SDL_GAMEPAD_NUM_8BITDO_ULTIMATE3_BUTTONS;
} else {
@@ -590,8 +596,13 @@ static void HIDAPI_Driver8BitDo_HandleStatePacket(SDL_Joystick *joystick, SDL_Dr
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[8] & 0x40) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[8] & 0x80) != 0));
- SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_8BITDO_PL, ((data[8] & 0x20) != 0));
- SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_8BITDO_PR, ((data[8] & 0x04) != 0));
+ if (joystick->nbuttons > SDL_GAMEPAD_BUTTON_8BITDO_PR) {
+ SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_8BITDO_PL, ((data[8] & 0x20) != 0));
+ SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_8BITDO_PR, ((data[8] & 0x04) != 0));
+ } else if (joystick->nbuttons > SDL_GAMEPAD_BUTTON_8BITDO_R4) {
+ SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_8BITDO_L4, ((data[8] & 0x04) != 0));
+ SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_8BITDO_R4, ((data[8] & 0x20) != 0));
+ }
}
if (ctx->last_state[9] != data[9]) {
diff --git a/src/joystick/usb_ids.h b/src/joystick/usb_ids.h
index 821d069419835..3284955aef124 100644
--- a/src/joystick/usb_ids.h
+++ b/src/joystick/usb_ids.h
@@ -78,6 +78,7 @@
#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_ULTIMATE2C_WIRELESS 0x301b // mode switch to BT
#define USB_PRODUCT_8BITDO_ULTIMATE3 0x202f // mode switch to BT
#define USB_PRODUCT_AMAZON_LUNA_CONTROLLER 0x0419
#define USB_PRODUCT_ASTRO_C40_XBOX360 0x0024