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.
This commit is contained in:
schuay
2026-03-22 11:07:46 +01:00
committed by Jakob Linke
parent 69c22a744e
commit 6320ff6d1b
3 changed files with 12 additions and 20 deletions

View File

@@ -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;
}

View File

@@ -331,28 +331,18 @@ namespace Ryujinx.Input.SDL3
public IEnumerable<IGamepad> 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);
}
}
}

View File

@@ -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}, " +