SDL: Support calling SteamAPI_InitEx() before SDL_Init(SDL_INIT_GAMEPAD)

From 517a3d20e8cf52cbd7bc3b9ae48349017e87c095 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sat, 13 Dec 2025 23:42:55 -0800
Subject: [PATCH] Support calling SteamAPI_InitEx() before
 SDL_Init(SDL_INIT_GAMEPAD)

Calling SteamAPI_InitEx() will set environment variables that SDL uses to properly support the Steam virtual gamepad. Make sure that we fall back to the real environment for the variables that Steam sets.
---
 include/SDL3/SDL_gamepad.h               |  3 +++
 src/joystick/SDL_gamepad.c               | 14 ++++++++++----
 src/joystick/SDL_joystick.c              |  6 ++++++
 src/joystick/SDL_steam_virtual_gamepad.c |  6 ++----
 4 files changed, 21 insertions(+), 8 deletions(-)

diff --git a/include/SDL3/SDL_gamepad.h b/include/SDL3/SDL_gamepad.h
index d1a21ca252a3f..4d660ed416797 100644
--- a/include/SDL3/SDL_gamepad.h
+++ b/include/SDL3/SDL_gamepad.h
@@ -48,6 +48,9 @@
  * SDL_INIT_GAMEPAD flag. This causes SDL to scan the system for gamepads, and
  * load appropriate drivers.
  *
+ * If you're using SDL gamepad support in a Steam game, you must call
+ * SteamAPI_InitEx() before calling SDL_Init().
+ *
  * If you would like to receive gamepad updates while the application is in
  * the background, you should set the following hint before calling
  * SDL_Init(): SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS
diff --git a/src/joystick/SDL_gamepad.c b/src/joystick/SDL_gamepad.c
index a291ec2694e7c..7ac5862a3fcfc 100644
--- a/src/joystick/SDL_gamepad.c
+++ b/src/joystick/SDL_gamepad.c
@@ -33,6 +33,7 @@
 #include "hidapi/SDL_hidapi_nintendo.h"
 #include "hidapi/SDL_hidapi_sinput.h"
 #include "../events/SDL_events_c.h"
+#include "../SDL_hints_c.h"
 
 #ifdef SDL_PLATFORM_WIN32
 #include "../core/windows/SDL_windows.h"
@@ -2616,7 +2617,11 @@ static int SDL_PrivateAddGamepadMapping(const char *mappingString, SDL_GamepadMa
                 }
 
             } else {
-                value = SDL_GetHintBoolean(hint, default_value);
+                const char *hint_value = SDL_GetHint(hint);
+                if (!hint_value) {
+                    hint_value = SDL_getenv_unsafe(hint_value);
+                }
+                value = SDL_GetStringBoolean(hint_value, default_value);
                 if (negate) {
                     value = !value;
                 }
@@ -3235,9 +3240,10 @@ bool SDL_ShouldIgnoreGamepad(Uint16 vendor_id, Uint16 product_id, Uint16 version
         }
     }
 
+    const char *hint = SDL_getenv_unsafe("SDL_GAMECONTROLLER_ALLOW_STEAM_VIRTUAL_GAMEPAD");
+    bool allow_steam_virtual_gamepad = SDL_GetStringBoolean(hint, false);
 #ifdef SDL_PLATFORM_WIN32
-    if (SDL_GetHintBoolean("SDL_GAMECONTROLLER_ALLOW_STEAM_VIRTUAL_GAMEPAD", false) &&
-        WIN_IsWine()) {
+    if (allow_steam_virtual_gamepad && WIN_IsWine()) {
         // We are launched by Steam and running under Proton or Wine
         // We can't tell whether this controller is a Steam Virtual Gamepad,
         // so assume that is doing the appropriate filtering of controllers
@@ -3247,7 +3253,7 @@ bool SDL_ShouldIgnoreGamepad(Uint16 vendor_id, Uint16 product_id, Uint16 version
 #endif // SDL_PLATFORM_WIN32
 
     if (SDL_IsJoystickSteamVirtualGamepad(vendor_id, product_id, version)) {
-        return !SDL_GetHintBoolean("SDL_GAMECONTROLLER_ALLOW_STEAM_VIRTUAL_GAMEPAD", false);
+        return !allow_steam_virtual_gamepad;
     }
 
     if (SDL_allowed_gamepads.num_included_entries > 0) {
diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c
index 3e3c66b221e0b..f095629d8222b 100644
--- a/src/joystick/SDL_joystick.c
+++ b/src/joystick/SDL_joystick.c
@@ -3961,9 +3961,15 @@ void SDL_LoadVIDPIDList(SDL_vidpid_list *list)
 
     if (list->included_hint_name) {
         included_list = SDL_GetHint(list->included_hint_name);
+        if (!included_list) {
+            included_list = SDL_getenv_unsafe(list->included_hint_name);
+        }
     }
     if (list->excluded_hint_name) {
         excluded_list = SDL_GetHint(list->excluded_hint_name);
+        if (!excluded_list) {
+            excluded_list = SDL_getenv_unsafe(list->excluded_hint_name);
+        }
     }
     SDL_LoadVIDPIDListFromHints(list, included_list, excluded_list);
 }
diff --git a/src/joystick/SDL_steam_virtual_gamepad.c b/src/joystick/SDL_steam_virtual_gamepad.c
index e44610664002c..c59dff7aa19ea 100644
--- a/src/joystick/SDL_steam_virtual_gamepad.c
+++ b/src/joystick/SDL_steam_virtual_gamepad.c
@@ -33,8 +33,6 @@
 #include <sys/stat.h>
 #endif
 
-#define SDL_HINT_STEAM_VIRTUAL_GAMEPAD_INFO_FILE    "SteamVirtualGamepadInfo"
-
 static char *SDL_steam_virtual_gamepad_info_file SDL_GUARDED_BY(SDL_joystick_lock) = NULL;
 static Uint64 SDL_steam_virtual_gamepad_info_file_mtime SDL_GUARDED_BY(SDL_joystick_lock) = 0;
 static Uint64 SDL_steam_virtual_gamepad_info_check_time SDL_GUARDED_BY(SDL_joystick_lock) = 0;
@@ -135,14 +133,14 @@ void SDL_InitSteamVirtualGamepadInfo(void)
         return;
     }
 
-    file = SDL_GetHint(SDL_HINT_STEAM_VIRTUAL_GAMEPAD_INFO_FILE);
+    file = SDL_getenv_unsafe("SteamVirtualGamepadInfo");
     if (file && *file) {
 #ifdef SDL_PLATFORM_LINUX
         // Older versions of Wine will blacklist the Steam Virtual Gamepad if
         // it appears to have the real controller's VID/PID, so ignore this.
         const char *exe = SDL_GetExeName();
         if (exe && SDL_strcmp(exe, "wine64-preloader") == 0) {
-            SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "Wine launched by Steam, ignoring %s", SDL_HINT_STEAM_VIRTUAL_GAMEPAD_INFO_FILE);
+            SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "Wine launched by Steam, ignoring SteamVirtualGamepadInfo");
             return;
         }
 #endif