Cleanup 8BitDo HIDAPI support for SF30 Pro and SN30 Pro

This sets the correct number of buttons for older controllers, and adds parsing for older firmware USB reports
This commit is contained in:
Sam Lantinga
2025-05-07 11:53:55 -07:00
parent 89a8cf2505
commit 5bee85408c
5 changed files with 146 additions and 41 deletions

View File

@@ -779,6 +779,20 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid)
} }
break; break;
} }
} else if (vendor == USB_VENDOR_8BITDO &&
(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)) {
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,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", 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 (vendor == USB_VENDOR_8BITDO &&
(product == USB_PRODUCT_8BITDO_SF30_PRO ||
product == USB_PRODUCT_8BITDO_SF30_PRO_BT)) {
// This controller has no guide button
SDL_strlcat(mapping_string, "a:b1,b:b0,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,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,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", sizeof(mapping_string));
} else { } else {
// All other gamepads have the standard set of 19 buttons and 6 axes // All other gamepads have the standard set of 19 buttons and 6 axes
if (SDL_IsJoystickGameCube(vendor, product)) { if (SDL_IsJoystickGameCube(vendor, product)) {
@@ -802,20 +816,20 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid)
SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string)); SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string));
} else if (SDL_IsJoystickGoogleStadiaController(vendor, product)) { } else if (SDL_IsJoystickGoogleStadiaController(vendor, product)) {
// The Google Stadia controller has a share button and a Google Assistant button // The Google Stadia controller has a share button and a Google Assistant button
SDL_strlcat(mapping_string, "misc1:b11,misc2:b12", sizeof(mapping_string)); SDL_strlcat(mapping_string, "misc1:b11,misc2:b12,", sizeof(mapping_string));
} else if (SDL_IsJoystickNVIDIASHIELDController(vendor, product)) { } else if (SDL_IsJoystickNVIDIASHIELDController(vendor, product)) {
// The NVIDIA SHIELD controller has a share button between back and start buttons // The NVIDIA SHIELD controller has a share button between back and start buttons
SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string)); SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string));
if (product == USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V103) { if (product == USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V103) {
// The original SHIELD controller has a touchpad and plus/minus buttons as well // The original SHIELD controller has a touchpad and plus/minus buttons as well
SDL_strlcat(mapping_string, "touchpad:b12,misc2:b13,misc3:b14", sizeof(mapping_string)); SDL_strlcat(mapping_string, "touchpad:b12,misc2:b13,misc3:b14,", sizeof(mapping_string));
} }
} else if (SDL_IsJoystickHoriSteamController(vendor, product)) { } else if (SDL_IsJoystickHoriSteamController(vendor, product)) {
/* The Wireless HORIPad for Steam has QAM, Steam, Capsense L/R Sticks, 2 rear buttons, and 2 misc buttons */ /* The Wireless HORIPad for Steam has QAM, Steam, Capsense L/R Sticks, 2 rear buttons, and 2 misc buttons */
SDL_strlcat(mapping_string, "paddle1:b13,paddle2:b12,paddle3:b15,paddle4:b14,misc2:b11,misc3:b16,misc4:b17", sizeof(mapping_string)); SDL_strlcat(mapping_string, "paddle1:b13,paddle2:b12,paddle3:b15,paddle4:b14,misc2:b11,misc3:b16,misc4:b17,", sizeof(mapping_string));
} else if (SDL_IsJoystick8BitDoController(vendor, product)) { } 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)); SDL_strlcat(mapping_string, "paddle1:b12,paddle2:b11,paddle3:b14,paddle4:b13,", sizeof(mapping_string));
} else { } else {
switch (SDL_GetGamepadTypeFromGUID(guid, NULL)) { switch (SDL_GetGamepadTypeFromGUID(guid, NULL)) {
case SDL_GAMEPAD_TYPE_PS4: case SDL_GAMEPAD_TYPE_PS4:
@@ -1295,7 +1309,7 @@ static bool SDL_PrivateParseGamepadElement(SDL_Gamepad *gamepad, const char *szG
static bool SDL_PrivateParseGamepadConfigString(SDL_Gamepad *gamepad, const char *pchString) static bool SDL_PrivateParseGamepadConfigString(SDL_Gamepad *gamepad, const char *pchString)
{ {
char szGameButton[20]; char szGameButton[20];
char szJoystickButton[20]; char szJoystickButton[128];
bool bGameButton = true; bool bGameButton = true;
int i = 0; int i = 0;
const char *pchPos = pchString; const char *pchPos = pchString;

View File

@@ -3175,24 +3175,6 @@ bool SDL_IsJoystickHoriSteamController(Uint16 vendor_id, Uint16 product_id)
return vendor_id == USB_VENDOR_HORI && (product_id == USB_PRODUCT_HORI_STEAM_CONTROLLER || product_id == USB_PRODUCT_HORI_STEAM_CONTROLLER_BT); return vendor_id == USB_VENDOR_HORI && (product_id == USB_PRODUCT_HORI_STEAM_CONTROLLER || product_id == USB_PRODUCT_HORI_STEAM_CONTROLLER_BT);
} }
bool SDL_IsJoystick8BitDoController(Uint16 vendor_id, Uint16 product_id)
{
if (vendor_id == USB_VENDOR_8BITDO) {
switch (product_id) {
case USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS:
case USB_PRODUCT_8BITDO_SN30_PRO:
case USB_PRODUCT_8BITDO_SN30_PRO_BT:
case USB_PRODUCT_8BITDO_SF30_PRO:
case USB_PRODUCT_8BITDO_PRO_2:
case USB_PRODUCT_8BITDO_PRO_2_BT:
return true;
default:
break;
}
}
return false;
}
bool SDL_IsJoystickSteamDeck(Uint16 vendor_id, Uint16 product_id) bool SDL_IsJoystickSteamDeck(Uint16 vendor_id, Uint16 product_id)
{ {
EControllerType eType = GuessControllerType(vendor_id, product_id); EControllerType eType = GuessControllerType(vendor_id, product_id);

View File

@@ -135,9 +135,6 @@ extern bool SDL_IsJoystickSteamController(Uint16 vendor_id, Uint16 product_id);
// Function to return whether a joystick is a HORI Steam controller // Function to return whether a joystick is a HORI Steam controller
extern bool SDL_IsJoystickHoriSteamController(Uint16 vendor_id, Uint16 product_id); extern bool SDL_IsJoystickHoriSteamController(Uint16 vendor_id, Uint16 product_id);
// Function to return whether a joystick is a 8BitDo controller
extern bool SDL_IsJoystick8BitDoController(Uint16 vendor_id, Uint16 product_id);
// Function to return whether a joystick is a Steam Deck // Function to return whether a joystick is a Steam Deck
extern bool SDL_IsJoystickSteamDeck(Uint16 vendor_id, Uint16 product_id); extern bool SDL_IsJoystickSteamDeck(Uint16 vendor_id, Uint16 product_id);

View File

@@ -126,7 +126,21 @@ static int ReadFeatureReport(SDL_hid_device *dev, Uint8 report_id, Uint8 *report
static bool HIDAPI_Driver8BitDo_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) static bool HIDAPI_Driver8BitDo_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)
{ {
return SDL_IsJoystick8BitDoController(vendor_id, product_id); 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_ULTIMATE2_WIRELESS:
return true;
default:
break;
}
}
return false;
} }
static bool HIDAPI_Driver8BitDo_InitDevice(SDL_HIDAPI_Device *device) static bool HIDAPI_Driver8BitDo_InitDevice(SDL_HIDAPI_Device *device)
@@ -147,21 +161,24 @@ static bool HIDAPI_Driver8BitDo_InitDevice(SDL_HIDAPI_Device *device)
ctx->rumble_supported = true; ctx->rumble_supported = true;
ctx->powerstate_supported = true; ctx->powerstate_supported = true;
} }
} else if (device->product_id == USB_PRODUCT_8BITDO_SN30_PRO || device->product_id == USB_PRODUCT_8BITDO_SN30_PRO_BT || } else {
device->product_id == USB_PRODUCT_8BITDO_SF30_PRO || device->product_id == USB_PRODUCT_8BITDO_PRO_2 ||
device->product_id == USB_PRODUCT_8BITDO_PRO_2_BT) {
Uint8 data[USB_PACKET_LENGTH]; Uint8 data[USB_PACKET_LENGTH];
int size = ReadFeatureReport(device->dev, SDL_8BITDO_FEATURE_REPORTID_ENABLE_SDL_REPORTID, data, sizeof(data)); int size = ReadFeatureReport(device->dev, SDL_8BITDO_FEATURE_REPORTID_ENABLE_SDL_REPORTID, data, sizeof(data));
if (size > 0) { if (size > 0) {
ctx->sensors_supported = true; ctx->sensors_supported = true;
ctx->rumble_supported = true; ctx->rumble_supported = true;
ctx->powerstate_supported = true; ctx->powerstate_supported = true;
} else {
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
"HIDAPI_Driver8BitDo_InitDevice(): Couldn't read feature report 0x06");
} }
} }
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");
}
return HIDAPI_JoystickConnected(device, NULL); return HIDAPI_JoystickConnected(device, NULL);
} }
@@ -187,7 +204,14 @@ static bool HIDAPI_Driver8BitDo_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joys
SDL_zeroa(ctx->last_state); SDL_zeroa(ctx->last_state);
// Initialize the joystick capabilities // Initialize the joystick capabilities
joystick->nbuttons = SDL_GAMEPAD_NUM_8BITDO_BUTTONS; if (device->product_id == USB_PRODUCT_8BITDO_PRO_2 ||
device->product_id == USB_PRODUCT_8BITDO_PRO_2_BT ||
device->product_id == USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS) {
// This controller has additional buttons
joystick->nbuttons = SDL_GAMEPAD_NUM_8BITDO_BUTTONS;
} else {
joystick->nbuttons = 11;
}
joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
joystick->nhats = 1; joystick->nhats = 1;
@@ -257,12 +281,95 @@ static bool HIDAPI_Driver8BitDo_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *dev
} }
return SDL_Unsupported(); return SDL_Unsupported();
} }
static void HIDAPI_Driver8BitDo_HandleOldStatePacket(SDL_Joystick *joystick, SDL_Driver8BitDo_Context *ctx, Uint8 *data, int size)
{
Sint16 axis;
Uint64 timestamp = SDL_GetTicksNS();
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_SendJoystickHat(timestamp, joystick, 0, hat);
}
if (ctx->last_state[0] != data[0]) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[0] & 0x01) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[0] & 0x02) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[0] & 0x08) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[0] & 0x10) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[0] & 0x40) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[0] & 0x80) != 0));
}
if (ctx->last_state[1] != data[1]) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[1] & 0x10) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[1] & 0x04) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[1] & 0x08) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[1] & 0x20) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[1] & 0x40) != 0));
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, (data[1] & 0x01) ? SDL_MAX_SINT16 : SDL_MIN_SINT16);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, (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_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
axis = READ_STICK_AXIS(4);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
axis = READ_STICK_AXIS(5);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
axis = READ_STICK_AXIS(6);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
}
#undef READ_STICK_AXIS
SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state)));
}
static void HIDAPI_Driver8BitDo_HandleStatePacket(SDL_Joystick *joystick, SDL_Driver8BitDo_Context *ctx, Uint8 *data, int size) static void HIDAPI_Driver8BitDo_HandleStatePacket(SDL_Joystick *joystick, SDL_Driver8BitDo_Context *ctx, Uint8 *data, int size)
{ {
Sint16 axis; Sint16 axis;
Uint64 timestamp = SDL_GetTicksNS(); Uint64 timestamp = SDL_GetTicksNS();
if (data[0] != SDL_8BITDO_REPORTID_SDL_REPORTID && data[0] != SDL_8BITDO_REPORTID_NOT_SUPPORTED_SDL_REPORTID &&
data[0] != SDL_8BITDO_BT_REPORTID_SDL_REPORTID) { 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 // We don't know how to handle this report
return; return;
} }
@@ -323,7 +430,7 @@ static void HIDAPI_Driver8BitDo_HandleStatePacket(SDL_Joystick *joystick, SDL_Dr
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[9] & 0x40) != 0)); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[9] & 0x40) != 0));
} }
if (ctx->last_state[10] != data[10]) { if (size > 10 && ctx->last_state[10] != data[10]) {
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_8BITDO_L4, ((data[10] & 0x01) != 0)); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_8BITDO_L4, ((data[10] & 0x01) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_8BITDO_R4, ((data[10] & 0x02) != 0)); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_8BITDO_R4, ((data[10] & 0x02) != 0));
} }
@@ -381,7 +488,6 @@ static void HIDAPI_Driver8BitDo_HandleStatePacket(SDL_Joystick *joystick, SDL_Dr
SDL_SendJoystickPowerInfo(joystick, state, percent); SDL_SendJoystickPowerInfo(joystick, state, percent);
} }
if (ctx->sensors_enabled) { if (ctx->sensors_enabled) {
Uint64 sensor_timestamp; Uint64 sensor_timestamp;
float values[3]; float values[3];
@@ -440,7 +546,12 @@ static bool HIDAPI_Driver8BitDo_UpdateDevice(SDL_HIDAPI_Device *device)
continue; continue;
} }
HIDAPI_Driver8BitDo_HandleStatePacket(joystick, ctx, data, size); 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) { if (size < 0) {

View File

@@ -60,9 +60,10 @@
#define USB_VENDOR_ZEROPLUS 0x0c12 #define USB_VENDOR_ZEROPLUS 0x0c12
#define USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS 0x6012 #define USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS 0x6012
#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 0x6001 // B + START
#define USB_PRODUCT_8BITDO_SN30_PRO_BT 0x6101 // B + START #define USB_PRODUCT_8BITDO_SN30_PRO_BT 0x6101 // B + START
#define USB_PRODUCT_8BITDO_SF30_PRO 0x6000 // B + START
#define USB_PRODUCT_8BITDO_PRO_2 0x6003 // mode switch to D #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_2_BT 0x6006 // mode switch to D
#define USB_PRODUCT_AMAZON_LUNA_CONTROLLER 0x0419 #define USB_PRODUCT_AMAZON_LUNA_CONTROLLER 0x0419