SDL: GameCubeAdapter: Add suppport for all rumble modes

From 94d43186f2b366dd83598d863a6af6fdb68e14ee Mon Sep 17 00:00:00 2001
From: Phillip Stephens <[EMAIL REDACTED]>
Date: Tue, 22 Feb 2022 00:41:15 -0800
Subject: [PATCH] GameCubeAdapter: Add suppport for all rumble modes This adds
 support for all 3 of the gamecube controller's rumble modes Rumble: 1 Stop: 0
 StopHard: 2 This is useful for applications that need the full range of
 support This also adds a hint to control rumble behavior, defaults 0 to
 maintain compatibility

---
 include/SDL_hints.h                       | 15 ++++++++++++
 src/joystick/hidapi/SDL_hidapi_gamecube.c | 28 +++++++++++++++++++++--
 2 files changed, 41 insertions(+), 2 deletions(-)

diff --git a/include/SDL_hints.h b/include/SDL_hints.h
index fe3e2790d96..92fbb5778b2 100644
--- a/include/SDL_hints.h
+++ b/include/SDL_hints.h
@@ -625,6 +625,21 @@ extern "C" {
  */
 #define SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE "SDL_JOYSTICK_HIDAPI_GAMECUBE"
 
+/**
+ *  \brief  A variable controlling whether "low_frequency_rumble" and "high_frequency_rumble" is used to implement
+ *          the GameCube controller's 3 rumble modes, Stop(0), Rumble(1), and StopHard(2)
+ *          this is useful for applications that need full compatibility for things like ADSR envelopes.
+ *          Stop is implemented by setting "low_frequency_rumble" to "0" and "high_frequency_rumble" ">0"
+ *          Rumble is both at any arbitrary value,
+ *          StopHard is implemented by setting both "low_frequency_rumble" and "high_frequency_rumble" to "0"
+ *
+ *  This variable can be set to the following values:
+ *    "0"       - Normal rumble behavior is behavior is used (default)
+ *    "1"       - Proper GameCube controller rumble behavior is used
+ *
+ */
+#define SDL_HINT_JOYSTICK_GAMECUBE_RUMBLE_BRAKE "SDL_JOYSTICK_GAMECUBE_RUMBLE_BRAKE"
+
  /**
   *  \brief  A variable controlling whether Switch Joy-Cons should be treated the same as Switch Pro Controllers when using the HIDAPI driver.
   *
diff --git a/src/joystick/hidapi/SDL_hidapi_gamecube.c b/src/joystick/hidapi/SDL_hidapi_gamecube.c
index ec1b029d5aa..694556482e8 100644
--- a/src/joystick/hidapi/SDL_hidapi_gamecube.c
+++ b/src/joystick/hidapi/SDL_hidapi_gamecube.c
@@ -53,6 +53,7 @@ typedef struct {
     /* Without this variable, hid_write starts to lag a TON */
     SDL_bool rumbleUpdate;
     SDL_bool m_bUseButtonLabels;
+    SDL_bool useRumbleBrake;
 } SDL_DriverGameCube_Context;
 
 static SDL_bool
@@ -92,6 +93,14 @@ static void SDLCALL SDL_GameControllerButtonReportingHintChanged(void *userdata,
     ctx->m_bUseButtonLabels = SDL_GetStringBoolean(hint, SDL_TRUE);
 }
 
+static void SDLCALL SDL_JoystickGameCubeRumbleBrakeHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
+{
+    if (hint) {
+        SDL_DriverGameCube_Context *ctx = (SDL_DriverGameCube_Context *)userdata;
+        ctx->useRumbleBrake = SDL_GetStringBoolean(hint, SDL_FALSE);
+    }
+}
+
 static Uint8 RemapButton(SDL_DriverGameCube_Context *ctx, Uint8 button)
 {
     if (!ctx->m_bUseButtonLabels) {
@@ -142,6 +151,7 @@ HIDAPI_DriverGameCube_InitDevice(SDL_HIDAPI_Device *device)
     ctx->joysticks[2] = -1;
     ctx->joysticks[3] = -1;
     ctx->rumble[0] = rumbleMagic;
+    ctx->useRumbleBrake = SDL_FALSE;
 
     if (device->vendor_id != USB_VENDOR_NINTENDO) {
         ctx->pc_mode = SDL_TRUE;
@@ -195,6 +205,8 @@ HIDAPI_DriverGameCube_InitDevice(SDL_HIDAPI_Device *device)
         }
     }
 
+    SDL_AddHintCallback(SDL_HINT_JOYSTICK_GAMECUBE_RUMBLE_BRAKE,
+                        SDL_JoystickGameCubeRumbleBrakeHintChanged, ctx);
     SDL_AddHintCallback(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS,
                         SDL_GameControllerButtonReportingHintChanged, ctx);
 
@@ -439,12 +451,22 @@ HIDAPI_DriverGameCube_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *jo
     for (i = 0; i < MAX_CONTROLLERS; i += 1) {
         if (joystick->instance_id == ctx->joysticks[i]) {
             if (ctx->wireless[i]) {
-                return SDL_SetError("Ninteno GameCube WaveBird controllers do not support rumble");
+                return SDL_SetError("Nintendo GameCube WaveBird controllers do not support rumble");
             }
             if (!ctx->rumbleAllowed[i]) {
                 return SDL_SetError("Second USB cable for WUP-028 not connected");
             }
-            val = (low_frequency_rumble > 0 || high_frequency_rumble > 0);
+            if (ctx->useRumbleBrake) {
+                if (low_frequency_rumble == 0 && high_frequency_rumble > 0) {
+                    val = 0; /* if only low is 0 we want to do a regular stop*/
+                } else if (low_frequency_rumble == 0 && high_frequency_rumble == 0) {
+                    val = 2; /* if both frequencies are 0 we want to do a hard stop */
+                } else {
+                    val = 1; /* normal rumble */
+                }
+            } else {
+                val = (low_frequency_rumble > 0 || high_frequency_rumble > 0);
+            }
             if (val != ctx->rumble[i + 1]) {
                 ctx->rumble[i + 1] = val;
                 ctx->rumbleUpdate = SDL_TRUE;
@@ -522,6 +544,8 @@ HIDAPI_DriverGameCube_FreeDevice(SDL_HIDAPI_Device *device)
 
     SDL_DelHintCallback(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS,
                         SDL_GameControllerButtonReportingHintChanged, ctx);
+    SDL_DelHintCallback(SDL_HINT_JOYSTICK_GAMECUBE_RUMBLE_BRAKE,
+                        SDL_JoystickGameCubeRumbleBrakeHintChanged, ctx);
 
     SDL_LockMutex(device->dev_lock);
     {