hidapi: Add support for Wii U/Switch USB GameCube controller adapter.

Note that a single USB device is responsible for all 4 joysticks, so a large
rewrite of the DeviceDriver functions was necessary to allow a single device to
produce multiple joysticks.
diff --git a/include/SDL_hints.h b/include/SDL_hints.h
index cd5adb2..ac605db 100644
--- a/include/SDL_hints.h
+++ b/include/SDL_hints.h
@@ -556,6 +556,17 @@
 #define SDL_HINT_JOYSTICK_HIDAPI_XBOX   "SDL_JOYSTICK_HIDAPI_XBOX"
 
 /**
+ *  \brief  A variable controlling whether the HIDAPI driver for Nintendo GameCube controllers should be used.
+ *
+ *  This variable can be set to the following values:
+ *    "0"       - HIDAPI driver is not used
+ *    "1"       - HIDAPI driver is used
+ *
+ *  The default is the value of SDL_HINT_JOYSTICK_HIDAPI
+ */
+#define SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE "SDL_JOYSTICK_HIDAPI_GAMECUBE"
+
+/**
  *  \brief  A variable that controls whether Steam Controllers should be exposed using the SDL joystick and game controller APIs
  *
  *  The variable can be set to the following values:
diff --git a/src/joystick/SDL_gamecontrollerdb.h b/src/joystick/SDL_gamecontrollerdb.h
index 7478b9e..77fe52e 100644
--- a/src/joystick/SDL_gamecontrollerdb.h
+++ b/src/joystick/SDL_gamecontrollerdb.h
@@ -564,6 +564,7 @@
     "03000000b50700001503000010010000,impact,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,",
     "030000009b2800000300000001010000,raphnet.net 4nes4snes v1.5,a:b0,b:b4,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b1,y:b5,",
     "03000000d80400008200000003000000,IMS PCU#0 Gamepad Interface,platform:Linux,a:b1,b:b0,x:b3,y:b2,back:b4,start:b5,dpup:-a1,dpdown:+a1,dpleft:-a0,dpright:+a0,",
+    "030000007e0500003703000000016800,Nintendo GameCube Controller,platform:Linux,a:b0,b:b2,x:b1,y:b3,start:b8,rightshoulder:b9,dpup:b7,dpdown:b6,dpleft:b4,dpright:b5,leftx:a0,lefty:a1~,rightx:a2,righty:a3~,lefttrigger:a4,righttrigger:a5,",
 #endif
 #if defined(__ANDROID__)
     "05000000d6020000e5890000dfff3f00,GPD XD Plus,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,",
diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c
index a0d5b7b..a5b3e63 100644
--- a/src/joystick/SDL_joystick.c
+++ b/src/joystick/SDL_joystick.c
@@ -1033,6 +1033,11 @@
     /* Make sure the list is unlocked while dispatching events to prevent application deadlocks */
     SDL_UnlockJoysticks();
 
+    /* Special function for HIDAPI devices, as a single device can provide multiple SDL_Joysticks */
+#ifdef SDL_JOYSTICK_HIDAPI
+    SDL_HIDAPI_UpdateDevices();
+#endif /* SDL_JOYSTICK_HIDAPI */
+
     for (joystick = SDL_joysticks; joystick; joystick = joystick->next) {
         if (joystick->attached) {
             joystick->driver->Update(joystick);
@@ -1188,6 +1193,12 @@
 }
 
 SDL_bool
+SDL_IsJoystickGameCube(Uint16 vendor, Uint16 product)
+{
+    return (GuessControllerType(vendor, product) == k_eControllerType_GameCube);
+}
+
+SDL_bool
 SDL_IsJoystickXInput(SDL_JoystickGUID guid)
 {
     return (guid.data[14] == 'x') ? SDL_TRUE : SDL_FALSE;
diff --git a/src/joystick/SDL_joystick_c.h b/src/joystick/SDL_joystick_c.h
index 165c370..f02d30b 100644
--- a/src/joystick/SDL_joystick_c.h
+++ b/src/joystick/SDL_joystick_c.h
@@ -66,6 +66,9 @@
 /* Function to return whether a joystick is an Xbox One controller */
 extern SDL_bool SDL_IsJoystickXboxOne(Uint16 vendor_id, Uint16 product_id);
 
+/* Function to return whether a joystick is a GameCube controller */
+extern SDL_bool SDL_IsJoystickGameCube(Uint16 vendor_id, Uint16 product_id);
+
 /* Function to return whether a joystick guid comes from the XInput driver */
 extern SDL_bool SDL_IsJoystickXInput(SDL_JoystickGUID guid);
 
diff --git a/src/joystick/SDL_sysjoystick.h b/src/joystick/SDL_sysjoystick.h
index 8f57523..ef7f082 100644
--- a/src/joystick/SDL_sysjoystick.h
+++ b/src/joystick/SDL_sysjoystick.h
@@ -152,6 +152,9 @@
 extern SDL_JoystickDriver SDL_LINUX_JoystickDriver;
 extern SDL_JoystickDriver SDL_WINDOWS_JoystickDriver;
 
+/* Special function to update HIDAPI devices */
+extern void SDL_HIDAPI_UpdateDevices(void);
+
 #endif /* SDL_sysjoystick_h_ */
 
 /* vi: set ts=4 sw=4 expandtab: */
diff --git a/src/joystick/controller_type.h b/src/joystick/controller_type.h
index 51ac20b..22dec71 100644
--- a/src/joystick/controller_type.h
+++ b/src/joystick/controller_type.h
@@ -57,6 +57,7 @@
 	k_eControllerType_SwitchJoyConPair = 41,
     k_eControllerType_SwitchInputOnlyController = 42,
 	k_eControllerType_MobileTouch = 43,
+        k_eControllerType_GameCube = 44,
 	k_eControllerType_LastController,			// Don't add game controllers below this enumeration - this enumeration can change value
 
 	// Keyboards and Mice
@@ -387,6 +388,8 @@
     { MAKE_CONTROLLER_ID( 0x20d6, 0xa711 ), k_eControllerType_SwitchInputOnlyController },  // PowerA Wired Controller Plus
     { MAKE_CONTROLLER_ID( 0x0f0d, 0x0092 ), k_eControllerType_SwitchInputOnlyController },  // HORI Pokken Tournament DX Pro Pad
 
+    { MAKE_CONTROLLER_ID( 0x057e, 0x0337 ), k_eControllerType_GameCube }, // Nintendo Wii U/Switch GameCube USB Adapter
+
 
 	// Valve products - don't add to public list
     { MAKE_CONTROLLER_ID( 0x0000, 0x11fb ), k_eControllerType_MobileTouch },		// Streaming mobile touch virtual controls
diff --git a/src/joystick/hidapi/SDL_hidapi_gamecube.c b/src/joystick/hidapi/SDL_hidapi_gamecube.c
new file mode 100644
index 0000000..d08fff2
--- /dev/null
+++ b/src/joystick/hidapi/SDL_hidapi_gamecube.c
@@ -0,0 +1,339 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+#include "../../SDL_internal.h"
+
+#ifdef SDL_JOYSTICK_HIDAPI
+
+#include "SDL_hints.h"
+#include "SDL_log.h"
+#include "SDL_events.h"
+#include "SDL_timer.h"
+#include "SDL_haptic.h"
+#include "SDL_joystick.h"
+#include "SDL_gamecontroller.h"
+#include "../SDL_sysjoystick.h"
+#include "SDL_hidapijoystick_c.h"
+
+
+#ifdef SDL_JOYSTICK_HIDAPI_GAMECUBE
+
+typedef struct {
+    SDL_JoystickID joysticks[4];
+    Uint8 rumble[5];
+    Uint32 rumbleExpiration[4];
+    /* Without this variable, hid_write starts to lag a TON */
+    Uint8 rumbleUpdate;
+} SDL_DriverGameCube_Context;
+
+static SDL_bool
+HIDAPI_DriverGameCube_IsSupportedDevice(Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number)
+{
+    return SDL_IsJoystickGameCube(vendor_id, product_id);
+}
+
+static const char *
+HIDAPI_DriverGameCube_GetDeviceName(Uint16 vendor_id, Uint16 product_id)
+{
+    /* Give a user friendly name for this controller */
+    if (SDL_IsJoystickGameCube(vendor_id, product_id)) {
+        return "Nintendo GameCube Controller";
+    }
+    return NULL;
+}
+
+static SDL_bool
+HIDAPI_DriverGameCube_InitDriver(SDL_HIDAPI_DriverData *context, Uint16 vendor_id, Uint16 product_id, int *num_joysticks)
+{
+    SDL_DriverGameCube_Context *ctx;
+    Uint8 packet[37];
+    Uint8 *curSlot;
+    Uint8 i;
+    int size;
+    Uint8 initMagic = 0x13;
+    Uint8 rumbleMagic = 0x11;
+
+    ctx = (SDL_DriverGameCube_Context *)SDL_calloc(1, sizeof(*ctx));
+    if (!ctx) {
+        SDL_OutOfMemory();
+        return SDL_FALSE;
+    }
+    ctx->joysticks[0] = -1;
+    ctx->joysticks[1] = -1;
+    ctx->joysticks[2] = -1;
+    ctx->joysticks[3] = -1;
+    ctx->rumble[0] = rumbleMagic;
+
+    context->context = ctx;
+
+    /* This is all that's needed to initialize the device. Really! */
+    if (hid_write(context->device, &initMagic, sizeof(initMagic)) <= 0) {
+        SDL_SetError("Couldn't initialize WUP-028");
+        SDL_free(ctx);
+        return SDL_FALSE;
+    }
+
+    /* Add all the applicable joysticks */
+    while ((size = hid_read_timeout(context->device, packet, sizeof(packet), 0)) > 0) {
+        if (size < 37 || packet[0] != 0x21) {
+            continue; /* Nothing to do yet...? */
+        }
+
+        /* Go through all 4 slots */
+        curSlot = packet + 1;
+        for (i = 0; i < 4; i += 1, curSlot += 9) {
+            if (curSlot[0] & 0x30) { /* 0x10 - Wired, 0x20 - Wireless */
+                if (ctx->joysticks[i] == -1) {
+                    ctx->joysticks[i] = SDL_GetNextJoystickInstanceID();
+
+                    *num_joysticks += 1;
+
+                    SDL_PrivateJoystickAdded(ctx->joysticks[i]);
+                }
+            } else {
+                if (ctx->joysticks[i] != -1) {
+                    SDL_PrivateJoystickRemoved(ctx->joysticks[i]);
+
+                    *num_joysticks -= 1;
+
+                    ctx->joysticks[i] = -1;
+                }
+                continue;
+            }
+        }
+    }
+
+    return SDL_TRUE;
+}
+
+static void
+HIDAPI_DriverGameCube_QuitDriver(SDL_HIDAPI_DriverData *context, SDL_bool send_event, int *num_joysticks)
+{
+    SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)context->context;
+    Uint8 i;
+
+    /* Stop all rumble activity */
+    for (i = 1; i < 5; i += 1) {
+        ctx->rumble[i] = 0;
+    }
+    hid_write(context->device, ctx->rumble, sizeof(ctx->rumble));
+
+    /* Remove all joysticks! */
+    for (i = 0; i < 4; i += 1) {
+        if (ctx->joysticks[i] != -1) {
+            *num_joysticks -= 1;
+            if (send_event) {
+                SDL_PrivateJoystickRemoved(ctx->joysticks[i]);
+            }
+        }
+    }
+
+    SDL_free(context->context);
+}
+
+static SDL_bool
+HIDAPI_DriverGameCube_UpdateDriver(SDL_HIDAPI_DriverData *context, int *num_joysticks)
+{
+    SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)context->context;
+    SDL_Joystick *joystick;
+    Uint8 packet[37];
+    Uint8 *curSlot;
+    Uint32 now;
+    Uint8 i;
+    int size;
+
+    /* Read input packet */
+    while ((size = hid_read_timeout(context->device, packet, sizeof(packet), 0)) > 0) {
+        if (size < 37 || packet[0] != 0x21) {
+            continue; /* Nothing to do right now...? */
+        }
+
+        /* Go through all 4 slots */
+        curSlot = packet + 1;
+        for (i = 0; i < 4; i += 1, curSlot += 9) {
+            if (curSlot[0] & 0x30) { /* 0x10 - Wired, 0x20 - Wireless */
+                if (ctx->joysticks[i] == -1) {
+                    ctx->joysticks[i] = SDL_GetNextJoystickInstanceID();
+
+                    *num_joysticks += 1;
+
+                    SDL_PrivateJoystickAdded(ctx->joysticks[i]);
+                }
+                joystick = SDL_JoystickFromInstanceID(ctx->joysticks[i]);
+
+                /* Hasn't been opened yet, skip */
+                if (joystick == NULL) {
+                    continue;
+                }
+            } else {
+                if (ctx->joysticks[i] != -1) {
+                    SDL_PrivateJoystickRemoved(ctx->joysticks[i]);
+
+                    *num_joysticks -= 1;
+
+                    ctx->joysticks[i] = -1;
+                }
+                continue;
+            }
+
+            #define READ_BUTTON(off, flag, button) \
+                SDL_PrivateJoystickButton( \
+                    joystick, \
+                    button, \
+                    (curSlot[off] & flag) ? SDL_PRESSED : SDL_RELEASED \
+                );
+            READ_BUTTON(1, 0x01, 0) /* A */
+            READ_BUTTON(1, 0x02, 1) /* B */
+            READ_BUTTON(1, 0x04, 2) /* X */
+            READ_BUTTON(1, 0x08, 3) /* Y */
+            READ_BUTTON(1, 0x10, 4) /* DPAD_LEFT */
+            READ_BUTTON(1, 0x20, 5) /* DPAD_RIGHT */
+            READ_BUTTON(1, 0x40, 6) /* DPAD_DOWN */
+            READ_BUTTON(1, 0x80, 7) /* DPAD_UP */
+            READ_BUTTON(2, 0x01, 8) /* START */
+            READ_BUTTON(2, 0x02, 9) /* RIGHTSHOULDER */
+            /* [2] 0x04 - R, [2] 0x08 - L */
+            #undef READ_BUTTON
+
+            /* Axis math taken from SDL_xinputjoystick.c */
+            #define READ_AXIS(off, axis) \
+                SDL_PrivateJoystickAxis( \
+                    joystick, \
+                    axis, \
+                    (Sint16)(((int)curSlot[off] * 257) - 32768) \
+                );
+            READ_AXIS(3, 0) /* LEFTX */
+            READ_AXIS(4, 1) /* LEFTY */
+            READ_AXIS(5, 2) /* RIGHTX */
+            READ_AXIS(6, 3) /* RIGHTY */
+            READ_AXIS(7, 4) /* TRIGGERLEFT */
+            READ_AXIS(8, 5) /* TRIGGERRIGHT */
+            #undef READ_AXIS
+        }
+    }
+
+    /* Write rumble packet */
+    now = SDL_GetTicks();
+    for (i = 0; i < 4; i += 1) {
+        if (ctx->rumbleExpiration[i] > 0) {
+            if (SDL_TICKS_PASSED(now, ctx->rumbleExpiration[i])) {
+                ctx->rumble[i + 1] = 0;
+                ctx->rumbleExpiration[i] = 0;
+                ctx->rumbleUpdate = 1;
+            }
+        }
+    }
+    if (ctx->rumbleUpdate) {
+        hid_write(context->device, ctx->rumble, sizeof(ctx->rumble));
+        ctx->rumbleUpdate = 0;
+    }
+
+    /* If we got here, nothing bad happened! */
+    return SDL_TRUE;
+}
+
+static int
+HIDAPI_DriverGameCube_NumJoysticks(SDL_HIDAPI_DriverData *context)
+{
+    SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)context->context;
+    int i, joysticks = 0;
+    for (i = 0; i < 4; i += 1) {
+        if (ctx->joysticks[i] != -1) {
+            joysticks += 1;
+        }
+    }
+    return joysticks;
+}
+
+static SDL_JoystickID
+HIDAPI_DriverGameCube_InstanceIDForIndex(SDL_HIDAPI_DriverData *context, int index)
+{
+    SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)context->context;
+    Uint8 i;
+    for (i = 0; i < 4; i += 1) {
+        if (ctx->joysticks[i] != -1) {
+            if (index == 0) {
+                return ctx->joysticks[i];
+            }
+            index -= 1;
+        }
+    }
+    return -1; /* Should never get here! */
+}
+
+static SDL_bool
+HIDAPI_DriverGameCube_OpenJoystick(SDL_HIDAPI_DriverData *context, SDL_Joystick *joystick)
+{
+    SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)context->context;
+    SDL_JoystickID instance = SDL_JoystickInstanceID(joystick);
+    Uint8 i;
+    for (i = 0; i < 4; i += 1) {
+        if (instance == ctx->joysticks[i]) {
+            joystick->nbuttons = 10;
+            joystick->naxes = 6;
+            joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED;
+            return SDL_TRUE;
+        }
+    }
+    return SDL_FALSE; /* Should never get here! */
+}
+
+static int
+HIDAPI_DriverGameCube_Rumble(SDL_HIDAPI_DriverData *context, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
+{
+    SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)context->context;
+    SDL_JoystickID instance = SDL_JoystickInstanceID(joystick);
+    Uint8 i, val;
+    for (i = 0; i < 4; i += 1) {
+        if (instance == ctx->joysticks[i]) {
+            val = (low_frequency_rumble > 0 || high_frequency_rumble > 0);
+            if (val != ctx->rumble[i + 1]) {
+                ctx->rumble[i + 1] = val;
+                ctx->rumbleUpdate = 1;
+            }
+            if (val && duration_ms < SDL_HAPTIC_INFINITY) {
+                ctx->rumbleExpiration[i] = SDL_GetTicks() + duration_ms;
+            } else {
+                ctx->rumbleExpiration[i] = 0;
+            }
+            return 0;
+        }
+    }
+    return -1;
+}
+
+SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGameCube =
+{
+    SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE,
+    SDL_TRUE,
+    HIDAPI_DriverGameCube_IsSupportedDevice,
+    HIDAPI_DriverGameCube_GetDeviceName,
+    HIDAPI_DriverGameCube_InitDriver,
+    HIDAPI_DriverGameCube_QuitDriver,
+    HIDAPI_DriverGameCube_UpdateDriver,
+    HIDAPI_DriverGameCube_NumJoysticks,
+    HIDAPI_DriverGameCube_InstanceIDForIndex,
+    HIDAPI_DriverGameCube_OpenJoystick,
+    HIDAPI_DriverGameCube_Rumble
+};
+
+#endif /* SDL_JOYSTICK_HIDAPI_GAMECUBE */
+
+#endif /* SDL_JOYSTICK_HIDAPI */
diff --git a/src/joystick/hidapi/SDL_hidapi_ps4.c b/src/joystick/hidapi/SDL_hidapi_ps4.c
index 9d160f9..e673200 100644
--- a/src/joystick/hidapi/SDL_hidapi_ps4.c
+++ b/src/joystick/hidapi/SDL_hidapi_ps4.c
@@ -108,6 +108,7 @@
 } DS4EffectsState_t;
 
 typedef struct {
+    SDL_JoystickID joystickID;
     SDL_bool is_dongle;
     SDL_bool is_bluetooth;
     SDL_bool audio_supported;
@@ -272,10 +273,8 @@
     return SDL_TRUE;
 }
 
-static int HIDAPI_DriverPS4_Rumble(SDL_Joystick *joystick, hid_device *dev, void *context, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms);
-
 static SDL_bool
-HIDAPI_DriverPS4_Init(SDL_Joystick *joystick, hid_device *dev, Uint16 vendor_id, Uint16 product_id, void **context)
+HIDAPI_DriverPS4_InitDriver(SDL_HIDAPI_DriverData *context, Uint16 vendor_id, Uint16 product_id, int *num_joysticks)
 {
     SDL_DriverPS4_Context *ctx;
 
@@ -284,14 +283,14 @@
         SDL_OutOfMemory();
         return SDL_FALSE;
     }
-    *context = ctx;
+    context->context = ctx;
 
     /* Check for type of connection */
     ctx->is_dongle = (vendor_id == SONY_USB_VID && product_id == SONY_DS4_DONGLE_PID);
     if (ctx->is_dongle) {
         ctx->is_bluetooth = SDL_FALSE;
     } else if (vendor_id == SONY_USB_VID) {
-        ctx->is_bluetooth = !CheckUSBConnected(dev);
+        ctx->is_bluetooth = !CheckUSBConnected(context->device);
     } else {
         /* Third party controllers appear to all be wired */
         ctx->is_bluetooth = SDL_FALSE;
@@ -314,8 +313,45 @@
         }
     }
 
+    ctx->joystickID = SDL_GetNextJoystickInstanceID();
+    *num_joysticks += 1;
+    SDL_PrivateJoystickAdded(ctx->joystickID);
+
+    return SDL_TRUE;
+}
+
+static void
+HIDAPI_DriverPS4_QuitDriver(SDL_HIDAPI_DriverData *context, SDL_bool send_event, int *num_joysticks)
+{
+    SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)context->context;
+
+    *num_joysticks -= 1;
+    if (send_event) {
+        SDL_PrivateJoystickRemoved(ctx->joystickID);
+    }
+    SDL_free(context->context);
+}
+
+static int
+HIDAPI_DriverPS4_NumJoysticks(SDL_HIDAPI_DriverData *context)
+{
+    return 1;
+}
+
+static SDL_JoystickID
+HIDAPI_DriverPS4_InstanceIDForIndex(SDL_HIDAPI_DriverData *context, int index)
+{
+    SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)context->context;
+    return ctx->joystickID;
+}
+
+static int HIDAPI_DriverPS4_Rumble(SDL_HIDAPI_DriverData *context, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms);
+
+static SDL_bool
+HIDAPI_DriverPS4_OpenJoystick(SDL_HIDAPI_DriverData *context, SDL_Joystick *joystick)
+{
     /* Initialize LED and effect state */
-    HIDAPI_DriverPS4_Rumble(joystick, dev, ctx, 0, 0, 0);
+    HIDAPI_DriverPS4_Rumble(context, joystick, 0, 0, 0);
 
     /* Initialize the joystick capabilities */
     joystick->nbuttons = SDL_CONTROLLER_BUTTON_MAX;
@@ -326,9 +362,9 @@
 }
 
 static int
-HIDAPI_DriverPS4_Rumble(SDL_Joystick *joystick, hid_device *dev, void *context, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
+HIDAPI_DriverPS4_Rumble(SDL_HIDAPI_DriverData *context, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
 {
-    SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)context;
+    SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)context->context;
     DS4EffectsState_t *effects;
     Uint8 data[78];
     int report_size, offset;
@@ -386,7 +422,7 @@
         SDL_memcpy(&data[report_size - sizeof(unCRC)], &unCRC, sizeof(unCRC));
     }
 
-    if (hid_write(dev, data, report_size) != report_size) {
+    if (hid_write(context->device, data, report_size) != report_size) {
         return SDL_SetError("Couldn't send rumble packet");
     }
 
@@ -508,20 +544,25 @@
 }
 
 static SDL_bool
-HIDAPI_DriverPS4_Update(SDL_Joystick *joystick, hid_device *dev, void *context)
+HIDAPI_DriverPS4_UpdateDriver(SDL_HIDAPI_DriverData *context, int *num_joysticks)
 {
-    SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)context;
+    SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)context->context;
+    SDL_Joystick *joystick = SDL_JoystickFromInstanceID(ctx->joystickID);
     Uint8 data[USB_PACKET_LENGTH];
     int size;
 
-    while ((size = hid_read_timeout(dev, data, sizeof(data), 0)) > 0) {
+    if (joystick == NULL) {
+        return SDL_TRUE; /* Nothing to do right now! */
+    }
+
+    while ((size = hid_read_timeout(context->device, data, sizeof(data), 0)) > 0) {
         switch (data[0]) {
         case k_EPS4ReportIdUsbState:
-            HIDAPI_DriverPS4_HandleStatePacket(joystick, dev, ctx, (PS4StatePacket_t *)&data[1]);
+            HIDAPI_DriverPS4_HandleStatePacket(joystick, context->device, ctx, (PS4StatePacket_t *)&data[1]);
             break;
         case k_EPS4ReportIdBluetoothState:
             /* Bluetooth state packets have two additional bytes at the beginning */
-            HIDAPI_DriverPS4_HandleStatePacket(joystick, dev, ctx, (PS4StatePacket_t *)&data[3]);
+            HIDAPI_DriverPS4_HandleStatePacket(joystick, context->device, ctx, (PS4StatePacket_t *)&data[3]);
             break;
         default:
 #ifdef DEBUG_JOYSTICK
@@ -534,29 +575,26 @@
     if (ctx->rumble_expiration) {
         Uint32 now = SDL_GetTicks();
         if (SDL_TICKS_PASSED(now, ctx->rumble_expiration)) {
-            HIDAPI_DriverPS4_Rumble(joystick, dev, context, 0, 0, 0);
+            HIDAPI_DriverPS4_Rumble(context, joystick, 0, 0, 0);
         }
     }
 
     return (size >= 0);
 }
 
-static void
-HIDAPI_DriverPS4_Quit(SDL_Joystick *joystick, hid_device *dev, void *context)
-{
-    SDL_free(context);
-}
-
 SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS4 =
 {
     SDL_HINT_JOYSTICK_HIDAPI_PS4,
     SDL_TRUE,
     HIDAPI_DriverPS4_IsSupportedDevice,
     HIDAPI_DriverPS4_GetDeviceName,
-    HIDAPI_DriverPS4_Init,
-    HIDAPI_DriverPS4_Rumble,
-    HIDAPI_DriverPS4_Update,
-    HIDAPI_DriverPS4_Quit
+    HIDAPI_DriverPS4_InitDriver,
+    HIDAPI_DriverPS4_QuitDriver,
+    HIDAPI_DriverPS4_UpdateDriver,
+    HIDAPI_DriverPS4_NumJoysticks,
+    HIDAPI_DriverPS4_InstanceIDForIndex,
+    HIDAPI_DriverPS4_OpenJoystick,
+    HIDAPI_DriverPS4_Rumble
 };
 
 #endif /* SDL_JOYSTICK_HIDAPI_PS4 */
diff --git a/src/joystick/hidapi/SDL_hidapi_switch.c b/src/joystick/hidapi/SDL_hidapi_switch.c
index 27c988c..b81112b 100644
--- a/src/joystick/hidapi/SDL_hidapi_switch.c
+++ b/src/joystick/hidapi/SDL_hidapi_switch.c
@@ -183,6 +183,7 @@
 #pragma pack()
 
 typedef struct {
+    SDL_JoystickID joystickID;
     hid_device *dev;
     SDL_bool m_bIsUsingBluetooth;
     Uint8 m_nCommandNumber;
@@ -570,7 +571,7 @@
 }
 
 static SDL_bool
-HIDAPI_DriverSwitch_Init(SDL_Joystick *joystick, hid_device *dev, Uint16 vendor_id, Uint16 product_id, void **context)
+HIDAPI_DriverSwitch_InitDriver(SDL_HIDAPI_DriverData *context, Uint16 vendor_id, Uint16 product_id, int *num_joysticks)
 {
     SDL_DriverSwitch_Context *ctx;
     Uint8 input_mode;
@@ -580,9 +581,9 @@
         SDL_OutOfMemory();
         return SDL_FALSE;
     }
-    ctx->dev = dev;
+    ctx->dev = context->device;
 
-    *context = ctx;
+    context->context = ctx;
 
     /* Initialize rumble data */
     SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[0]);
@@ -627,6 +628,18 @@
         }
     }
 
+    ctx->joystickID = SDL_GetNextJoystickInstanceID();
+    *num_joysticks += 1;
+    SDL_PrivateJoystickAdded(ctx->joystickID);
+
+    return SDL_TRUE;
+}
+
+static SDL_bool
+HIDAPI_DriverSwitch_OpenJoystick(SDL_HIDAPI_DriverData *context, SDL_Joystick *joystick)
+{
+    SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)context->context;
+
     /* Set the LED state */
     SetHomeLED(ctx, 100);
     SetSlotLED(ctx, (joystick->instance_id % 4));
@@ -640,9 +653,9 @@
 }
 
 static int
-HIDAPI_DriverSwitch_Rumble(SDL_Joystick *joystick, hid_device *dev, void *context, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
+HIDAPI_DriverSwitch_Rumble(SDL_HIDAPI_DriverData *context, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
 {
-    SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)context;
+    SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)context->context;
 
     /* Experimentally determined rumble values. These will only matter on some controllers as tested ones
      * seem to disregard these and just use any non-zero rumble values as a binary flag for constant rumble
@@ -847,11 +860,16 @@
 }
 
 static SDL_bool
-HIDAPI_DriverSwitch_Update(SDL_Joystick *joystick, hid_device *dev, void *context)
+HIDAPI_DriverSwitch_UpdateDriver(SDL_HIDAPI_DriverData *context, int *num_joysticks)
 {
-    SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)context;
+    SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)context->context;
+    SDL_Joystick *joystick = SDL_JoystickFromInstanceID(ctx->joystickID);
     int size;
 
+    if (joystick == NULL) {
+        return SDL_TRUE; /* Nothing to do right now! */
+    }
+
     while ((size = ReadInput(ctx)) > 0) {
         switch (ctx->m_rgucReadBuffer[0]) {
         case k_eSwitchInputReportIDs_SimpleControllerState:
@@ -868,7 +886,7 @@
     if (ctx->m_nRumbleExpiration) {
         Uint32 now = SDL_GetTicks();
         if (SDL_TICKS_PASSED(now, ctx->m_nRumbleExpiration)) {
-            HIDAPI_DriverSwitch_Rumble(joystick, dev, context, 0, 0, 0);
+            HIDAPI_DriverSwitch_Rumble(context, joystick, 0, 0, 0);
         }
     }
 
@@ -876,14 +894,31 @@
 }
 
 static void
-HIDAPI_DriverSwitch_Quit(SDL_Joystick *joystick, hid_device *dev, void *context)
+HIDAPI_DriverSwitch_QuitDriver(SDL_HIDAPI_DriverData *context, SDL_bool send_event, int *num_joysticks)
 {
-    SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)context;
+    SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)context->context;
 
     /* Restore simple input mode for other applications */
     SetInputMode(ctx, k_eSwitchInputReportIDs_SimpleControllerState);
 
-    SDL_free(context);
+    *num_joysticks -= 1;
+    if (send_event) {
+        SDL_PrivateJoystickRemoved(ctx->joystickID);
+    }
+    SDL_free(context->context);
+}
+
+static int
+HIDAPI_DriverSwitch_NumJoysticks(SDL_HIDAPI_DriverData *context)
+{
+    return 1;
+}
+
+static SDL_JoystickID
+HIDAPI_DriverSwitch_InstanceIDForIndex(SDL_HIDAPI_DriverData *context, int index)
+{
+    SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)context->context;
+    return ctx->joystickID;
 }
 
 SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSwitch =
@@ -892,10 +927,13 @@
     SDL_TRUE,
     HIDAPI_DriverSwitch_IsSupportedDevice,
     HIDAPI_DriverSwitch_GetDeviceName,
-    HIDAPI_DriverSwitch_Init,
-    HIDAPI_DriverSwitch_Rumble,
-    HIDAPI_DriverSwitch_Update,
-    HIDAPI_DriverSwitch_Quit
+    HIDAPI_DriverSwitch_InitDriver,
+    HIDAPI_DriverSwitch_QuitDriver,
+    HIDAPI_DriverSwitch_UpdateDriver,
+    HIDAPI_DriverSwitch_NumJoysticks,
+    HIDAPI_DriverSwitch_InstanceIDForIndex,
+    HIDAPI_DriverSwitch_OpenJoystick,
+    HIDAPI_DriverSwitch_Rumble
 };
 
 #endif /* SDL_JOYSTICK_HIDAPI_SWITCH */
diff --git a/src/joystick/hidapi/SDL_hidapi_xbox360.c b/src/joystick/hidapi/SDL_hidapi_xbox360.c
index ab1ee9e..ad0a192 100644
--- a/src/joystick/hidapi/SDL_hidapi_xbox360.c
+++ b/src/joystick/hidapi/SDL_hidapi_xbox360.c
@@ -54,6 +54,7 @@
 
 
 typedef struct {
+    SDL_JoystickID joystickID;
     Uint8 last_state[USB_PACKET_LENGTH];
     Uint32 rumble_expiration;
 #ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_XINPUT
@@ -277,7 +278,7 @@
 }
 
 static SDL_bool
-HIDAPI_DriverXbox360_Init(SDL_Joystick *joystick, hid_device *dev, Uint16 vendor_id, Uint16 product_id, void **context)
+HIDAPI_DriverXbox360_InitDriver(SDL_HIDAPI_DriverData *context, Uint16 vendor_id, Uint16 product_id, int *num_joysticks)
 {
     SDL_DriverXbox360_Context *ctx;
 
@@ -296,10 +297,20 @@
 #ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_GAMING_INPUT
     HIDAPI_DriverXbox360_InitWindowsGamingInput(ctx);
 #endif
-    *context = ctx;
+    context->context = ctx;
 
+    ctx->joystickID = SDL_GetNextJoystickInstanceID();
+    *num_joysticks += 1;
+    SDL_PrivateJoystickAdded(ctx->joystickID);
+
+    return SDL_TRUE;
+}
+
+static SDL_bool
+HIDAPI_DriverXbox360_OpenJoystick(SDL_HIDAPI_DriverData *context, SDL_Joystick *joystick)
+{
     /* Set the controller LED */
-    SetSlotLED(dev, (joystick->instance_id % 4));
+    SetSlotLED(context->device, (joystick->instance_id % 4));
 
     /* Initialize the joystick capabilities */
     joystick->nbuttons = SDL_CONTROLLER_BUTTON_MAX;
@@ -310,9 +321,22 @@
 }
 
 static int
-HIDAPI_DriverXbox360_Rumble(SDL_Joystick *joystick, hid_device *dev, void *context, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
+HIDAPI_DriverXbox360_NumJoysticks(SDL_HIDAPI_DriverData *context)
 {
-    SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)context;
+    return 1;
+}
+
+static SDL_JoystickID
+HIDAPI_DriverXbox360_InstanceIDForIndex(SDL_HIDAPI_DriverData *context, int index)
+{
+    SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)context->context;
+    return ctx->joystickID;
+}
+
+static int
+HIDAPI_DriverXbox360_Rumble(SDL_HIDAPI_DriverData *context, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
+{
+    SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)context->context;
 
 #ifdef __WIN32__
     SDL_bool rumbled = SDL_FALSE;
@@ -365,7 +389,7 @@
     rumble_packet[4] = (high_frequency_rumble >> 8);
 #endif
 
-    if (hid_write(dev, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {
+    if (hid_write(context->device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {
         return SDL_SetError("Couldn't send rumble packet");
     }
 #endif /* __WIN32__ */
@@ -705,26 +729,31 @@
 #endif /* __MACOSX__ */
 
 static SDL_bool
-HIDAPI_DriverXbox360_Update(SDL_Joystick *joystick, hid_device *dev, void *context)
+HIDAPI_DriverXbox360_UpdateDriver(SDL_HIDAPI_DriverData *context, int *num_joysticks)
 {
-    SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)context;
+    SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)context->context;
+    SDL_Joystick *joystick = SDL_JoystickFromInstanceID(ctx->joystickID);
     Uint8 data[USB_PACKET_LENGTH];
     int size;
 
-    while ((size = hid_read_timeout(dev, data, sizeof(data), 0)) > 0) {
+    if (joystick == NULL) {
+        return SDL_TRUE; /* Nothing to do right now! */
+    }
+
+    while ((size = hid_read_timeout(context->device, data, sizeof(data), 0)) > 0) {
 #ifdef __WIN32__
-        HIDAPI_DriverXbox360_HandleStatePacket(joystick, dev, ctx, data, size);
+        HIDAPI_DriverXbox360_HandleStatePacket(joystick, context->device, ctx, data, size);
 #else
         switch (data[0]) {
         case 0x00:
-            HIDAPI_DriverXbox360_HandleStatePacket(joystick, dev, ctx, data, size);
+            HIDAPI_DriverXbox360_HandleStatePacket(joystick, context->device, ctx, data, size);
             break;
 #ifdef __MACOSX__
         case 0x01:
-            HIDAPI_DriverXboxOneS_HandleStatePacket(joystick, dev, ctx, data, size);
+            HIDAPI_DriverXboxOneS_HandleStatePacket(joystick, context->device, ctx, data, size);
             break;
         case 0x02:
-            HIDAPI_DriverXboxOneS_HandleGuidePacket(joystick, dev, ctx, data, size);
+            HIDAPI_DriverXboxOneS_HandleGuidePacket(joystick, context->device, ctx, data, size);
             break;
 #endif
         default:
@@ -742,7 +771,7 @@
     if (ctx->rumble_expiration) {
         Uint32 now = SDL_GetTicks();
         if (SDL_TICKS_PASSED(now, ctx->rumble_expiration)) {
-            HIDAPI_DriverXbox360_Rumble(joystick, dev, context, 0, 0, 0);
+            HIDAPI_DriverXbox360_Rumble(context, joystick, 0, 0, 0);
         }
     }
 
@@ -750,11 +779,9 @@
 }
 
 static void
-HIDAPI_DriverXbox360_Quit(SDL_Joystick *joystick, hid_device *dev, void *context)
+HIDAPI_DriverXbox360_QuitDriver(SDL_HIDAPI_DriverData *context, SDL_bool send_event, int *num_joysticks)
 {
-#if defined(SDL_JOYSTICK_HIDAPI_WINDOWS_XINPUT) || defined(SDL_JOYSTICK_HIDAPI_WINDOWS_GAMING_INPUT)
-    SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)context;
-#endif
+    SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)context->context;
 
 #ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_XINPUT
     if (ctx->xinput_enabled) {
@@ -765,7 +792,11 @@
 #ifdef SDL_JOYSTICK_HIDAPI_WINDOWS_GAMING_INPUT
     HIDAPI_DriverXbox360_InitWindowsGamingInput(ctx);
 #endif
-    SDL_free(context);
+    *num_joysticks -= 1;
+    if (send_event) {
+        SDL_PrivateJoystickRemoved(ctx->joystickID);
+    }
+    SDL_free(context->context);
 }
 
 SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360 =
@@ -774,10 +805,13 @@
     SDL_TRUE,
     HIDAPI_DriverXbox360_IsSupportedDevice,
     HIDAPI_DriverXbox360_GetDeviceName,
-    HIDAPI_DriverXbox360_Init,
-    HIDAPI_DriverXbox360_Rumble,
-    HIDAPI_DriverXbox360_Update,
-    HIDAPI_DriverXbox360_Quit
+    HIDAPI_DriverXbox360_InitDriver,
+    HIDAPI_DriverXbox360_QuitDriver,
+    HIDAPI_DriverXbox360_UpdateDriver,
+    HIDAPI_DriverXbox360_NumJoysticks,
+    HIDAPI_DriverXbox360_InstanceIDForIndex,
+    HIDAPI_DriverXbox360_OpenJoystick,
+    HIDAPI_DriverXbox360_Rumble
 };
 
 #endif /* SDL_JOYSTICK_HIDAPI_XBOX360 */
diff --git a/src/joystick/hidapi/SDL_hidapi_xboxone.c b/src/joystick/hidapi/SDL_hidapi_xboxone.c
index fde74bb..90c2aa3 100644
--- a/src/joystick/hidapi/SDL_hidapi_xboxone.c
+++ b/src/joystick/hidapi/SDL_hidapi_xboxone.c
@@ -124,6 +124,7 @@
 };
 
 typedef struct {
+    SDL_JoystickID joystickID;
     Uint8 sequence;
     Uint8 last_state[USB_PACKET_LENGTH];
     Uint32 rumble_expiration;
@@ -143,7 +144,7 @@
 }
 
 static SDL_bool
-HIDAPI_DriverXboxOne_Init(SDL_Joystick *joystick, hid_device *dev, Uint16 vendor_id, Uint16 product_id, void **context)
+HIDAPI_DriverXboxOne_InitDriver(SDL_HIDAPI_DriverData *context, Uint16 vendor_id, Uint16 product_id, int *num_joysticks)
 {
     SDL_DriverXboxOne_Context *ctx;
     int i;
@@ -154,7 +155,7 @@
         SDL_OutOfMemory();
         return SDL_FALSE;
     }
-    *context = ctx;
+    context->context = ctx;
 
     /* Send the controller init data */
     for (i = 0; i < SDL_arraysize(xboxone_init_packets); ++i) {
@@ -162,7 +163,7 @@
         if (!packet->vendor_id || (vendor_id == packet->vendor_id && product_id == packet->product_id)) {
             SDL_memcpy(init_packet, packet->data, packet->size);
             init_packet[2] = ctx->sequence++;
-            if (hid_write(dev, init_packet, packet->size) != packet->size) {
+            if (hid_write(context->device, init_packet, packet->size) != packet->size) {
                 SDL_SetError("Couldn't write Xbox One initialization packet");
                 SDL_free(ctx);
                 return SDL_FALSE;
@@ -170,6 +171,16 @@
         }
     }
 
+    ctx->joystickID = SDL_GetNextJoystickInstanceID();
+    *num_joysticks += 1;
+    SDL_PrivateJoystickAdded(ctx->joystickID);
+
+    return SDL_TRUE;
+}
+
+static SDL_bool
+HIDAPI_DriverXboxOne_OpenJoystick(SDL_HIDAPI_DriverData *context, SDL_Joystick *joystick)
+{
     /* Initialize the joystick capabilities */
     joystick->nbuttons = SDL_CONTROLLER_BUTTON_MAX;
     joystick->naxes = SDL_CONTROLLER_AXIS_MAX;
@@ -178,10 +189,35 @@
     return SDL_TRUE;
 }
 
-static int
-HIDAPI_DriverXboxOne_Rumble(SDL_Joystick *joystick, hid_device *dev, void *context, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
+static void
+HIDAPI_DriverXboxOne_QuitDriver(SDL_HIDAPI_DriverData *context, SDL_bool send_event, int *num_joysticks)
 {
-    SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)context;
+    SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)context->context;
+
+    *num_joysticks -= 1;
+    if (send_event) {
+        SDL_PrivateJoystickRemoved(ctx->joystickID);
+    }
+    SDL_free(context->context);
+}
+
+static int
+HIDAPI_DriverXboxOne_NumJoysticks(SDL_HIDAPI_DriverData *context)
+{
+    return 1;
+}
+
+static SDL_JoystickID
+HIDAPI_DriverXboxOne_InstanceIDForIndex(SDL_HIDAPI_DriverData *context, int index)
+{
+    SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)context->context;
+    return ctx->joystickID;
+}
+
+static int
+HIDAPI_DriverXboxOne_Rumble(SDL_HIDAPI_DriverData *context, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
+{
+    SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)context->context;
     Uint8 rumble_packet[] = { 0x09, 0x00, 0x00, 0x09, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF };
 
     /* The Rock Candy Xbox One Controller limits the range of
@@ -194,7 +230,7 @@
     rumble_packet[8] = (low_frequency_rumble >> 9);
     rumble_packet[9] = (high_frequency_rumble >> 9);
 
-    if (hid_write(dev, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {
+    if (hid_write(context->device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {
         return SDL_SetError("Couldn't send rumble packet");
     }
 
@@ -267,19 +303,24 @@
 }
 
 static SDL_bool
-HIDAPI_DriverXboxOne_Update(SDL_Joystick *joystick, hid_device *dev, void *context)
+HIDAPI_DriverXboxOne_UpdateDriver(SDL_HIDAPI_DriverData *context, int *num_joysticks)
 {
-    SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)context;
+    SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)context->context;
+    SDL_Joystick *joystick = SDL_JoystickFromInstanceID(ctx->joystickID);
     Uint8 data[USB_PACKET_LENGTH];
     int size;
 
-    while ((size = hid_read_timeout(dev, data, sizeof(data), 0)) > 0) {
+    if (joystick == NULL) {
+        return SDL_TRUE; /* Nothing to do right now! */
+    }
+
+    while ((size = hid_read_timeout(context->device, data, sizeof(data), 0)) > 0) {
         switch (data[0]) {
         case 0x20:
-            HIDAPI_DriverXboxOne_HandleStatePacket(joystick, dev, ctx, data, size);
+            HIDAPI_DriverXboxOne_HandleStatePacket(joystick, context->device, ctx, data, size);
             break;
         case 0x07:
-            HIDAPI_DriverXboxOne_HandleModePacket(joystick, dev, ctx, data, size);
+            HIDAPI_DriverXboxOne_HandleModePacket(joystick, context->device, ctx, data, size);
             break;
         default:
 #ifdef DEBUG_JOYSTICK
@@ -292,29 +333,26 @@
     if (ctx->rumble_expiration) {
         Uint32 now = SDL_GetTicks();
         if (SDL_TICKS_PASSED(now, ctx->rumble_expiration)) {
-            HIDAPI_DriverXboxOne_Rumble(joystick, dev, context, 0, 0, 0);
+            HIDAPI_DriverXboxOne_Rumble(context, joystick, 0, 0, 0);
         }
     }
 
     return (size >= 0);
 }
 
-static void
-HIDAPI_DriverXboxOne_Quit(SDL_Joystick *joystick, hid_device *dev, void *context)
-{
-    SDL_free(context);
-}
-
 SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXboxOne =
 {
     SDL_HINT_JOYSTICK_HIDAPI_XBOX,
     SDL_TRUE,
     HIDAPI_DriverXboxOne_IsSupportedDevice,
     HIDAPI_DriverXboxOne_GetDeviceName,
-    HIDAPI_DriverXboxOne_Init,
-    HIDAPI_DriverXboxOne_Rumble,
-    HIDAPI_DriverXboxOne_Update,
-    HIDAPI_DriverXboxOne_Quit
+    HIDAPI_DriverXboxOne_InitDriver,
+    HIDAPI_DriverXboxOne_QuitDriver,
+    HIDAPI_DriverXboxOne_UpdateDriver,
+    HIDAPI_DriverXboxOne_NumJoysticks,
+    HIDAPI_DriverXboxOne_InstanceIDForIndex,
+    HIDAPI_DriverXboxOne_OpenJoystick,
+    HIDAPI_DriverXboxOne_Rumble
 };
 
 #endif /* SDL_JOYSTICK_HIDAPI_XBOXONE */
diff --git a/src/joystick/hidapi/SDL_hidapijoystick.c b/src/joystick/hidapi/SDL_hidapijoystick.c
index e420354..7cbd9f4 100644
--- a/src/joystick/hidapi/SDL_hidapijoystick.c
+++ b/src/joystick/hidapi/SDL_hidapijoystick.c
@@ -50,18 +50,10 @@
 #endif
 #endif
 
-struct joystick_hwdata
-{
-    SDL_HIDAPI_DeviceDriver *driver;
-    void *context;
-
-    SDL_mutex *mutex;
-    hid_device *dev;
-};
-
 typedef struct _SDL_HIDAPI_Device
 {
-    SDL_JoystickID instance_id;
+    SDL_HIDAPI_DriverData devdata;
+    SDL_mutex *mutex;
     char *name;
     char *path;
     Uint16 vendor_id;
@@ -95,6 +87,9 @@
 #ifdef SDL_JOYSTICK_HIDAPI_XBOXONE
     &SDL_HIDAPI_DriverXboxOne,
 #endif
+#ifdef SDL_JOYSTICK_HIDAPI_GAMECUBE
+    &SDL_HIDAPI_DriverGameCube,
+#endif
 };
 static SDL_HIDAPI_Device *SDL_HIDAPI_devices;
 static int SDL_HIDAPI_numjoysticks = 0;
@@ -393,6 +388,36 @@
 #endif
 }
 
+static void
+HIDAPI_InitDriver(SDL_HIDAPI_Device *device)
+{
+    device->devdata.device = hid_open_path(device->path, 0);
+    if (!device->devdata.device) {
+        SDL_SetError("Couldn't open HID device %s", device->path);
+        device->driver = NULL;
+    } else {
+        device->driver->InitDriver(
+            &device->devdata,
+            device->vendor_id,
+            device->product_id,
+            &SDL_HIDAPI_numjoysticks
+        );
+        device->mutex = SDL_CreateMutex();
+    }
+}
+
+static void
+HIDAPI_QuitDriver(SDL_HIDAPI_Device *device, SDL_bool send_event)
+{
+    device->driver->QuitDriver(
+        &device->devdata,
+        send_event,
+        &SDL_HIDAPI_numjoysticks
+    );
+    hid_close(device->devdata.device);
+    SDL_DestroyMutex(device->mutex);
+    device->driver = NULL;
+}
 
 const char *
 HIDAPI_XboxControllerName(Uint16 vendor_id, Uint16 product_id)
@@ -605,15 +630,17 @@
 }
 
 static SDL_HIDAPI_Device *
-HIDAPI_GetJoystickByIndex(int device_index)
+HIDAPI_GetDeviceByIndex(int device_index)
 {
     SDL_HIDAPI_Device *device = SDL_HIDAPI_devices;
+    int joysticks;
     while (device) {
         if (device->driver) {
-            if (device_index == 0) {
+            joysticks = device->driver->NumJoysticks(&device->devdata);
+            if (device_index < joysticks) {
                 break;
             }
-            --device_index;
+            device_index -= joysticks;
         }
         device = device->next;
     }
@@ -660,20 +687,12 @@
     while (device) {
         if (device->driver) {
             if (!device->driver->enabled) {
-                device->driver = NULL;
-
-                --SDL_HIDAPI_numjoysticks;
-
-                SDL_PrivateJoystickRemoved(device->instance_id);
+                HIDAPI_QuitDriver(device, SDL_TRUE);
             }
         } else {
             device->driver = HIDAPI_GetDeviceDriver(device);
             if (device->driver) {
-                device->instance_id = SDL_GetNextJoystickInstanceID();
-
-                ++SDL_HIDAPI_numjoysticks;
-
-                SDL_PrivateJoystickAdded(device->instance_id);
+                HIDAPI_InitDriver(device);
             }
         }
         device = device->next;
@@ -723,7 +742,6 @@
     if (!device) {
         return;
     }
-    device->instance_id = -1;
     device->seen = SDL_TRUE;
     device->vendor_id = info->vendor_id;
     device->product_id = info->product_id;
@@ -818,12 +836,8 @@
     }
 
     if (device->driver) {
-        /* It's a joystick! */
-        device->instance_id = SDL_GetNextJoystickInstanceID();
-
-        ++SDL_HIDAPI_numjoysticks;
-
-        SDL_PrivateJoystickAdded(device->instance_id);
+        /* It's a joystick device! */
+        HIDAPI_InitDriver(device);
     }
 }
 
@@ -840,11 +854,8 @@
                 SDL_HIDAPI_devices = curr->next;
             }
 
-            if (device->driver && send_event) {
-                /* Need to decrement the joystick count before we post the event */
-                --SDL_HIDAPI_numjoysticks;
-
-                SDL_PrivateJoystickRemoved(device->instance_id);
+            if (device->driver) {
+                HIDAPI_QuitDriver(device, send_event);
             }
 
             SDL_free(device->name);
@@ -931,7 +942,7 @@
 static const char *
 HIDAPI_JoystickGetDeviceName(int device_index)
 {
-    return HIDAPI_GetJoystickByIndex(device_index)->name;
+    return HIDAPI_GetDeviceByIndex(device_index)->name;
 }
 
 static int
@@ -943,89 +954,61 @@
 static SDL_JoystickGUID
 HIDAPI_JoystickGetDeviceGUID(int device_index)
 {
-    return HIDAPI_GetJoystickByIndex(device_index)->guid;
+    return HIDAPI_GetDeviceByIndex(device_index)->guid;
 }
 
 static SDL_JoystickID
 HIDAPI_JoystickGetDeviceInstanceID(int device_index)
 {
-    return HIDAPI_GetJoystickByIndex(device_index)->instance_id;
+    SDL_HIDAPI_Device *device = SDL_HIDAPI_devices;
+    int joysticks;
+    while (device) {
+        if (device->driver) {
+            joysticks = device->driver->NumJoysticks(&device->devdata);
+            if (device_index < joysticks) {
+                break;
+            }
+            device_index -= joysticks;
+        }
+        device = device->next;
+    }
+    return device->driver->InstanceIDForIndex(&device->devdata, device_index);
 }
 
 static int
 HIDAPI_JoystickOpen(SDL_Joystick * joystick, int device_index)
 {
-    SDL_HIDAPI_Device *device = HIDAPI_GetJoystickByIndex(device_index);
-    struct joystick_hwdata *hwdata;
+    SDL_HIDAPI_Device *device = HIDAPI_GetDeviceByIndex(device_index);
 
-    hwdata = (struct joystick_hwdata *)SDL_calloc(1, sizeof(*hwdata));
-    if (!hwdata) {
-        return SDL_OutOfMemory();
-    }
-
-    hwdata->driver = device->driver;
-    hwdata->dev = hid_open_path(device->path, 0);
-    if (!hwdata->dev) {
-        SDL_free(hwdata);
-        return SDL_SetError("Couldn't open HID device %s", device->path);
-    }
-    hwdata->mutex = SDL_CreateMutex();
-
-    if (!device->driver->Init(joystick, hwdata->dev, device->vendor_id, device->product_id, &hwdata->context)) {
-        hid_close(hwdata->dev);
-        SDL_free(hwdata);
+    if (!device->driver->OpenJoystick(&device->devdata, joystick)) {
         return -1;
     }
 
-    joystick->hwdata = hwdata;
+    joystick->hwdata = (struct joystick_hwdata *)device;
     return 0;
 }
 
 static int
 HIDAPI_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms)
 {
-    struct joystick_hwdata *hwdata = joystick->hwdata;
-    SDL_HIDAPI_DeviceDriver *driver = hwdata->driver;
+    SDL_HIDAPI_Device *device = (SDL_HIDAPI_Device *)joystick->hwdata;
     int result;
 
-    SDL_LockMutex(hwdata->mutex);
-    result = driver->Rumble(joystick, hwdata->dev, hwdata->context, low_frequency_rumble, high_frequency_rumble, duration_ms);
-    SDL_UnlockMutex(hwdata->mutex);
+    SDL_LockMutex(device->mutex);
+    result = device->driver->Rumble(&device->devdata, joystick, low_frequency_rumble, high_frequency_rumble, duration_ms);
+    SDL_UnlockMutex(device->mutex);
     return result;
 }
 
 static void
 HIDAPI_JoystickUpdate(SDL_Joystick * joystick)
 {
-    struct joystick_hwdata *hwdata = joystick->hwdata;
-    SDL_HIDAPI_DeviceDriver *driver = hwdata->driver;
-    SDL_bool succeeded;
-
-    SDL_LockMutex(hwdata->mutex);
-    succeeded = driver->Update(joystick, hwdata->dev, hwdata->context);
-    SDL_UnlockMutex(hwdata->mutex);
-    
-    if (!succeeded) {
-        SDL_HIDAPI_Device *device;
-        for (device = SDL_HIDAPI_devices; device; device = device->next) {
-            if (device->instance_id == joystick->instance_id) {
-                HIDAPI_DelDevice(device, SDL_TRUE);
-                break;
-            }
-        }
-    }
+    /* No-op, all updates are done in SDL_HIDAPI_UpdateDevices */
 }
 
 static void
 HIDAPI_JoystickClose(SDL_Joystick * joystick)
 {
-    struct joystick_hwdata *hwdata = joystick->hwdata;
-    SDL_HIDAPI_DeviceDriver *driver = hwdata->driver;
-    driver->Quit(joystick, hwdata->dev, hwdata->context);
-
-    hid_close(hwdata->dev);
-    SDL_DestroyMutex(hwdata->mutex);
-    SDL_free(hwdata);
     joystick->hwdata = NULL;
 }
 
@@ -1050,6 +1033,30 @@
     hid_exit();
 }
 
+void
+SDL_HIDAPI_UpdateDevices(void)
+{
+    SDL_HIDAPI_Device *next, *device = SDL_HIDAPI_devices;
+    SDL_bool succeeded;
+
+    while (device) {
+        if (device->driver) {
+            SDL_LockMutex(device->mutex);
+            succeeded = device->driver->UpdateDriver(&device->devdata, &SDL_HIDAPI_numjoysticks);
+            SDL_UnlockMutex(device->mutex);
+            if (!succeeded) {
+                next = device->next;
+                HIDAPI_DelDevice(device, SDL_TRUE);
+                device = next;
+            } else {
+                device = device->next;
+            }
+        } else {
+            device = device->next;
+        }
+    }
+}
+
 SDL_JoystickDriver SDL_HIDAPI_JoystickDriver =
 {
     HIDAPI_JoystickInit,
diff --git a/src/joystick/hidapi/SDL_hidapijoystick_c.h b/src/joystick/hidapi/SDL_hidapijoystick_c.h
index a8e7073..807a301 100644
--- a/src/joystick/hidapi/SDL_hidapijoystick_c.h
+++ b/src/joystick/hidapi/SDL_hidapijoystick_c.h
@@ -30,6 +30,7 @@
 #define SDL_JOYSTICK_HIDAPI_SWITCH
 #define SDL_JOYSTICK_HIDAPI_XBOX360
 #define SDL_JOYSTICK_HIDAPI_XBOXONE
+#define SDL_JOYSTICK_HIDAPI_GAMECUBE
 
 #ifdef __WINDOWS__
 /* On Windows, Xbox One controllers are handled by the Xbox 360 driver */
@@ -43,16 +44,36 @@
 #undef SDL_JOYSTICK_HIDAPI_XBOXONE
 #endif
 
+typedef struct _SDL_HIDAPI_DriverData
+{
+    hid_device *device;
+    void *context;
+} SDL_HIDAPI_DriverData;
+
 typedef struct _SDL_HIDAPI_DeviceDriver
 {
     const char *hint;
     SDL_bool enabled;
     SDL_bool (*IsSupportedDevice)(Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number);
     const char *(*GetDeviceName)(Uint16 vendor_id, Uint16 product_id);
-    SDL_bool (*Init)(SDL_Joystick *joystick, hid_device *dev, Uint16 vendor_id, Uint16 product_id, void **context);
-    int (*Rumble)(SDL_Joystick *joystick, hid_device *dev, void *context, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms);
-    SDL_bool (*Update)(SDL_Joystick *joystick, hid_device *dev, void *context);
-    void (*Quit)(SDL_Joystick *joystick, hid_device *dev, void *context);
+
+    SDL_bool       (*InitDriver)(SDL_HIDAPI_DriverData *context,
+                                 Uint16 vendor_id, Uint16 product_id, int *num_joysticks);
+    void           (*QuitDriver)(SDL_HIDAPI_DriverData *context,
+                                 SDL_bool send_event,
+                                 int *num_joysticks);
+    SDL_bool       (*UpdateDriver)(SDL_HIDAPI_DriverData *context,
+                                   int *num_joysticks);
+    int            (*NumJoysticks)(SDL_HIDAPI_DriverData *context);
+    SDL_JoystickID (*InstanceIDForIndex)(SDL_HIDAPI_DriverData *context,
+                                         int index);
+    SDL_bool       (*OpenJoystick)(SDL_HIDAPI_DriverData *context,
+                                   SDL_Joystick *joystick);
+    int            (*Rumble)(SDL_HIDAPI_DriverData *context,
+                             SDL_Joystick *joystick,
+                             Uint16 low_frequency_rumble,
+                             Uint16 high_frequency_rumble,
+                             Uint32 duration_ms);
 
 } SDL_HIDAPI_DeviceDriver;
 
@@ -62,6 +83,7 @@
 extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSwitch;
 extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360;
 extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXboxOne;
+extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGameCube;
 
 /* Return true if a HID device is present and supported as a joystick */
 extern SDL_bool HIDAPI_IsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version);