SDL: Don't crash if SDL functions are passed a closed joystick or gamecontroller

From 0e4baf1c4eb3d18b248f1b5b28ed0864026b7872 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Tue, 30 Aug 2022 12:39:23 -0700
Subject: [PATCH] Don't crash if SDL functions are passed a closed joystick or
 gamecontroller

---
 include/SDL_joystick.h            |   4 +-
 src/joystick/SDL_gamecontroller.c | 207 +++++++++++++++++++++++-------
 src/joystick/SDL_joystick.c       | 186 +++++++++++++--------------
 src/joystick/SDL_sysjoystick.h    |   2 +
 4 files changed, 257 insertions(+), 142 deletions(-)

diff --git a/include/SDL_joystick.h b/include/SDL_joystick.h
index 4c2e50a08d8..d6531699e10 100644
--- a/include/SDL_joystick.h
+++ b/include/SDL_joystick.h
@@ -126,8 +126,8 @@ typedef enum
  *
  * As of SDL 2.26.0, you can take the joystick lock around reinitializing the
  * joystick subsystem, to prevent other threads from seeing joysticks in an
- * uninitialized state. However, all open joysticks will be closed and calling
- * SDL functions using them will potentially cause a crash.
+ * uninitialized state. However, all open joysticks will be closed and SDL
+ * functions called with them will fail.
  *
  * \since This function is available since SDL 2.0.7.
  */
diff --git a/src/joystick/SDL_gamecontroller.c b/src/joystick/SDL_gamecontroller.c
index 79e8a4fd189..14fae84d4b8 100644
--- a/src/joystick/SDL_gamecontroller.c
+++ b/src/joystick/SDL_gamecontroller.c
@@ -107,10 +107,13 @@ static SDL_JoystickGUID s_zeroGUID;
 static ControllerMapping_t *s_pSupportedControllers = NULL;
 static ControllerMapping_t *s_pDefaultMapping = NULL;
 static ControllerMapping_t *s_pXInputMapping = NULL;
+static char gamecontroller_magic;
 
 /* The SDL game controller structure */
 struct _SDL_GameController
 {
+    const void *magic;
+
     SDL_Joystick *joystick; /* underlying joystick device */
     int ref_count;
 
@@ -125,6 +128,13 @@ struct _SDL_GameController
 };
 
 
+#define CHECK_GAMECONTROLLER_MAGIC(gamecontroller, retval) \
+    if (!gamecontroller || gamecontroller->magic != &gamecontroller_magic || \
+        !SDL_PrivateJoystickValid(gamecontroller->joystick)) { \
+        SDL_InvalidParamError("gamecontroller"); \
+        return retval; \
+    }
+
 typedef struct
 {
     int num_entries;
@@ -222,9 +232,12 @@ static void ResetOutput(SDL_GameController *gamecontroller, SDL_ExtendedGameCont
 static void HandleJoystickAxis(SDL_GameController *gamecontroller, int axis, int value)
 {
     int i;
-    SDL_ExtendedGameControllerBind *last_match = gamecontroller->last_match_axis[axis];
+    SDL_ExtendedGameControllerBind *last_match;
     SDL_ExtendedGameControllerBind *match = NULL;
 
+    CHECK_GAMECONTROLLER_MAGIC(gamecontroller, );
+
+    last_match = gamecontroller->last_match_axis[axis];
     for (i = 0; i < gamecontroller->num_bindings; ++i) {
         SDL_ExtendedGameControllerBind *binding = &gamecontroller->bindings[i];
         if (binding->inputType == SDL_CONTROLLER_BINDTYPE_AXIS &&
@@ -275,6 +288,8 @@ static void HandleJoystickButton(SDL_GameController *gamecontroller, int button,
 {
     int i;
 
+    CHECK_GAMECONTROLLER_MAGIC(gamecontroller, );
+
     for (i = 0; i < gamecontroller->num_bindings; ++i) {
         SDL_ExtendedGameControllerBind *binding = &gamecontroller->bindings[i];
         if (binding->inputType == SDL_CONTROLLER_BINDTYPE_BUTTON &&
@@ -293,9 +308,12 @@ static void HandleJoystickButton(SDL_GameController *gamecontroller, int button,
 static void HandleJoystickHat(SDL_GameController *gamecontroller, int hat, Uint8 value)
 {
     int i;
-    Uint8 last_mask = gamecontroller->last_hat_mask[hat];
-    Uint8 changed_mask = (last_mask ^ value);
+    Uint8 last_mask, changed_mask;
+
+    CHECK_GAMECONTROLLER_MAGIC(gamecontroller, );
 
+    last_mask = gamecontroller->last_hat_mask[hat];
+    changed_mask = (last_mask ^ value);
     for (i = 0; i < gamecontroller->num_bindings; ++i) {
         SDL_ExtendedGameControllerBind *binding = &gamecontroller->bindings[i];
         if (binding->inputType == SDL_CONTROLLER_BINDTYPE_HAT && hat == binding->input.hat.hat) {
@@ -327,6 +345,8 @@ static void RecenterGameController(SDL_GameController *gamecontroller)
     SDL_GameControllerButton button;
     SDL_GameControllerAxis axis;
 
+    CHECK_GAMECONTROLLER_MAGIC(gamecontroller, );
+
     for (button = (SDL_GameControllerButton) 0; button < SDL_CONTROLLER_BUTTON_MAX; button++) {
         if (SDL_GameControllerGetButton(gamecontroller, button)) {
             SDL_PrivateGameControllerButton(gamecontroller, button, SDL_RELEASED);
@@ -850,6 +870,8 @@ static void SDL_PrivateGameControllerParseElement(SDL_GameController *gamecontro
     char half_axis_input = 0;
     char half_axis_output = 0;
 
+    CHECK_GAMECONTROLLER_MAGIC(gamecontroller, );
+
     if (*szGameButton == '+' || *szGameButton == '-') {
         half_axis_output = *szGameButton++;
     }
@@ -945,6 +967,8 @@ SDL_PrivateGameControllerParseControllerConfigString(SDL_GameController *gamecon
     int i = 0;
     const char *pchPos = pchString;
 
+    CHECK_GAMECONTROLLER_MAGIC(gamecontroller, );
+
     SDL_zeroa(szGameButton);
     SDL_zeroa(szJoystickButton);
 
@@ -992,6 +1016,8 @@ static void SDL_PrivateLoadButtonMapping(SDL_GameController *gamecontroller, con
 {
     int i;
 
+    CHECK_GAMECONTROLLER_MAGIC(gamecontroller, );
+
     gamecontroller->name = pchName;
     gamecontroller->num_bindings = 0;
     if (gamecontroller->joystick->naxes) {
@@ -1679,9 +1705,7 @@ SDL_GameControllerMappingForGUID(SDL_JoystickGUID guid)
 char *
 SDL_GameControllerMapping(SDL_GameController *gamecontroller)
 {
-    if (!gamecontroller) {
-        return NULL;
-    }
+    CHECK_GAMECONTROLLER_MAGIC(gamecontroller, NULL);
 
     return SDL_GameControllerMappingForGUID(gamecontroller->joystick->guid);
 }
@@ -2001,6 +2025,7 @@ SDL_GameControllerOpen(int device_index)
         SDL_UnlockJoysticks();
         return NULL;
     }
+    gamecontroller->magic = &gamecontroller_magic;
 
     gamecontroller->joystick = SDL_JoystickOpen(device_index);
     if (!gamecontroller->joystick) {
@@ -2060,7 +2085,11 @@ SDL_GameControllerUpdate(void)
 SDL_bool
 SDL_GameControllerHasAxis(SDL_GameController *gamecontroller, SDL_GameControllerAxis axis)
 {
-    SDL_GameControllerButtonBind bind = SDL_GameControllerGetBindForAxis(gamecontroller, axis);
+    SDL_GameControllerButtonBind bind;
+
+    CHECK_GAMECONTROLLER_MAGIC(gamecontroller, SDL_FALSE);
+
+    bind = SDL_GameControllerGetBindForAxis(gamecontroller, axis);
     return (bind.bindType != SDL_CONTROLLER_BINDTYPE_NONE) ? SDL_TRUE : SDL_FALSE;
 }
 
@@ -2072,8 +2101,7 @@ SDL_GameControllerGetAxis(SDL_GameController *gamecontroller, SDL_GameController
 {
     int i;
 
-    if (!gamecontroller)
-        return 0;
+    CHECK_GAMECONTROLLER_MAGIC(gamecontroller, 0);
 
     for (i = 0; i < gamecontroller->num_bindings; ++i) {
         SDL_ExtendedGameControllerBind *binding = &gamecontroller->bindings[i];
@@ -2129,7 +2157,11 @@ SDL_GameControllerGetAxis(SDL_GameController *gamecontroller, SDL_GameController
 SDL_bool
 SDL_GameControllerHasButton(SDL_GameController *gamecontroller, SDL_GameControllerButton button)
 {
-    SDL_GameControllerButtonBind bind = SDL_GameControllerGetBindForButton(gamecontroller, button);
+    SDL_GameControllerButtonBind bind;
+
+    CHECK_GAMECONTROLLER_MAGIC(gamecontroller, SDL_FALSE);
+
+    bind = SDL_GameControllerGetBindForButton(gamecontroller, button);
     return (bind.bindType != SDL_CONTROLLER_BINDTYPE_NONE) ? SDL_TRUE : SDL_FALSE;
 }
 
@@ -2141,8 +2173,7 @@ SDL_GameControllerGetButton(SDL_GameController *gamecontroller, SDL_GameControll
 {
     int i;
 
-    if (!gamecontroller)
-        return 0;
+    CHECK_GAMECONTROLLER_MAGIC(gamecontroller, 0);
 
     for (i = 0; i < gamecontroller->num_bindings; ++i) {
         SDL_ExtendedGameControllerBind *binding = &gamecontroller->bindings[i];
@@ -2267,7 +2298,7 @@ int SDL_GameControllerSetSensorEnabled(SDL_GameController *gamecontroller, SDL_S
     int i;
 
     if (!joystick) {
-        return SDL_InvalidParamError("gamecontroller");
+        return -1;
     }
 
     for (i = 0; i < joystick->nsensors; ++i) {
@@ -2352,7 +2383,7 @@ SDL_GameControllerGetSensorData(SDL_GameController *gamecontroller, SDL_SensorTy
     int i;
 
     if (!joystick) {
-        return SDL_InvalidParamError("gamecontroller");
+        return -1;
     }
 
     for (i = 0; i < joystick->nsensors; ++i) {
@@ -2370,8 +2401,7 @@ SDL_GameControllerGetSensorData(SDL_GameController *gamecontroller, SDL_SensorTy
 const char *
 SDL_GameControllerName(SDL_GameController *gamecontroller)
 {
-    if (!gamecontroller)
-        return NULL;
+    CHECK_GAMECONTROLLER_MAGIC(gamecontroller, NULL);
 
     if (SDL_strcmp(gamecontroller->name, "*") == 0) {
         return SDL_JoystickName(SDL_GameControllerGetJoystick(gamecontroller));
@@ -2383,22 +2413,34 @@ SDL_GameControllerName(SDL_GameController *gamecontroller)
 const char *
 SDL_GameControllerPath(SDL_GameController *gamecontroller)
 {
-    if (!gamecontroller)
-        return NULL;
+    SDL_Joystick *joystick = SDL_GameControllerGetJoystick(gamecontroller);
 
-    return SDL_JoystickPath(SDL_GameControllerGetJoystick(gamecontroller));
+    if (!joystick) {
+        return NULL;
+    }
+    return SDL_JoystickPath(joystick);
 }
 
 SDL_GameControllerType
 SDL_GameControllerGetType(SDL_GameController *gamecontroller)
 {
-    return SDL_GetJoystickGameControllerTypeFromGUID(SDL_JoystickGetGUID(SDL_GameControllerGetJoystick(gamecontroller)), SDL_JoystickName(SDL_GameControllerGetJoystick(gamecontroller)));
+    SDL_Joystick *joystick = SDL_GameControllerGetJoystick(gamecontroller);
+
+    if (!joystick) {
+        return SDL_CONTROLLER_TYPE_UNKNOWN;
+    }
+    return SDL_GetJoystickGameControllerTypeFromGUID(SDL_JoystickGetGUID(joystick), SDL_JoystickName(joystick));
 }
 
 int
 SDL_GameControllerGetPlayerIndex(SDL_GameController *gamecontroller)
 {
-    return SDL_JoystickGetPlayerIndex(SDL_GameControllerGetJoystick(gamecontroller));
+    SDL_Joystick *joystick = SDL_GameControllerGetJoystick(gamecontroller);
+
+    if (!joystick) {
+        return -1;
+    }
+    return SDL_JoystickGetPlayerIndex(joystick);
 }
 
 /**
@@ -2407,37 +2449,67 @@ SDL_GameControllerGetPlayerIndex(SDL_GameController *gamecontroller)
 void
 SDL_GameControllerSetPlayerIndex(SDL_GameController *gamecontroller, int player_index)
 {
-    SDL_JoystickSetPlayerIndex(SDL_GameControllerGetJoystick(gamecontroller), player_index);
+    SDL_Joystick *joystick = SDL_GameControllerGetJoystick(gamecontroller);
+
+    if (!joystick) {
+        return;
+    }
+    SDL_JoystickSetPlayerIndex(joystick, player_index);
 }
 
 Uint16
 SDL_GameControllerGetVendor(SDL_GameController *gamecontroller)
 {
-    return SDL_JoystickGetVendor(SDL_GameControllerGetJoystick(gamecontroller));
+    SDL_Joystick *joystick = SDL_GameControllerGetJoystick(gamecontroller);
+
+    if (!joystick) {
+        return 0;
+    }
+    return SDL_JoystickGetVendor(joystick);
 }
 
 Uint16
 SDL_GameControllerGetProduct(SDL_GameController *gamecontroller)
 {
-    return SDL_JoystickGetProduct(SDL_GameControllerGetJoystick(gamecontroller));
+    SDL_Joystick *joystick = SDL_GameControllerGetJoystick(gamecontroller);
+
+    if (!joystick) {
+        return 0;
+    }
+    return SDL_JoystickGetProduct(joystick);
 }
 
 Uint16
 SDL_GameControllerGetProductVersion(SDL_GameController *gamecontroller)
 {
-    return SDL_JoystickGetProductVersion(SDL_GameControllerGetJoystick(gamecontroller));
+    SDL_Joystick *joystick = SDL_GameControllerGetJoystick(gamecontroller);
+
+    if (!joystick) {
+        return 0;
+    }
+    return SDL_JoystickGetProductVersion(joystick);
 }
 
 Uint16
 SDL_GameControllerGetFirmwareVersion(SDL_GameController *gamecontroller)
 {
-    return SDL_JoystickGetFirmwareVersion(SDL_GameControllerGetJoystick(gamecontroller));
+    SDL_Joystick *joystick = SDL_GameControllerGetJoystick(gamecontroller);
+
+    if (!joystick) {
+        return 0;
+    }
+    return SDL_JoystickGetFirmwareVersion(joystick);
 }
 
 const char *
 SDL_GameControllerGetSerial(SDL_GameController *gamecontroller)
 {
-    return SDL_JoystickGetSerial(SDL_GameControllerGetJoystick(gamecontroller));
+    SDL_Joystick *joystick = SDL_GameControllerGetJoystick(gamecontroller);
+
+    if (!joystick) {
+        return NULL;
+    }
+    return SDL_JoystickGetSerial(joystick);
 }
 
 /*
@@ -2447,8 +2519,7 @@ SDL_GameControllerGetSerial(SDL_GameController *gamecontroller)
 SDL_bool
 SDL_GameControllerGetAttached(SDL_GameController *gamecontroller)
 {
-    if (!gamecontroller)
-        return SDL_FALSE;
+    CHECK_GAMECONTROLLER_MAGIC(gamecontroller, SDL_FALSE);
 
     return SDL_JoystickGetAttached(gamecontroller->joystick);
 }
@@ -2459,8 +2530,7 @@ SDL_GameControllerGetAttached(SDL_GameController *gamecontroller)
 SDL_Joystick *
 SDL_GameControllerGetJoystick(SDL_GameController *gamecontroller)
 {
-    if (!gamecontroller)
-        return NULL;
+    CHECK_GAMECONTROLLER_MAGIC(gamecontroller, NULL);
 
     return gamecontroller->joystick;
 }
@@ -2510,7 +2580,9 @@ SDL_GameControllerButtonBind SDL_GameControllerGetBindForAxis(SDL_GameController
     SDL_GameControllerButtonBind bind;
     SDL_zero(bind);
 
-    if (!gamecontroller || axis == SDL_CONTROLLER_AXIS_INVALID)
+    CHECK_GAMECONTROLLER_MAGIC(gamecontroller, bind);
+
+    if (axis == SDL_CONTROLLER_AXIS_INVALID)
         return bind;
 
     for (i = 0; i < gamecontroller->num_bindings; ++i) {
@@ -2542,7 +2614,9 @@ SDL_GameControllerButtonBind SDL_GameControllerGetBindForButton(SDL_GameControll
     SDL_GameControllerButtonBind bind;
     SDL_zero(bind);
 
-    if (!gamecontroller || button == SDL_CONTROLLER_BUTTON_INVALID)
+    CHECK_GAMECONTROLLER_MAGIC(gamecontroller, bind);
+
+    if (button == SDL_CONTROLLER_BUTTON_INVALID)
         return bind;
 
     for (i = 0; i < gamecontroller->num_bindings; ++i) {
@@ -2567,43 +2641,78 @@ SDL_GameControllerButtonBind SDL_GameControllerGetBindForButton(SDL_GameControll
 int
 SDL_GameControllerRumble(SDL_GameController *gamecontroller, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
 {
-    return SDL_JoystickRumble(SDL_GameControllerGetJoystick(gamecontroller), low_frequency_rumble, high_frequency_rumble, duration_ms);
+    SDL_Joystick *joystick = SDL_GameControllerGetJoystick(gamecontroller);
+
+    if (!joystick) {
+        return -1;
+    }
+    return SDL_JoystickRumble(joystick, low_frequency_rumble, high_frequency_rumble, duration_ms);
 }
 
 int
 SDL_GameControllerRumbleTriggers(SDL_GameController *gamecontroller, Uint16 left_rumble, Uint16 right_rumble, Uint32 duration_ms)
 {
-    return SDL_JoystickRumbleTriggers(SDL_GameControllerGetJoystick(gamecontroller), left_rumble, right_rumble, duration_ms);
+    SDL_Joystick *joystick = SDL_GameControllerGetJoystick(gamecontroller);
+
+    if (!joystick) {
+        return -1;
+    }
+    return SDL_JoystickRumbleTriggers(joystick, left_rumble, right_rumble, duration_ms);
 }
 
 SDL_bool
 SDL_GameControllerHasLED(SDL_GameController *gamecontroller)
 {
-    return SDL_JoystickHasLED(SDL_GameControllerGetJoystick(gamecontroller));
+    SDL_Joystick *joystick = SDL_GameControllerGetJoystick(gamecontroller);
+
+    if (!joystick) {
+        return SDL_FALSE;
+    }
+    return SDL_JoystickHasLED(joystick);
 }
 
 SDL_bool
 SDL_GameControllerHasRumble(SDL_GameController *gamecontroller)
 {
-    return SDL_JoystickHasRumble(SDL_GameControllerGetJoystick(gamecontroller));
+    SDL_Joystick *joystick = SDL_GameControllerGetJoystick(gamecontroller);
+
+    if (!joystick) {
+        return SDL_FALSE;
+    }
+    return SDL_JoystickHasRumble(joystick);
 }
 
 SDL_bool
 SDL_GameControllerHasRumbleTriggers(SDL_GameController *gamecontroller)
 {
-    return SDL_JoystickHasRumbleTriggers(SDL_GameControllerGetJoystick(gamecontroller));
+    SDL_Joystick *joystick = SDL_GameControllerGetJoystick(gamecontroller);
+
+    if (!joystick) {
+        return SDL_FALSE;
+    }
+    return SDL_JoystickHasRumbleTriggers(joystick);
 }
 
 int
 SDL_GameControllerSetLED(SDL_GameController *gamecontroller, Uint8 red, Uint8 green, Uint8 blue)
 {
-    return SDL_JoystickSetLED(SDL_GameControllerGetJoystick(gamecontroller), red, green, blue);
+    SDL_Joystick *joystick = SDL_GameControllerGetJoystick(gamecontroller);
+
+    if (!joystick) {
+        return -1;
+    }
+    return SDL_JoystickSetLED(joystick, red, green, blue);
 }
 
 int
 SDL_GameControllerSendEffect(SDL_GameController *gamecontroller, const void *data, int size)
 {
-    return SDL_JoystickSendEffect(SDL_GameControllerGetJoystick(gamecontroller), data, size);
+    SDL_Joystick *joystick = SDL_GameControllerGetJoystick(gamecontroller);
+
+    if (!joystick) {
+        return -1;
+    }
+    return SDL_JoystickSendEffect(joystick, data, size);
 }
 
 void
@@ -2611,7 +2720,7 @@ SDL_GameControllerClose(SDL_GameController *gamecontroller)
 {
     SDL_GameController *gamecontrollerlist, *gamecontrollerlistprev;
 
-    if (!gamecontroller)
+    if (!gamecontroller || gamecontroller->magic != &gamecontroller_magic)
         return;
 
     SDL_LockJoysticks();
@@ -2640,6 +2749,7 @@ SDL_GameControllerClose(SDL_GameController *gamecontroller)
         gamecontrollerlist = gamecontrollerlist->next;
     }
 
+    gamecontroller->magic = NULL;
     SDL_free(gamecontroller->bindings);
     SDL_free(gamecontroller->last_match_axis);
     SDL_free(gamecontroller->last_hat_mask);
@@ -2701,6 +2811,8 @@ SDL_PrivateGameControllerAxis(SDL_GameController *gamecontroller, SDL_GameContro
 {
     int posted;
 
+    CHECK_GAMECONTROLLER_MAGIC(gamecontroller, 0);
+
     /* translate the event, if desired */
     posted = 0;
 #if !SDL_EVENTS_DISABLED
@@ -2727,8 +2839,11 @@ SDL_PrivateGameControllerButton(SDL_GameController *gamecontroller, SDL_GameCont
 #if !SDL_EVENTS_DISABLED
     SDL_Event event;
 
-    if (button == SDL_CONTROLLER_BUTTON_INVALID)
-        return (0);
+    CHECK_GAMECONTROLLER_MAGIC(gamecontroller, 0);
+
+    if (button == SDL_CONTROLLER_BUTTON_INVALID) {
+        return 0;
+    }
 
     switch (state) {
     case SDL_PRESSED:
@@ -2828,6 +2943,9 @@ SDL_GameControllerGetAppleSFSymbolsNameForButton(SDL_GameController *gamecontrol
 {
 #if defined(SDL_JOYSTICK_MFI)
     const char *IOS_GameControllerGetAppleSFSymbolsNameForButton(SDL_GameController *gamecontroller, SDL_GameControllerButton button);
+
+    CHECK_GAMECONTROLLER_MAGIC(gamecontroller, NULL);
+
     return IOS_GameControllerGetAppleSFSymbolsNameForButton(gamecontroller, button);
 #else
     return NULL;
@@ -2839,6 +2957,9 @@ SDL_GameControllerGetAppleSFSymbolsNameForAxis(SDL_GameController *gamecontrolle
 {
 #if defined(SDL_JOYSTICK_MFI)
     const char *IOS_GameControllerGetAppleSFSymbolsNameForAxis(SDL_GameController *gamecontroller, SDL_GameControllerAxis axis);
+
+    CHECK_GAMECONTROLLER_MAGIC(gamecontroller, NULL);
+
     return IOS_GameControllerGetAppleSFSymbolsNameForAxis(gamecontroller, axis);
 #else
     return NULL;
diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c
index da8773739e5..cd41ca5b225 100644
--- a/src/joystick/SDL_joystick.c
+++ b/src/joystick/SDL_joystick.c
@@ -115,6 +115,14 @@ static SDL_atomic_t SDL_next_joystick_instance_id;
 static int SDL_joystick_player_count = 0;
 static SDL_JoystickID *SDL_joystick_players = NULL;
 static SDL_bool SDL_joystick_allows_background_events = SDL_FALSE;
+static char joystick_magic;
+
+
+#define CHECK_JOYSTICK_MAGIC(joystick, retval) \
+    if (!joystick || joystick->magic != &joystick_magic) { \
+        SDL_InvalidParamError("joystick"); \
+        return retval; \
+    }
 
 SDL_bool
 SDL_JoysticksInitialized(void)
@@ -497,6 +505,7 @@ SDL_JoystickOpen(int device_index)
         SDL_UnlockJoysticks();
         return NULL;
     }
+    joystick->magic = &joystick_magic;
     joystick->driver = driver;
     joystick->instance_id = instance_id;
     joystick->attached = SDL_TRUE;
@@ -648,6 +657,8 @@ SDL_JoystickIsVirtual(int device_index)
 int
 SDL_JoystickSetVirtualAxis(SDL_Joystick *joystick, int axis, Sint16 value)
 {
+    CHECK_JOYSTICK_MAGIC(joystick, -1);
+
 #if SDL_JOYSTICK_VIRTUAL
     return SDL_JoystickSetVirtualAxisInner(joystick, axis, value);
 #else
@@ -658,6 +669,8 @@ SDL_JoystickSetVirtualAxis(SDL_Joystick *joystick, int axis, Sint16 value)
 int
 SDL_JoystickSetVirtualButton(SDL_Joystick *joystick, int button, Uint8 value)
 {
+    CHECK_JOYSTICK_MAGIC(joystick, -1);
+
 #if SDL_JOYSTICK_VIRTUAL
     return SDL_JoystickSetVirtualButtonInner(joystick, button, value);
 #else
@@ -668,6 +681,8 @@ SDL_JoystickSetVirtualButton(SDL_Joystick *joystick, int button, Uint8 value)
 int
 SDL_JoystickSetVirtualHat(SDL_Joystick *joystick, int hat, Uint8 value)
 {
+    CHECK_JOYSTICK_MAGIC(joystick, -1);
+
 #if SDL_JOYSTICK_VIRTUAL
     return SDL_JoystickSetVirtualHatInner(joystick, hat, value);
 #else
@@ -681,16 +696,8 @@ SDL_JoystickSetVirtualHat(SDL_Joystick *joystick, int hat, Uint8 value)
 SDL_bool
 SDL_PrivateJoystickValid(SDL_Joystick *joystick)
 {
-    SDL_bool valid;
-
-    if (joystick == NULL) {
-        SDL_SetError("Joystick hasn't been opened yet");
-        valid = SDL_FALSE;
-    } else {
-        valid = SDL_TRUE;
-    }
-
-    return valid;
+    CHECK_JOYSTICK_MAGIC(joystick, SDL_FALSE);
+    return SDL_TRUE;
 }
 
 SDL_bool
@@ -714,9 +721,8 @@ SDL_PrivateJoystickGetAutoGamepadMapping(int device_index, SDL_GamepadMapping *
 int
 SDL_JoystickNumAxes(SDL_Joystick *joystick)
 {
-    if (!SDL_PrivateJoystickValid(joystick)) {
-        return -1;
-    }
+    CHECK_JOYSTICK_MAGIC(joystick, -1);
+
     return joystick->naxes;
 }
 
@@ -726,9 +732,8 @@ SDL_JoystickNumAxes(SDL_Joystick *joystick)
 int
 SDL_JoystickNumHats(SDL_Joystick *joystick)
 {
-    if (!SDL_PrivateJoystickValid(joystick)) {
-        return -1;
-    }
+    CHECK_JOYSTICK_MAGIC(joystick, -1);
+
     return joystick->nhats;
 }
 
@@ -738,9 +743,8 @@ SDL_JoystickNumHats(SDL_Joystick *joystick)
 int
 SDL_JoystickNumBalls(SDL_Joystick *joystick)
 {
-    if (!SDL_PrivateJoystickValid(joystick)) {
-        return -1;
-    }
+    CHECK_JOYSTICK_MAGIC(joystick, -1);
+
     return joystick->nballs;
 }
 
@@ -750,9 +754,8 @@ SDL_JoystickNumBalls(SDL_Joystick *joystick)
 int
 SDL_JoystickNumButtons(SDL_Joystick *joystick)
 {
-    if (!SDL_PrivateJoystickValid(joystick)) {
-        return -1;
-    }
+    CHECK_JOYSTICK_MAGIC(joystick, -1);
+
     return joystick->nbuttons;
 }
 
@@ -764,9 +767,8 @@ SDL_JoystickGetAxis(SDL_Joystick *joystick, int axis)
 {
     Sint16 state;
 
-    if (!SDL_PrivateJoystickValid(joystick)) {
-        return 0;
-    }
+    CHECK_JOYSTICK_MAGIC(joystick, 0);
+
     if (axis < joystick->naxes) {
         state = joystick->axes[axis].value;
     } else {
@@ -782,9 +784,8 @@ SDL_JoystickGetAxis(SDL_Joystick *joystick, int axis)
 SDL_bool
 SDL_JoystickGetAxisInitialState(SDL_Joystick *joystick, int axis, Sint16 *state)
 {
-    if (!SDL_PrivateJoystickValid(joystick)) {
-        return SDL_FALSE;
-    }
+    CHECK_JOYSTICK_MAGIC(joystick, SDL_FALSE);
+
     if (axis >= joystick->naxes) {
         SDL_SetError("Joystick only has %d axes", joystick->naxes);
         return SDL_FALSE;
@@ -803,9 +804,8 @@ SDL_JoystickGetHat(SDL_Joystick *joystick, int hat)
 {
     Uint8 state;
 
-    if (!SDL_PrivateJoystickValid(joystick)) {
-        return 0;
-    }
+    CHECK_JOYSTICK_MAGIC(joystick, 0);
+
     if (hat < joystick->nhats) {
         state = joystick->hats[hat];
     } else {
@@ -823,9 +823,7 @@ SDL_JoystickGetBall(SDL_Joystick *joystick, int ball, int *dx, int *dy)
 {
     int retval;
 
-    if (!SDL_PrivateJoystickValid(joystick)) {
-        return -1;
-    }
+    CHECK_JOYSTICK_MAGIC(joystick, -1);
 
     retval = 0;
     if (ball < joystick->nballs) {
@@ -851,9 +849,8 @@ SDL_JoystickGetButton(SDL_Joystick *joystick, int button)
 {
     Uint8 state;
 
-    if (!SDL_PrivateJoystickValid(joystick)) {
-        return 0;
-    }
+    CHECK_JOYSTICK_MAGIC(joystick, 0);
+
     if (button < joystick->nbuttons) {
         state = joystick->buttons[button];
     } else {
@@ -870,9 +867,7 @@ SDL_JoystickGetButton(SDL_Joystick *joystick, int button)
 SDL_bool
 SDL_JoystickGetAttached(SDL_Joystick *joystick)
 {
-    if (!joystick) {
-        return SDL_FALSE;
-    }
+    CHECK_JOYSTICK_MAGIC(joystick, SDL_FALSE);
 
     return joystick->attached;
 }
@@ -883,9 +878,7 @@ SDL_JoystickGetAttached(SDL_Joystick *joystick)
 SDL_JoystickID
 SDL_JoystickInstanceID(SDL_Joystick *joystick)
 {
-    if (!SDL_PrivateJoystickValid(joystick)) {
-        return -1;
-    }
+    CHECK_JOYSTICK_MAGIC(joystick, -1);
 
     return joystick->instance_id;
 }
@@ -934,9 +927,7 @@ SDL_JoystickFromPlayerIndex(int player_index)
 const char *
 SDL_JoystickName(SDL_Joystick *joystick)
 {
-    if (!SDL_PrivateJoystickValid(joystick)) {
-        return NULL;
-    }
+    CHECK_JOYSTICK_MAGIC(joystick, NULL);
 
     return joystick->name;
 }
@@ -947,9 +938,7 @@ SDL_JoystickName(SDL_Joystick *joystick)
 const char *
 SDL_JoystickPath(SDL_Joystick *joystick)
 {
-    if (!SDL_PrivateJoystickValid(joystick)) {
-        return NULL;
-    }
+    CHECK_JOYSTICK_MAGIC(joystick, NULL);
 
     if (!joystick->path) {
         SDL_Unsupported();
@@ -966,9 +955,7 @@ SDL_JoystickGetPlayerIndex(SDL_Joystick *joystick)
 {
     int player_index;
 
-    if (!SDL_PrivateJoystickValid(joystick)) {
-        return -1;
-    }
+    CHECK_JOYSTICK_MAGIC(joystick, -1);
 
     SDL_LockJoysticks();
     player_index = SDL_GetPlayerIndexForJoystickID(joystick->instance_id);
@@ -983,9 +970,7 @@ SDL_JoystickGetPlayerIndex(SDL_Joystick *joystick)
 void
 SDL_JoystickSetPlayerIndex(SDL_Joystick *joystick, int player_index)
 {
-    if (!SDL_PrivateJoystickValid(joystick)) {
-        return;
-    }
+    CHECK_JOYSTICK_MAGIC(joystick, );
 
     SDL_LockJoysticks();
     SDL_SetJoystickIDForPlayerIndex(player_index, joystick->instance_id);
@@ -997,9 +982,7 @@ SDL_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 h
 {
     int result;
 
-    if (!SDL_PrivateJoystickValid(joystick)) {
-        return -1;
-    }
+    CHECK_JOYSTICK_MAGIC(joystick, -1);
 
     SDL_LockJoysticks();
     if (low_frequency_rumble == joystick->low_frequency_rumble &&
@@ -1033,9 +1016,7 @@ SDL_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 ri
 {
     int result;
 
-    if (!SDL_PrivateJoystickValid(joystick)) {
-        return -1;
-    }
+    CHECK_JOYSTICK_MAGIC(joystick, -1);
 
     SDL_LockJoysticks();
     if (left_rumble == joystick->left_trigger_rumble && right_rumble == joystick->right_trigger_rumble) {
@@ -1068,9 +1049,7 @@ SDL_JoystickHasLED(SDL_Joystick *joystick)
 {
     SDL_bool result;
 
-    if (!SDL_PrivateJoystickValid(joystick)) {
-        return SDL_FALSE;
-    }
+    CHECK_JOYSTICK_MAGIC(joystick, SDL_FALSE);
 
     SDL_LockJoysticks();
 
@@ -1086,9 +1065,7 @@ SDL_JoystickHasRumble(SDL_Joystick *joystick)
 {
     SDL_bool result;
 
-    if (!SDL_PrivateJoystickValid(joystick)) {
-        return SDL_FALSE;
-    }
+    CHECK_JOYSTICK_MAGIC(joystick, SDL_FALSE);
 
     SDL_LockJoysticks();
 
@@ -1104,9 +1081,7 @@ SDL_JoystickHasRumbleTriggers(SDL_Joystick *joystick)
 {
     SDL_bool result;
 
-    if (!SDL_PrivateJoystickValid(joystick)) {
-        return SDL_FALSE;
-    }
+    CHECK_JOYSTICK_MAGIC(joystick, SDL_FALSE);
 
     SDL_LockJoysticks();
 
@@ -1123,9 +1098,7 @@ SDL_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
     int result;
     SDL_bool isfreshvalue;
 
-    if (!SDL_PrivateJoystickValid(joystick)) {
-        return -1;
-    }
+    CHECK_JOYSTICK_MAGIC(joystick, -1);
 
     SDL_LockJoysticks();
 
@@ -1156,9 +1129,7 @@ SDL_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
 {
     int result;
 
-    if (!SDL_PrivateJoystickValid(joystick)) {
-        return -1;
-    }
+    CHECK_JOYSTICK_MAGIC(joystick, -1);
 
     SDL_LockJoysticks();
 
@@ -1179,9 +1150,7 @@ SDL_JoystickClose(SDL_Joystick *joystick)
     SDL_Joystick *joysticklistprev;
     int i;
 
-    if (!SDL_PrivateJoystickValid(joystick)) {
-        return;
-    }
+    CHECK_JOYSTICK_MAGIC(joystick, );
 
     SDL_LockJoysticks();
 
@@ -1200,6 +1169,7 @@ SDL_JoystickClose(SDL_Joystick *joystick)
 
     joystick->driver->Close(joystick);
     joystick->hwdata = NULL;
+    joystick->magic = NULL;
 
     joysticklist = SDL_joysticks;
     joysticklistprev = NULL;
@@ -1297,8 +1267,13 @@ SDL_PrivateJoystickShouldIgnoreEvent()
 
 void SDL_PrivateJoystickAddTouchpad(SDL_Joystick *joystick, int nfingers)
 {
-    int ntouchpads = joystick->ntouchpads + 1;
-    SDL_JoystickTouchpadInfo *touchpads = (SDL_JoystickTouchpadInfo *)SDL_realloc(joystick->touchpads, (ntouchpads * sizeof(SDL_JoystickTouchpadInfo)));
+    int ntouchpads;
+    SDL_JoystickTouchpadInfo *touchpads;
+
+    CHECK_JOYSTICK_MAGIC(joystick, );
+
+    ntouchpads = joystick->ntouchpads + 1;
+    touchpads = (SDL_JoystickTouchpadInfo *)SDL_realloc(joystick->touchpads, (ntouchpads * sizeof(SDL_JoystickTouchpadInfo)));
     if (touchpads) {
         SDL_JoystickTouchpadInfo *touchpad = &touchpads[ntouchpads - 1];
         SDL_JoystickTouchpadFingerInfo *fingers = (SDL_JoystickTouchpadFingerInfo *)SDL_calloc(nfingers, sizeof(SDL_JoystickTouchpadFingerInfo));
@@ -1319,8 +1294,13 @@ void SDL_PrivateJoystickAddTouchpad(SDL_Joystick *joystick, int nfingers)
 
 void SDL_PrivateJoystickAddSensor(SDL_Joystick *joystick, SDL_SensorType type, float rate)
 {
-    int nsensors = joystick->nsensors + 1;
-    SDL_JoystickSensorInfo *sensors = (SDL_JoystickSensorInfo *)SDL_realloc(joystick->sensors, (nsensors * sizeof(SDL_JoystickSensorInfo)));
+    int nsensors;
+    SDL_JoystickSensorInfo *sensors;
+
+    CHECK_JOYSTICK_MAGIC(joystick, );
+
+    nsensors = joystick->nsensors + 1;
+    sensors = (SDL_JoystickSensorInfo *)SDL_realloc(joystick->sensors, (nsensors * sizeof(SDL_JoystickSensorInfo)));
     if (sensors) {
         SDL_JoystickSensorInfo *sensor = &sensors[nsensors - 1];
 
@@ -1438,6 +1418,8 @@ SDL_PrivateJoystickForceRecentering(SDL_Joystick *joystick)
 {
     int i, j;
 
+    CHECK_JOYSTICK_MAGIC(joystick, );
+
     /* Tell the app that everything is centered/unpressed... */
     for (i = 0; i < joystick->naxes; i++) {
         if (joystick->axes[i].has_initial_value) {
@@ -1512,6 +1494,8 @@ SDL_PrivateJoystickAxis(SDL_Joystick *joystick, Uint8 axis, Sint16 value)
 
     SDL_AssertJoysticksLocked();
 
+    CHECK_JOYSTICK_MAGIC(joystick, 0);
+
     /* Make sure we're not getting garbage or duplicate events */
     if (axis >= joystick->naxes) {
         return 0;
@@ -1577,6 +1561,8 @@ SDL_PrivateJoystickHat(SDL_Joystick *joystick, Uint8 hat, Uint8 value)
 
     SDL_AssertJoysticksLocked();
 
+    CHECK_JOYSTICK_MAGIC(joystick, 0);
+
     /* Make sure we're not getting garbage or duplicate events */
     if (hat >= joystick->nhats) {
         return 0;
@@ -1620,6 +1606,8 @@ SDL_PrivateJoystickBall(SDL_Joystick *joystick, Uint8 ball,
 
     SDL_AssertJoysticksLocked();
 
+    CHECK_JOYSTICK_MAGIC(joystick, 0);
+
     /* Make sure we're not getting garbage events */
     if (ball >= joystick->nballs) {
         return 0;
@@ -1657,6 +1645,8 @@ SDL_PrivateJoystickButton(SDL_Joystick *joystick, Uint8 button, Uint8 state)
 #if !SDL_EVENTS_DISABLED
     SDL_Event event;
 
+    CHECK_JOYSTICK_MAGIC(joystick, 0);
+
     switch (state) {
     case SDL_PRESSED:
         event.type = SDL_JOYBUTTONDOWN;
@@ -2808,11 +2798,10 @@ int SDL_JoystickGetDeviceIndexFromInstanceID(SDL_JoystickID instance_id)
 
 SDL_JoystickGUID SDL_JoystickGetGUID(SDL_Joystick *joystick)
 {
-    if (!SDL_PrivateJoystickValid(joystick)) {
-

(Patch may be truncated, please check the link at the top of this post.)