SDL: Added support for the 8BitDo Ultimate 2C Wireless controller via HIDAPI

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