SDL: hidapi: Use a whitelist for libusb when other backends are available (793a0)

From 793a0681a92d156733fa3aaa174651dc7345cc5f Mon Sep 17 00:00:00 2001
From: Phena Ildanach <[EMAIL REDACTED]>
Date: Fri, 17 May 2024 02:40:19 -0500
Subject: [PATCH] hidapi: Use a whitelist for libusb when other backends are
 available

(cherry picked from commit fdf28471c03731b82f215c979e4d5930f9c29bca)
---
 src/hidapi/SDL_hidapi.c | 47 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 47 insertions(+)

diff --git a/src/hidapi/SDL_hidapi.c b/src/hidapi/SDL_hidapi.c
index 6ed1eb613a42b..ef9597782ff84 100644
--- a/src/hidapi/SDL_hidapi.c
+++ b/src/hidapi/SDL_hidapi.c
@@ -877,6 +877,41 @@ static int SDL_libusb_get_string_descriptor(libusb_device_handle *dev,
 #undef make_path
 #undef read_thread
 
+/* If the platform has any backend other than libusb, try to avoid using
+ * libusb as the main backend for devices, since it detaches drivers and
+ * therefore makes devices inaccessible to the rest of the OS.
+ *
+ * We do this by whitelisting devices we know to be accessible _exclusively_
+ * via libusb; these are typically devices that look like HIDs but have a
+ * quirk that requires direct access to the hardware.
+ */
+static const struct {
+    Uint16 vendor;
+    Uint16 product;
+} SDL_libusb_whitelist[] = {
+    { 0x057e, 0x0337 } /* Nintendo WUP-028, Wii U/Switch GameCube Adapter */
+};
+
+static SDL_bool
+IsInWhitelist(Uint16 vendor, Uint16 product)
+{
+    int i;
+    for (i = 0; i < SDL_arraysize(SDL_libusb_whitelist); i += 1) {
+        if (vendor == SDL_libusb_whitelist[i].vendor &&
+            product == SDL_libusb_whitelist[i].product) {
+            return SDL_TRUE;
+        }
+    }
+    return SDL_FALSE;
+}
+
+#if defined(HAVE_PLATFORM_BACKEND) || HAVE_DRIVER_BACKEND
+static const SDL_bool use_libusb_whitelist_default = SDL_TRUE;
+#else
+static const SDL_bool use_libusb_whitelist_default = SDL_FALSE;
+#endif /* HAVE_PLATFORM_BACKEND || HAVE_DRIVER_BACKEND */
+static SDL_bool use_libusb_whitelist = use_libusb_whitelist_default;
+
 #endif /* HAVE_LIBUSB */
 
 #endif /* !SDL_HIDAPI_DISABLED */
@@ -1062,6 +1097,8 @@ int SDL_hid_init(void)
 #endif
 
 #ifdef HAVE_LIBUSB
+    use_libusb_whitelist = SDL_GetHintBoolean("SDL_HIDAPI_LIBUSB_WHITELIST",
+                                              use_libusb_whitelist_default);
     if (SDL_getenv("SDL_HIDAPI_DISABLE_LIBUSB") != NULL) {
         SDL_LogDebug(SDL_LOG_CATEGORY_INPUT,
                      "libusb disabled by SDL_HIDAPI_DISABLE_LIBUSB");
@@ -1238,6 +1275,16 @@ struct SDL_hid_device_info *SDL_hid_enumerate(unsigned short vendor_id, unsigned
         SDL_Log("libusb devices found:");
 #endif
         for (usb_dev = usb_devs; usb_dev; usb_dev = usb_dev->next) {
+            if (use_libusb_whitelist) {
+                if (!IsInWhitelist(usb_dev->vendor_id, usb_dev->product_id)) {
+#ifdef DEBUG_HIDAPI
+                    SDL_Log("Device was not in libusb whitelist: %ls %ls 0x%.4hx 0x%.4hx",
+                            usb_dev->manufacturer_string, usb_dev->product_string,
+                            usb_dev->vendor_id, usb_dev->product_id);
+#endif /* DEBUG_HIDAPI */
+                    continue;
+                }
+            }
             new_dev = (struct SDL_hid_device_info *)SDL_malloc(sizeof(struct SDL_hid_device_info));
             if (new_dev == NULL) {
                 LIBUSB_hid_free_enumeration(usb_devs);