SDL: Don't rely on the device VID/PID to get the Nintendo controller type

From 57c3b2c95089600d4e1cdbbfb58ffd6ba84ca402 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Wed, 3 Aug 2022 21:31:12 -0700
Subject: [PATCH] Don't rely on the device VID/PID to get the Nintendo
 controller type

The Nintendo Online Sega Genesis controller reports the SNES VID/PID over Bluetooth. This is a more robust way of handling future controllers as well, so let's go with this instead.

Also use full reports over Bluetooth, and don't report gyro for Nintendo Online classic controllers.
---
 src/joystick/SDL_gamecontroller.c       | 22 ++++---
 src/joystick/hidapi/SDL_hidapi_switch.c | 78 ++++++++++++++++---------
 2 files changed, 62 insertions(+), 38 deletions(-)

diff --git a/src/joystick/SDL_gamecontroller.c b/src/joystick/SDL_gamecontroller.c
index 3c782a1f696..8f802949d2b 100644
--- a/src/joystick/SDL_gamecontroller.c
+++ b/src/joystick/SDL_gamecontroller.c
@@ -587,21 +587,25 @@ static ControllerMapping_t *SDL_CreateMappingForHIDAPIController(SDL_JoystickGUI
         (vendor == USB_VENDOR_SHENZHEN && product == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER)) {
         /* GameCube driver has 12 buttons and 6 axes */
         SDL_strlcat(mapping_string, "a:b0,b:b1,dpdown:b6,dpleft:b4,dpright:b5,dpup:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b9,righttrigger:a5,rightx:a2,righty:a3,start:b8,x:b2,y:b3,", sizeof(mapping_string));
-    } else if (vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_N64_CONTROLLER) {
-        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,righttrigger:a5,start:b6,x:b2,y:b3,misc1:b15,", sizeof(mapping_string));
-    } else if (vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_SEGA_GENESIS_CONTROLLER) {
-        SDL_strlcat(mapping_string, "a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,rightshoulder:b10,righttrigger:a5,start:b6,misc1:b15,", sizeof(mapping_string));
-    } else if (vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_SNES_CONTROLLER) {
-        SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,lefttrigger:a4,rightshoulder:b10,righttrigger:a5,start:b6,x:b2,y:b3,", sizeof(mapping_string));
-    } else if (SDL_IsJoystickNintendoSwitchJoyConLeft(vendor, product) ||
-               SDL_IsJoystickNintendoSwitchJoyConRight(vendor, product) ||
-               SDL_IsJoystickNintendoSwitchJoyConGrip(vendor, product)) {
+    } else if (vendor == USB_VENDOR_NINTENDO && guid.data[15] != 0 && guid.data[15] != 3) {
         switch (guid.data[15]) {
         case 9:
         case 10:
             /* NES Controller */
             SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,rightshoulder:b10,start:b6,", sizeof(mapping_string));
             break;
+        case 11:
+            /* SNES Controller */
+            SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,lefttrigger:a4,rightshoulder:b10,righttrigger:a5,start:b6,x:b2,y:b3,", sizeof(mapping_string));
+            break;
+        case 12:
+            /* N64 Controller */
+            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,righttrigger:a5,start:b6,x:b2,y:b3,misc1:b15,", sizeof(mapping_string));
+            break;
+        case 13:
+            /* SEGA Genesis Controller */
+            SDL_strlcat(mapping_string, "a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,rightshoulder:b10,righttrigger:a5,start:b6,misc1:b15,", sizeof(mapping_string));
+            break;
         default:
             /* Mini gamepad mode */
             SDL_strlcat(mapping_string, "a:b0,b:b1,guide:b5,leftshoulder:b9,leftstick:b7,leftx:a0,lefty:a1,rightshoulder:b10,start:b6,x:b2,y:b3,", sizeof(mapping_string));
diff --git a/src/joystick/hidapi/SDL_hidapi_switch.c b/src/joystick/hidapi/SDL_hidapi_switch.c
index 69f69506b9a..68753809350 100644
--- a/src/joystick/hidapi/SDL_hidapi_switch.c
+++ b/src/joystick/hidapi/SDL_hidapi_switch.c
@@ -1095,36 +1095,55 @@ ReadJoyConControllerType(SDL_HIDAPI_Device *device)
     return eControllerType;
 }
 
+static void
+UpdateDeviceName(SDL_HIDAPI_Device *device, ESwitchDeviceInfoControllerType eControllerType)
+{
+    const char *name = NULL;
+
+    switch (eControllerType) {
+    case k_eSwitchDeviceInfoControllerType_JoyConLeft:
+        name = "Nintendo Switch Joy-Con (L)";
+        break;
+    case k_eSwitchDeviceInfoControllerType_JoyConRight:
+        name = "Nintendo Switch Joy-Con (R)";
+        break;
+    case k_eSwitchDeviceInfoControllerType_ProController:
+        name = "Nintendo Switch Pro Controller";
+        break;
+    case k_eSwitchDeviceInfoControllerType_NESLeft:
+        name = "Nintendo NES Controller (L)";
+        break;
+    case k_eSwitchDeviceInfoControllerType_NESRight:
+        name = "Nintendo NES Controller (R)";
+        break;
+    case k_eSwitchDeviceInfoControllerType_SNES:
+        name = "Nintendo SNES Controller";
+        break;
+    case k_eSwitchDeviceInfoControllerType_N64:
+        name = "Nintendo N64 Controller";
+        break;
+    case k_eSwitchDeviceInfoControllerType_SEGA_Genesis:
+        name = "Nintendo SEGA Genesis Controller";
+        break;
+    default:
+        break;
+    }
+
+    if (name && (!name || SDL_strcmp(name, device->name) != 0)) {
+        SDL_free(device->name);
+        device->name = SDL_strdup(name);
+    }
+}
+
 static SDL_bool
 HIDAPI_DriverSwitch_InitDevice(SDL_HIDAPI_Device *device)
 {
     /* The NES controllers need additional fix up, since we can't detect them without opening the device */
-    if (device->vendor_id == USB_VENDOR_NINTENDO &&
-        (device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOY_CON_RIGHT ||
-         device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOY_CON_GRIP)) {
+    if (device->vendor_id == USB_VENDOR_NINTENDO) {
         ESwitchDeviceInfoControllerType eControllerType = ReadJoyConControllerType(device);
         switch (eControllerType) {
-        case k_eSwitchDeviceInfoControllerType_JoyConLeft:
-            SDL_free(device->name);
-            device->name = SDL_strdup("Nintendo Switch Joy-Con (L)");
-            device->guid.data[15] = eControllerType;
-            break;
-        case k_eSwitchDeviceInfoControllerType_JoyConRight:
-            SDL_free(device->name);
-            device->name = SDL_strdup("Nintendo Switch Joy-Con (R)");
-            device->guid.data[15] = eControllerType;
-            break;
-        case k_eSwitchDeviceInfoControllerType_NESLeft:
-            SDL_free(device->name);
-            device->name = SDL_strdup("NES Controller (L)");
-            device->guid.data[15] = eControllerType;
-            break;
-        case k_eSwitchDeviceInfoControllerType_NESRight:
-            SDL_free(device->name);
-            device->name = SDL_strdup("NES Controller (R)");
-            device->guid.data[15] = eControllerType;
-            break;
         case k_eSwitchDeviceInfoControllerType_Unknown:
+            /* This might be a Joy-Con that's missing from a charging grip slot */
             if (device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOY_CON_GRIP) {
                 if (device->interface_number == 1) {
                     SDL_free(device->name);
@@ -1138,6 +1157,8 @@ HIDAPI_DriverSwitch_InitDevice(SDL_HIDAPI_Device *device)
             }
             break;
         default:
+            UpdateDeviceName(device, eControllerType);
+            device->guid.data[15] = eControllerType;
             break;
         }
     }
@@ -1208,17 +1229,16 @@ HIDAPI_DriverSwitch_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joysti
          * HandleFullControllerState is completely pointless. We need full state if we want battery
          * level and we only care about battery level over bluetooth anyway.
          */
-        if (device->vendor_id == USB_VENDOR_NINTENDO &&
-            (device->product_id == USB_PRODUCT_NINTENDO_SWITCH_PRO ||
-             device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOY_CON_GRIP ||
-             device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOY_CON_LEFT ||
-             device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOY_CON_RIGHT)) {
+        if (device->vendor_id == USB_VENDOR_NINTENDO) {
             input_mode = k_eSwitchInputReportIDs_FullControllerState;
         }
         
         if (input_mode == k_eSwitchInputReportIDs_FullControllerState &&
             ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_NESLeft &&
-            ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_NESRight) {
+            ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_NESRight &&
+            ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_SNES &&
+            ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_N64 &&
+            ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_SEGA_Genesis) {
             /* Use the right sensor in the combined Joy-Con pair */
             if (!device->parent ||
                 ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {