SDL: Dynamically update the serial number if it isn't available at first

From d1c72bb0bc54fba41ba89930a3ed98f8767be724 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Wed, 8 Feb 2023 14:16:17 -0800
Subject: [PATCH] Dynamically update the serial number if it isn't available at
 first

This happens for Xbox One controllers using newer firmware connected over NDIS
---
 src/joystick/hidapi/SDL_hidapijoystick.c | 95 ++++++++++++++++--------
 1 file changed, 66 insertions(+), 29 deletions(-)

diff --git a/src/joystick/hidapi/SDL_hidapijoystick.c b/src/joystick/hidapi/SDL_hidapijoystick.c
index f8b683589aea..2cfa329fdcc3 100644
--- a/src/joystick/hidapi/SDL_hidapijoystick.c
+++ b/src/joystick/hidapi/SDL_hidapijoystick.c
@@ -92,6 +92,26 @@ static SDL_bool SDL_HIDAPI_combine_joycons = SDL_TRUE;
 static SDL_bool initialized = SDL_FALSE;
 static SDL_bool shutting_down = SDL_FALSE;
 
+static char *HIDAPI_ConvertString(const wchar_t *wide_string)
+{
+    char *string = NULL;
+
+    if (wide_string) {
+        string = SDL_iconv_string("UTF-8", "WCHAR_T", (char *)wide_string, (SDL_wcslen(wide_string) + 1) * sizeof(wchar_t));
+        if (string == NULL) {
+            switch (sizeof(wchar_t)) {
+            case 2:
+                string = SDL_iconv_string("UTF-8", "UCS-2-INTERNAL", (char *)wide_string, (SDL_wcslen(wide_string) + 1) * sizeof(wchar_t));
+                break;
+            case 4:
+                string = SDL_iconv_string("UTF-8", "UCS-4-INTERNAL", (char *)wide_string, (SDL_wcslen(wide_string) + 1) * sizeof(wchar_t));
+                break;
+            }
+        }
+    }
+    return string;
+}
+
 void HIDAPI_DumpPacket(const char *prefix, const Uint8 *data, int size)
 {
     int i;
@@ -561,16 +581,53 @@ void HIDAPI_SetDeviceProduct(SDL_HIDAPI_Device *device, Uint16 product_id)
     SDL_SetJoystickGUIDProduct(&device->guid, product_id);
 }
 
+static void HIDAPI_UpdateJoystickSerial(SDL_HIDAPI_Device *device)
+{
+    int i;
+
+    for (i = 0; i < device->num_joysticks; ++i) {
+        SDL_Joystick *joystick = SDL_GetJoystickFromInstanceID(device->joysticks[i]);
+        if (joystick && device->serial) {
+            SDL_free(joystick->serial);
+            joystick->serial = SDL_strdup(device->serial);
+        }
+    }
+}
+
 void HIDAPI_SetDeviceSerial(SDL_HIDAPI_Device *device, const char *serial)
 {
     if (serial && *serial && (!device->serial || SDL_strcmp(serial, device->serial) != 0)) {
         SDL_free(device->serial);
         device->serial = SDL_strdup(serial);
+        HIDAPI_UpdateJoystickSerial(device);
     }
 }
 
-SDL_bool
-HIDAPI_HasConnectedUSBDevice(const char *serial)
+static int wcstrcmp(const wchar_t *str1, const char *str2)
+{
+    int result;
+
+    while (1) {
+        result = (*str1 - *str2);
+        if (result != 0 || *str1 == 0) {
+            break;
+        }
+        ++str1;
+        ++str2;
+    }
+    return result;
+}
+
+static void HIDAPI_SetDeviceSerialW(SDL_HIDAPI_Device *device, const wchar_t *serial)
+{
+    if (serial && *serial && (!device->serial || wcstrcmp(serial, device->serial) != 0)) {
+        SDL_free(device->serial);
+        device->serial = HIDAPI_ConvertString(serial);
+        HIDAPI_UpdateJoystickSerial(device);
+    }
+}
+
+SDL_bool HIDAPI_HasConnectedUSBDevice(const char *serial)
 {
     SDL_HIDAPI_Device *device;
 
@@ -623,8 +680,7 @@ void HIDAPI_DisconnectBluetoothDevice(const char *serial)
     }
 }
 
-SDL_bool
-HIDAPI_JoystickConnected(SDL_HIDAPI_Device *device, SDL_JoystickID *pJoystickID)
+SDL_bool HIDAPI_JoystickConnected(SDL_HIDAPI_Device *device, SDL_JoystickID *pJoystickID)
 {
     int i, j;
     SDL_JoystickID joystickID;
@@ -700,26 +756,6 @@ static int HIDAPI_JoystickGetCount(void)
     return SDL_HIDAPI_numjoysticks;
 }
 
-static char *HIDAPI_ConvertString(const wchar_t *wide_string)
-{
-    char *string = NULL;
-
-    if (wide_string) {
-        string = SDL_iconv_string("UTF-8", "WCHAR_T", (char *)wide_string, (SDL_wcslen(wide_string) + 1) * sizeof(wchar_t));
-        if (string == NULL) {
-            switch (sizeof(wchar_t)) {
-            case 2:
-                string = SDL_iconv_string("UTF-8", "UCS-2-INTERNAL", (char *)wide_string, (SDL_wcslen(wide_string) + 1) * sizeof(wchar_t));
-                break;
-            case 4:
-                string = SDL_iconv_string("UTF-8", "UCS-4-INTERNAL", (char *)wide_string, (SDL_wcslen(wide_string) + 1) * sizeof(wchar_t));
-                break;
-            }
-        }
-    }
-    return string;
-}
-
 static SDL_HIDAPI_Device *HIDAPI_AddDevice(const struct SDL_hid_device_info *info, int num_children, SDL_HIDAPI_Device **children)
 {
     SDL_HIDAPI_Device *device;
@@ -957,6 +993,9 @@ static void HIDAPI_UpdateDeviceList(void)
                 device = HIDAPI_GetJoystickByInfo(info->path, info->vendor_id, info->product_id);
                 if (device) {
                     device->seen = SDL_TRUE;
+
+                    /* Check to see if the serial number is available now */
+                    HIDAPI_SetDeviceSerialW(device, info->serial_number);
                 } else {
                     HIDAPI_AddDevice(info, 0, NULL);
                 }
@@ -1040,8 +1079,7 @@ static SDL_bool HIDAPI_IsEquivalentToDevice(Uint16 vendor_id, Uint16 product_id,
     return SDL_FALSE;
 }
 
-SDL_bool
-HIDAPI_IsDeviceTypePresent(SDL_GamepadType type)
+SDL_bool HIDAPI_IsDeviceTypePresent(SDL_GamepadType type)
 {
     SDL_HIDAPI_Device *device;
     SDL_bool result = SDL_FALSE;
@@ -1071,8 +1109,7 @@ HIDAPI_IsDeviceTypePresent(SDL_GamepadType type)
     return result;
 }
 
-SDL_bool
-HIDAPI_IsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
+SDL_bool HIDAPI_IsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
 {
     SDL_HIDAPI_Device *device;
     SDL_bool supported = SDL_FALSE;
@@ -1306,7 +1343,7 @@ static int HIDAPI_JoystickOpen(SDL_Joystick *joystick, int device_index)
         return -1;
     }
 
-    if (!joystick->serial && device->serial) {
+    if (device->serial) {
         joystick->serial = SDL_strdup(device->serial);
     }