SDL: Fixed infinite recursion in SDL_IsGamepad()

From 5985f0a327910648aff75ca7134f3a7c7ff83721 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Mon, 17 Mar 2025 19:10:26 -0700
Subject: [PATCH] Fixed infinite recursion in SDL_IsGamepad()

SDL_IsGamepad() calls SDL_GetJoystickTypeForID(), which will call SDL_IsGamepad() if it's not a known controller type. The new code which is breaking was added to prevent Logitech FFB wheels from showing up as gamepads, which we check for separately.
---
 src/joystick/SDL_gamepad.c    | 12 +++++++-----
 src/joystick/SDL_joystick.c   |  2 +-
 src/joystick/SDL_joystick_c.h |  3 +++
 3 files changed, 11 insertions(+), 6 deletions(-)

diff --git a/src/joystick/SDL_gamepad.c b/src/joystick/SDL_gamepad.c
index 59bd217f5b920..0bff7bf5f855b 100644
--- a/src/joystick/SDL_gamepad.c
+++ b/src/joystick/SDL_gamepad.c
@@ -699,6 +699,12 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid)
 
     SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL, NULL);
 
+    if (SDL_IsJoystickWheel(vendor, product)) {
+        // We don't want to pick up Logitech FFB wheels here
+        // Some versions of WINE will also not treat devices that show up as gamepads as wheels
+        return NULL;
+    }
+
     if ((vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_GAMECUBE_ADAPTER) ||
         (vendor == USB_VENDOR_DRAGONRISE &&
          (product == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER1 ||
@@ -2633,11 +2639,7 @@ bool SDL_IsGamepad(SDL_JoystickID instance_id)
         if (SDL_FindInHashTable(s_gamepadInstanceIDs, (void *)(uintptr_t)instance_id, &value)) {
             result = (bool)(uintptr_t)value;
         } else {
-            SDL_JoystickType js_type = SDL_GetJoystickTypeForID(instance_id);
-            if (js_type != SDL_JOYSTICK_TYPE_GAMEPAD && js_type != SDL_JOYSTICK_TYPE_UNKNOWN) {
-                // avoid creating HIDAPI mapping if SDL_Joystick knows it is not a game pad
-                result = false;
-            } else if (SDL_PrivateGetGamepadMapping(instance_id, true) != NULL) {
+            if (SDL_PrivateGetGamepadMapping(instance_id, true) != NULL) {
                 result = true;
             } else {
                 result = false;
diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c
index a4d86688c7302..8f7947b825b3c 100644
--- a/src/joystick/SDL_joystick.c
+++ b/src/joystick/SDL_joystick.c
@@ -3049,7 +3049,7 @@ bool SDL_IsJoystickVIRTUAL(SDL_GUID guid)
     return (guid.data[14] == 'v') ? true : false;
 }
 
-static bool SDL_IsJoystickWheel(Uint16 vendor_id, Uint16 product_id)
+bool SDL_IsJoystickWheel(Uint16 vendor_id, Uint16 product_id)
 {
     return SDL_VIDPIDInList(vendor_id, product_id, &wheel_devices);
 }
diff --git a/src/joystick/SDL_joystick_c.h b/src/joystick/SDL_joystick_c.h
index d931cf7ed73f2..6b82365c5865b 100644
--- a/src/joystick/SDL_joystick_c.h
+++ b/src/joystick/SDL_joystick_c.h
@@ -156,6 +156,9 @@ extern bool SDL_IsJoystickRAWINPUT(SDL_GUID guid);
 // Function to return whether a joystick guid comes from the Virtual driver
 extern bool SDL_IsJoystickVIRTUAL(SDL_GUID guid);
 
+// Function to return whether a joystick is a wheel
+bool SDL_IsJoystickWheel(Uint16 vendor_id, Uint16 product_id);
+
 // Function to return whether a joystick should be ignored
 extern bool SDL_ShouldIgnoreJoystick(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name);