From 6320ff6d1b791e9eeda4eabae62574bf0f701474 Mon Sep 17 00:00:00 2001 From: schuay <36006+schuay@users.noreply.github.com> Date: Sun, 22 Mar 2026 11:07:46 +0100 Subject: [PATCH] Fix SDL3 gamepad crash, broken motion, and thread safety issues - Remove SDL_DestroyProperties call on SDL-owned properties in SDL3Gamepad.GetFeaturesFlag(). SDL_GetGamepadProperties returns properties owned internally by SDL, freed when SDL_CloseGamepad is called. Destroying them causes use-after-free, breaking motion sensor data retrieval and crashing on controller reconnect. - Fix GetGamepads() using wrong lock objects (_gamepadsIds, _joyConsIds, _linkedJoyConsIds instead of _lock) and holding locks across yield boundaries, which is incompatible with System.Threading.Lock. Snapshot IDs under _lock and iterate outside it. - Add null-conditional to _gamepad.Rumble() in NpadController.UpdateRumble() to prevent NullReferenceException if gamepad is disconnected mid-update. --- src/Ryujinx.Input.SDL3/SDL3Gamepad.cs | 4 +++- src/Ryujinx.Input.SDL3/SDL3GamepadDriver.cs | 26 +++++++-------------- src/Ryujinx.Input/HLE/NpadController.cs | 2 +- 3 files changed, 12 insertions(+), 20 deletions(-) diff --git a/src/Ryujinx.Input.SDL3/SDL3Gamepad.cs b/src/Ryujinx.Input.SDL3/SDL3Gamepad.cs index 2b006147d..4985d8eea 100644 --- a/src/Ryujinx.Input.SDL3/SDL3Gamepad.cs +++ b/src/Ryujinx.Input.SDL3/SDL3Gamepad.cs @@ -151,7 +151,9 @@ namespace Ryujinx.Input.SDL3 result |= GamepadFeaturesFlag.Led; } SDL_UnlockProperties(propID); - SDL_DestroyProperties(propID); + + // NOTE: Do not call SDL_DestroyProperties here. These properties are owned + // internally by SDL and are freed when SDL_CloseGamepad is called (in Dispose). return result; } diff --git a/src/Ryujinx.Input.SDL3/SDL3GamepadDriver.cs b/src/Ryujinx.Input.SDL3/SDL3GamepadDriver.cs index 897966689..0e2a63579 100644 --- a/src/Ryujinx.Input.SDL3/SDL3GamepadDriver.cs +++ b/src/Ryujinx.Input.SDL3/SDL3GamepadDriver.cs @@ -331,28 +331,18 @@ namespace Ryujinx.Input.SDL3 public IEnumerable GetGamepads() { - lock (_gamepadsIds) + string[] ids; + lock (_lock) { - foreach (var gamepad in _gamepadsIds) - { - yield return GetGamepad(gamepad.Value); - } + ids = _gamepadsIds.Values + .Concat(_joyConsIds.Values) + .Concat(_linkedJoyConsIds.Values) + .ToArray(); } - lock (_joyConsIds) + foreach (string id in ids) { - foreach (var gamepad in _joyConsIds) - { - yield return GetGamepad(gamepad.Value); - } - } - - lock (_linkedJoyConsIds) - { - foreach (var gamepad in _linkedJoyConsIds) - { - yield return GetGamepad(gamepad.Value); - } + yield return GetGamepad(id); } } } diff --git a/src/Ryujinx.Input/HLE/NpadController.cs b/src/Ryujinx.Input/HLE/NpadController.cs index 84f9e89ab..29bc973f6 100644 --- a/src/Ryujinx.Input/HLE/NpadController.cs +++ b/src/Ryujinx.Input/HLE/NpadController.cs @@ -563,7 +563,7 @@ namespace Ryujinx.Input.HLE float low = Math.Min(1f, (float)((rightVibrationValue.AmplitudeLow * 0.85 + rightVibrationValue.AmplitudeHigh * 0.15) * controllerConfig.Rumble.StrongRumble)); float high = Math.Min(1f, (float)((leftVibrationValue.AmplitudeLow * 0.15 + leftVibrationValue.AmplitudeHigh * 0.85) * controllerConfig.Rumble.WeakRumble)); - _gamepad.Rumble(low, high, uint.MaxValue); + _gamepad?.Rumble(low, high, uint.MaxValue); Logger.Debug?.Print(LogClass.Hid, $"Effect for {controllerConfig.PlayerIndex} " + $"L.low.amp={leftVibrationValue.AmplitudeLow}, " +