SDL: Fixed crash if a virtual joystick was disconnected

From 3c3ccb1d48a52a0f10e75d826b719440ccd0cc59 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Mon, 16 May 2022 08:55:54 -0700
Subject: [PATCH] Fixed crash if a virtual joystick was disconnected

---
 src/joystick/virtual/SDL_virtualjoystick.c   | 90 +++++++++++++-------
 src/joystick/virtual/SDL_virtualjoystick_c.h |  3 +-
 test/testgamecontroller.c                    | 49 ++++++++---
 3 files changed, 98 insertions(+), 44 deletions(-)

diff --git a/src/joystick/virtual/SDL_virtualjoystick.c b/src/joystick/virtual/SDL_virtualjoystick.c
index 4449d0af069..b557d0c6767 100644
--- a/src/joystick/virtual/SDL_virtualjoystick.c
+++ b/src/joystick/virtual/SDL_virtualjoystick.c
@@ -71,6 +71,10 @@ VIRTUAL_FreeHWData(joystick_hwdata *hwdata)
         cur = cur->next;
     }
 
+    if (hwdata->joystick) {
+        hwdata->joystick->hwdata = NULL;
+        hwdata->joystick = NULL;
+    }
     if (hwdata->name) {
         SDL_free(hwdata->name);
         hwdata->name = NULL;
@@ -434,16 +438,12 @@ VIRTUAL_JoystickOpen(SDL_Joystick *joystick, int device_index)
     if (!hwdata) {
         return SDL_SetError("No such device");
     }
-    if (hwdata->opened) {
-        /* This should never happen, it's handled by the higher joystick code */
-        return SDL_SetError("Joystick already opened");
-    }
     joystick->instance_id = hwdata->instance_id;
     joystick->hwdata = hwdata;
     joystick->naxes = hwdata->desc.naxes;
     joystick->nbuttons = hwdata->desc.nbuttons;
     joystick->nhats = hwdata->desc.nhats;
-    hwdata->opened = SDL_TRUE;
+    hwdata->joystick = joystick;
     return 0;
 }
 
@@ -451,23 +451,39 @@ VIRTUAL_JoystickOpen(SDL_Joystick *joystick, int device_index)
 static int
 VIRTUAL_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
 {
-    joystick_hwdata *hwdata = joystick->hwdata;
+    int result;
 
-    if (hwdata && hwdata->desc.Rumble) {
-        return hwdata->desc.Rumble(hwdata->desc.userdata, low_frequency_rumble, high_frequency_rumble);
+    if (joystick->hwdata) {
+        joystick_hwdata *hwdata = joystick->hwdata;
+        if (hwdata->desc.Rumble) {
+            result = hwdata->desc.Rumble(hwdata->desc.userdata, low_frequency_rumble, high_frequency_rumble);
+        } else {
+            result = SDL_Unsupported();
+        }
+    } else {
+        result = SDL_SetError("Rumble failed, device disconnected");
     }
-    return SDL_Unsupported();
+
+    return result;
 }
 
 static int
 VIRTUAL_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
 {
-    joystick_hwdata *hwdata = joystick->hwdata;
+    int result;
 
-    if (hwdata && hwdata->desc.RumbleTriggers) {
-        return hwdata->desc.RumbleTriggers(hwdata->desc.userdata, left_rumble, right_rumble);
+    if (joystick->hwdata) {
+        joystick_hwdata *hwdata = joystick->hwdata;
+        if (hwdata->desc.RumbleTriggers) {
+            result = hwdata->desc.RumbleTriggers(hwdata->desc.userdata, left_rumble, right_rumble);
+        } else {
+            result = SDL_Unsupported();
+        }
+    } else {
+        result = SDL_SetError("Rumble failed, device disconnected");
     }
-    return SDL_Unsupported();
+
+    return result;
 }
 
 
@@ -495,23 +511,39 @@ VIRTUAL_JoystickGetCapabilities(SDL_Joystick *joystick)
 static int
 VIRTUAL_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
 {
-    joystick_hwdata *hwdata = joystick->hwdata;
+    int result;
 
-    if (hwdata && hwdata->desc.SetLED) {
-        return hwdata->desc.SetLED(hwdata->desc.userdata, red, green, blue);
+    if (joystick->hwdata) {
+        joystick_hwdata *hwdata = joystick->hwdata;
+        if (hwdata->desc.SetLED) {
+            result = hwdata->desc.SetLED(hwdata->desc.userdata, red, green, blue);
+        } else {
+            result = SDL_Unsupported();
+        }
+    } else {
+        result = SDL_SetError("SetLED failed, device disconnected");
     }
-    return SDL_Unsupported();
+
+    return result;
 }
 
 static int
 VIRTUAL_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
 {
-    joystick_hwdata *hwdata = joystick->hwdata;
-
-    if (hwdata && hwdata->desc.SendEffect) {
-        return hwdata->desc.SendEffect(hwdata->desc.userdata, data, size);
+    int result;
+
+    if (joystick->hwdata) {
+        joystick_hwdata *hwdata = joystick->hwdata;
+        if (hwdata->desc.SendEffect) {
+            result = hwdata->desc.SendEffect(hwdata->desc.userdata, data, size);
+        } else {
+            result = SDL_Unsupported();
+        }
+    } else {
+        result = SDL_SetError("SendEffect failed, device disconnected");
     }
-    return SDL_Unsupported();
+
+    return result;
 }
 
 static int
@@ -555,17 +587,11 @@ VIRTUAL_JoystickUpdate(SDL_Joystick *joystick)
 static void
 VIRTUAL_JoystickClose(SDL_Joystick *joystick)
 {
-    joystick_hwdata *hwdata;
-
-    if (!joystick) {
-        return;
-    }
-    if (!joystick->hwdata) {
-        return;
+    if (joystick->hwdata) {
+        joystick_hwdata *hwdata = joystick->hwdata;
+        hwdata->joystick = NULL;
+        joystick->hwdata = NULL;
     }
-
-    hwdata = (joystick_hwdata *)joystick->hwdata;
-    hwdata->opened = SDL_FALSE;
 }
 
 
diff --git a/src/joystick/virtual/SDL_virtualjoystick_c.h b/src/joystick/virtual/SDL_virtualjoystick_c.h
index a12d6c84a9c..5e15f1dd6ea 100644
--- a/src/joystick/virtual/SDL_virtualjoystick_c.h
+++ b/src/joystick/virtual/SDL_virtualjoystick_c.h
@@ -41,7 +41,8 @@ typedef struct joystick_hwdata
     Uint8 *buttons;
     Uint8 *hats;
     SDL_JoystickID instance_id;
-    SDL_bool opened;
+    SDL_Joystick *joystick;
+
     struct joystick_hwdata *next;
 } joystick_hwdata;
 
diff --git a/test/testgamecontroller.c b/test/testgamecontroller.c
index b24581598a4..45560c5da79 100644
--- a/test/testgamecontroller.c
+++ b/test/testgamecontroller.c
@@ -335,9 +335,10 @@ static int VirtualControllerSetLED(void *userdata, Uint8 red, Uint8 green, Uint8
     return 0;
 }
 
-static int OpenVirtualController()
+static void OpenVirtualController()
 {
     SDL_VirtualJoystickDesc desc;
+    int virtual_index;
 
     SDL_zero(desc);
     desc.version = SDL_VIRTUAL_JOYSTICK_DESC_VERSION;
@@ -348,7 +349,32 @@ static int OpenVirtualController()
     desc.Rumble = VirtualControllerRumble;
     desc.RumbleTriggers = VirtualControllerRumbleTriggers;
     desc.SetLED = VirtualControllerSetLED;
-    return SDL_JoystickAttachVirtualEx(&desc);
+
+    virtual_index = SDL_JoystickAttachVirtualEx(&desc);
+    if (virtual_index < 0) {
+        SDL_Log("Couldn't open virtual device: %s\n", SDL_GetError());
+    } else {
+        virtual_joystick = SDL_JoystickOpen(virtual_index);
+        if (!virtual_joystick) {
+            SDL_Log("Couldn't open virtual device: %s\n", SDL_GetError());
+        }
+    }
+}
+
+static void CloseVirtualController()
+{
+    int i;
+
+    for (i = 0; i < SDL_NumJoysticks(); ++i) {
+        if (SDL_JoystickIsVirtual(i)) {
+            SDL_JoystickDetachVirtual(i);
+        }
+    }
+
+    if (virtual_joystick) {
+        SDL_JoystickClose(virtual_joystick);
+        virtual_joystick = NULL;
+    }
 }
 
 static SDL_GameControllerButton FindButtonAtPosition(int x, int y)
@@ -580,6 +606,14 @@ loop(void *arg)
                 }
                 break;
             }
+            if (event.key.keysym.sym == SDLK_a) {
+                OpenVirtualController();
+                break;
+            }
+            if (event.key.keysym.sym == SDLK_d) {
+                CloseVirtualController();
+                break;
+            }
             if (event.key.keysym.sym != SDLK_ESCAPE) {
                 break;
             }
@@ -836,15 +870,7 @@ main(int argc, char *argv[])
 
     for (i = 1; i < argc; ++i) {
         if (SDL_strcmp(argv[i], "--virtual") == 0) {
-            int virtual_index = OpenVirtualController();
-            if (virtual_index < 0) {
-                SDL_Log("Couldn't open virtual device: %s\n", SDL_GetError());
-            } else {
-                virtual_joystick = SDL_JoystickOpen(virtual_index);
-                if (!virtual_joystick) {
-                    SDL_Log("Couldn't open virtual device: %s\n", SDL_GetError());
-                }
-            }
+            OpenVirtualController();
         }
         if (argv[i] && *argv[i] != '-') {
             controller_index = SDL_atoi(argv[i]);
@@ -873,6 +899,7 @@ main(int argc, char *argv[])
         CyclePS5TriggerEffect();
     }
 
+    CloseVirtualController();
     SDL_DestroyRenderer(screen);
     SDL_DestroyWindow(window);
     SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);