diff --git a/Ryujinx.Common/Configuration/Hid/Controller/GenericControllerInputConfig.cs b/Ryujinx.Common/Configuration/Hid/Controller/GenericControllerInputConfig.cs
index e3423bb5ec..6c4562cfb2 100644
--- a/Ryujinx.Common/Configuration/Hid/Controller/GenericControllerInputConfig.cs
+++ b/Ryujinx.Common/Configuration/Hid/Controller/GenericControllerInputConfig.cs
@@ -33,5 +33,10 @@ namespace Ryujinx.Common.Configuration.Hid.Controller
/// Controller Motion Settings
///
public MotionConfigController Motion { get; set; }
+
+ ///
+ /// Controller Rumble Settings
+ ///
+ public RumbleConfigController Rumble { get; set; }
}
}
diff --git a/Ryujinx.Common/Configuration/Hid/Controller/RumbleConfigController.cs b/Ryujinx.Common/Configuration/Hid/Controller/RumbleConfigController.cs
new file mode 100644
index 0000000000..48be4f13e2
--- /dev/null
+++ b/Ryujinx.Common/Configuration/Hid/Controller/RumbleConfigController.cs
@@ -0,0 +1,20 @@
+namespace Ryujinx.Common.Configuration.Hid.Controller
+{
+ public class RumbleConfigController
+ {
+ ///
+ /// Controller Strong Rumble Multiplier
+ ///
+ public float StrongRumble { get; set; }
+
+ ///
+ /// Controller Weak Rumble Multiplier
+ ///
+ public float WeakRumble { get; set; }
+
+ ///
+ /// Enable Rumble
+ ///
+ public bool EnableRumble { get; set; }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs b/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs
index bbc30172c0..55f8070a77 100644
--- a/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs
+++ b/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs
@@ -1,7 +1,9 @@
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Ryujinx.Common;
+using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Hid.Types;
@@ -20,11 +22,21 @@ namespace Ryujinx.HLE.HOS.Services.Hid
private ControllerType[] _configuredTypes;
private KEvent[] _styleSetUpdateEvents;
private bool[] _supportedPlayers;
+ private static HidVibrationValue _neutralVibrationValue = new HidVibrationValue
+ {
+ AmplitudeLow = 0f,
+ FrequencyLow = 160f,
+ AmplitudeHigh = 0f,
+ FrequencyHigh = 320f
+ };
internal NpadJoyHoldType JoyHold { get; set; }
internal bool SixAxisActive = false; // TODO: link to hidserver when implemented
internal ControllerType SupportedStyleSets { get; set; }
+ public Dictionary> RumbleQueues = new Dictionary>();
+ public Dictionary LastVibrationValues = new Dictionary();
+
public NpadDevices(Switch device, bool active = true) : base(device, active)
{
_configuredTypes = new ControllerType[MaxControllers];
@@ -596,5 +608,49 @@ namespace Ryujinx.HLE.HOS.Services.Hid
WriteNewSixInputEntry(ref currentNpad.JoyLeftSixAxisSensor, ref newState);
WriteNewSixInputEntry(ref currentNpad.JoyRightSixAxisSensor, ref newState);
}
+
+ public void UpdateRumbleQueue(PlayerIndex index, Dictionary dualVibrationValues)
+ {
+ if (RumbleQueues.TryGetValue(index, out ConcurrentQueue<(HidVibrationValue, HidVibrationValue)> currentQueue))
+ {
+ if (!dualVibrationValues.TryGetValue(0, out HidVibrationValue leftVibrationValue))
+ {
+ leftVibrationValue = _neutralVibrationValue;
+ }
+
+ if (!dualVibrationValues.TryGetValue(1, out HidVibrationValue rightVibrationValue))
+ {
+ rightVibrationValue = _neutralVibrationValue;
+ }
+
+ if (!LastVibrationValues.TryGetValue(index, out (HidVibrationValue, HidVibrationValue) dualVibrationValue) || !leftVibrationValue.Equals(dualVibrationValue.Item1) || !rightVibrationValue.Equals(dualVibrationValue.Item2))
+ {
+ currentQueue.Enqueue((leftVibrationValue, rightVibrationValue));
+
+ LastVibrationValues[index] = (leftVibrationValue, rightVibrationValue);
+ }
+ }
+ }
+
+ public HidVibrationValue GetLastVibrationValue(PlayerIndex index, byte position)
+ {
+ if (!LastVibrationValues.TryGetValue(index, out (HidVibrationValue, HidVibrationValue) dualVibrationValue))
+ {
+ return _neutralVibrationValue;
+ }
+
+ return (position == 0) ? dualVibrationValue.Item1 : dualVibrationValue.Item2;
+ }
+
+ public ConcurrentQueue<(HidVibrationValue, HidVibrationValue)> GetRumbleQueue(PlayerIndex index)
+ {
+ if (!RumbleQueues.TryGetValue(index, out ConcurrentQueue<(HidVibrationValue, HidVibrationValue)> rumbleQueue))
+ {
+ rumbleQueue = new ConcurrentQueue<(HidVibrationValue, HidVibrationValue)>();
+ _device.Hid.Npads.RumbleQueues[index] = rumbleQueue;
+ }
+
+ return rumbleQueue;
+ }
}
}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationDeviceHandle.cs b/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationDeviceHandle.cs
new file mode 100644
index 0000000000..4501c721a4
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationDeviceHandle.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ public struct HidVibrationDeviceHandle
+ {
+ public byte DeviceType;
+ public byte PlayerId;
+ public byte Position;
+ public byte Reserved;
+ }
+}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationDeviceType.cs b/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationDeviceType.cs
index cf9e64985c..898384be73 100644
--- a/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationDeviceType.cs
+++ b/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationDeviceType.cs
@@ -3,6 +3,7 @@
public enum HidVibrationDeviceType
{
None,
- LinearResonantActuator
+ LinearResonantActuator,
+ GcErm
}
}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationValue.cs b/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationValue.cs
index 7211396e42..3f45d26991 100644
--- a/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationValue.cs
+++ b/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/HidVibrationValue.cs
@@ -1,4 +1,7 @@
-namespace Ryujinx.HLE.HOS.Services.Hid
+using Ryujinx.HLE.HOS.Tamper;
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Hid
{
public struct HidVibrationValue
{
@@ -6,5 +9,17 @@
public float FrequencyLow;
public float AmplitudeHigh;
public float FrequencyHigh;
+
+ public override bool Equals(object obj)
+ {
+ return obj is HidVibrationValue value &&
+ AmplitudeLow == value.AmplitudeLow &&
+ AmplitudeHigh == value.AmplitudeHigh;
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(AmplitudeLow, AmplitudeHigh);
+ }
}
}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs b/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs
index 3f2aae356f..140545ad92 100644
--- a/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs
+++ b/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs
@@ -1,10 +1,13 @@
+using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Hid.HidServer;
using Ryujinx.HLE.HOS.Services.Hid.Types;
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad;
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.InteropServices;
@@ -37,7 +40,6 @@ namespace Ryujinx.HLE.HOS.Services.Hid
private HidSensorFusionParameters _sensorFusionParams;
private HidAccelerometerParameters _accelerometerParams;
- private HidVibrationValue _vibrationValue;
public IHidServer(ServiceCtx context) : base(context.Device.System.HidServer)
{
@@ -52,7 +54,6 @@ namespace Ryujinx.HLE.HOS.Services.Hid
_sensorFusionParams = new HidSensorFusionParameters();
_accelerometerParams = new HidAccelerometerParameters();
- _vibrationValue = new HidVibrationValue();
// TODO: signal event at right place
_xpadIdEvent.ReadableEvent.Signal();
@@ -1025,29 +1026,78 @@ namespace Ryujinx.HLE.HOS.Services.Hid
// GetVibrationDeviceInfo(nn::hid::VibrationDeviceHandle) -> nn::hid::VibrationDeviceInfo
public ResultCode GetVibrationDeviceInfo(ServiceCtx context)
{
- int vibrationDeviceHandle = context.RequestData.ReadInt32();
+ HidVibrationDeviceHandle deviceHandle = context.RequestData.ReadStruct();
+ NpadStyleIndex deviceType = (NpadStyleIndex)deviceHandle.DeviceType;
+ NpadIdType npadIdType = (NpadIdType)deviceHandle.PlayerId;
- HidVibrationDeviceValue deviceInfo = new HidVibrationDeviceValue
+ if (deviceType < NpadStyleIndex.System || deviceType >= NpadStyleIndex.FullKey)
{
- DeviceType = HidVibrationDeviceType.None,
- Position = HidVibrationDevicePosition.None
- };
+ if (npadIdType >= (NpadIdType.Player8 + 1) && npadIdType != NpadIdType.Handheld && npadIdType != NpadIdType.Unknown)
+ {
+ return ResultCode.InvalidNpadIdType;
+ }
- context.ResponseData.Write((int)deviceInfo.DeviceType);
- context.ResponseData.Write((int)deviceInfo.Position);
+ if (deviceHandle.Position > 1)
+ {
+ return ResultCode.InvalidDeviceIndex;
+ }
- Logger.Stub?.PrintStub(LogClass.ServiceHid, new { vibrationDeviceHandle, deviceInfo.DeviceType, deviceInfo.Position });
+ HidVibrationDeviceType vibrationDeviceType = HidVibrationDeviceType.None;
- return ResultCode.Success;
+ if (Enum.IsDefined(typeof(NpadStyleIndex), deviceType))
+ {
+ vibrationDeviceType = HidVibrationDeviceType.LinearResonantActuator;
+ }
+ else if ((uint)deviceType == 8)
+ {
+ vibrationDeviceType = HidVibrationDeviceType.GcErm;
+ }
+
+ HidVibrationDevicePosition vibrationDevicePosition = HidVibrationDevicePosition.None;
+
+ if (vibrationDeviceType == HidVibrationDeviceType.LinearResonantActuator)
+ {
+ if (deviceHandle.Position == 0)
+ {
+ vibrationDevicePosition = HidVibrationDevicePosition.Left;
+ }
+ else if (deviceHandle.Position == 1)
+ {
+ vibrationDevicePosition = HidVibrationDevicePosition.Right;
+ }
+ else
+ {
+ throw new ArgumentOutOfRangeException(nameof(deviceHandle.Position));
+ }
+ }
+
+ HidVibrationDeviceValue deviceInfo = new HidVibrationDeviceValue
+ {
+ DeviceType = vibrationDeviceType,
+ Position = vibrationDevicePosition
+ };
+
+ context.ResponseData.WriteStruct(deviceInfo);
+
+ return ResultCode.Success;
+ }
+
+ return ResultCode.InvalidNpadDeviceType;
}
[CommandHipc(201)]
// SendVibrationValue(nn::hid::VibrationDeviceHandle, nn::hid::VibrationValue, nn::applet::AppletResourceUserId)
public ResultCode SendVibrationValue(ServiceCtx context)
{
- int vibrationDeviceHandle = context.RequestData.ReadInt32();
+ HidVibrationDeviceHandle deviceHandle = new HidVibrationDeviceHandle
+ {
+ DeviceType = context.RequestData.ReadByte(),
+ PlayerId = context.RequestData.ReadByte(),
+ Position = context.RequestData.ReadByte(),
+ Reserved = context.RequestData.ReadByte()
+ };
- _vibrationValue = new HidVibrationValue
+ HidVibrationValue vibrationValue = new HidVibrationValue
{
AmplitudeLow = context.RequestData.ReadSingle(),
FrequencyLow = context.RequestData.ReadSingle(),
@@ -1057,14 +1107,11 @@ namespace Ryujinx.HLE.HOS.Services.Hid
long appletResourceUserId = context.RequestData.ReadInt64();
- Logger.Debug?.PrintStub(LogClass.ServiceHid, new {
- appletResourceUserId,
- vibrationDeviceHandle,
- _vibrationValue.AmplitudeLow,
- _vibrationValue.FrequencyLow,
- _vibrationValue.AmplitudeHigh,
- _vibrationValue.FrequencyHigh
- });
+ Dictionary dualVibrationValues = new Dictionary();
+
+ dualVibrationValues[deviceHandle.Position] = vibrationValue;
+
+ context.Device.Hid.Npads.UpdateRumbleQueue((PlayerIndex)deviceHandle.PlayerId, dualVibrationValues);
return ResultCode.Success;
}
@@ -1073,22 +1120,22 @@ namespace Ryujinx.HLE.HOS.Services.Hid
// GetActualVibrationValue(nn::hid::VibrationDeviceHandle, nn::applet::AppletResourceUserId) -> nn::hid::VibrationValue
public ResultCode GetActualVibrationValue(ServiceCtx context)
{
- int vibrationDeviceHandle = context.RequestData.ReadInt32();
+ HidVibrationDeviceHandle deviceHandle = new HidVibrationDeviceHandle
+ {
+ DeviceType = context.RequestData.ReadByte(),
+ PlayerId = context.RequestData.ReadByte(),
+ Position = context.RequestData.ReadByte(),
+ Reserved = context.RequestData.ReadByte()
+ };
+
long appletResourceUserId = context.RequestData.ReadInt64();
- context.ResponseData.Write(_vibrationValue.AmplitudeLow);
- context.ResponseData.Write(_vibrationValue.FrequencyLow);
- context.ResponseData.Write(_vibrationValue.AmplitudeHigh);
- context.ResponseData.Write(_vibrationValue.FrequencyHigh);
+ HidVibrationValue vibrationValue = context.Device.Hid.Npads.GetLastVibrationValue((PlayerIndex)deviceHandle.PlayerId, deviceHandle.Position);
- Logger.Stub?.PrintStub(LogClass.ServiceHid, new {
- appletResourceUserId,
- vibrationDeviceHandle,
- _vibrationValue.AmplitudeLow,
- _vibrationValue.FrequencyLow,
- _vibrationValue.AmplitudeHigh,
- _vibrationValue.FrequencyHigh
- });
+ context.ResponseData.Write(vibrationValue.AmplitudeLow);
+ context.ResponseData.Write(vibrationValue.FrequencyLow);
+ context.ResponseData.Write(vibrationValue.AmplitudeHigh);
+ context.ResponseData.Write(vibrationValue.FrequencyHigh);
return ResultCode.Success;
}
@@ -1138,13 +1185,31 @@ namespace Ryujinx.HLE.HOS.Services.Hid
context.Memory.Read(context.Request.PtrBuff[1].Position, vibrationValueBuffer);
- // TODO: Read all handles and values from buffer.
+ Span deviceHandles = MemoryMarshal.Cast(vibrationDeviceHandleBuffer);
+ Span vibrationValues = MemoryMarshal.Cast(vibrationValueBuffer);
- Logger.Debug?.PrintStub(LogClass.ServiceHid, new {
- appletResourceUserId,
- VibrationDeviceHandleBufferLength = vibrationDeviceHandleBuffer.Length,
- VibrationValueBufferLength = vibrationValueBuffer.Length
- });
+ if (!deviceHandles.IsEmpty && vibrationValues.Length == deviceHandles.Length)
+ {
+ Dictionary dualVibrationValues = new Dictionary();
+ PlayerIndex currentIndex = (PlayerIndex)deviceHandles[0].PlayerId;
+
+ for (int deviceCounter = 0; deviceCounter < deviceHandles.Length; deviceCounter++)
+ {
+ PlayerIndex index = (PlayerIndex)deviceHandles[deviceCounter].PlayerId;
+ byte position = deviceHandles[deviceCounter].Position;
+
+ if (index != currentIndex || dualVibrationValues.Count == 2)
+ {
+ context.Device.Hid.Npads.UpdateRumbleQueue(currentIndex, dualVibrationValues);
+ dualVibrationValues = new Dictionary();
+ }
+
+ dualVibrationValues[position] = vibrationValues[deviceCounter];
+ currentIndex = index;
+ }
+
+ context.Device.Hid.Npads.UpdateRumbleQueue(currentIndex, dualVibrationValues);
+ }
return ResultCode.Success;
}
diff --git a/Ryujinx.HLE/HOS/Services/Hid/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Hid/ResultCode.cs
index 9b829cc503..9c87ac1dc9 100644
--- a/Ryujinx.HLE/HOS/Services/Hid/ResultCode.cs
+++ b/Ryujinx.HLE/HOS/Services/Hid/ResultCode.cs
@@ -7,6 +7,9 @@
Success = 0,
- InvalidNpadIdType = (710 << ErrorCodeShift) | ModuleId
+ InvalidNpadDeviceType = (122 << ErrorCodeShift) | ModuleId,
+ InvalidNpadIdType = (123 << ErrorCodeShift) | ModuleId,
+ InvalidDeviceIndex = (124 << ErrorCodeShift) | ModuleId,
+ InvalidBufferSize = (131 << ErrorCodeShift) | ModuleId
}
}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadStyleIndex.cs b/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadStyleIndex.cs
new file mode 100644
index 0000000000..b85681d271
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadStyleIndex.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ public enum NpadStyleIndex : byte
+ {
+ FullKey = 3,
+ Handheld = 4,
+ JoyDual = 5,
+ JoyLeft = 6,
+ JoyRight = 7,
+ SystemExt = 32,
+ System = 33
+ }
+}
diff --git a/Ryujinx.Headless.SDL2/Program.cs b/Ryujinx.Headless.SDL2/Program.cs
index 2884f38a3e..c3a10929db 100644
--- a/Ryujinx.Headless.SDL2/Program.cs
+++ b/Ryujinx.Headless.SDL2/Program.cs
@@ -236,6 +236,12 @@ namespace Ryujinx.Headless.SDL2
EnableMotion = true,
Sensitivity = 100,
GyroDeadzone = 1,
+ },
+ Rumble = new RumbleConfigController
+ {
+ StrongRumble = 1f,
+ WeakRumble = 1f,
+ EnableRumble = false
}
};
}
diff --git a/Ryujinx.Input.SDL2/SDL2Gamepad.cs b/Ryujinx.Input.SDL2/SDL2Gamepad.cs
index 26a808e493..0ccd8bb34e 100644
--- a/Ryujinx.Input.SDL2/SDL2Gamepad.cs
+++ b/Ryujinx.Input.SDL2/SDL2Gamepad.cs
@@ -1,5 +1,6 @@
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller;
+using Ryujinx.Common.Logging;
using System;
using System.Collections.Generic;
using System.Numerics;
@@ -151,7 +152,18 @@ namespace Ryujinx.Input.SDL2
ushort lowFrequencyRaw = (ushort)(lowFrequency * ushort.MaxValue);
ushort highFrequencyRaw = (ushort)(highFrequency * ushort.MaxValue);
- SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, durationMs);
+ if (durationMs == uint.MaxValue)
+ {
+ SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, SDL_HAPTIC_INFINITY);
+ }
+ else if (durationMs > SDL_HAPTIC_INFINITY)
+ {
+ Logger.Error?.Print(LogClass.Hid, $"Unsupported rumble duration {durationMs}");
+ }
+ else
+ {
+ SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, durationMs);
+ }
}
}
diff --git a/Ryujinx.Input/HLE/NpadController.cs b/Ryujinx.Input/HLE/NpadController.cs
index 3559b015c8..79c18ecf7f 100644
--- a/Ryujinx.Input/HLE/NpadController.cs
+++ b/Ryujinx.Input/HLE/NpadController.cs
@@ -2,8 +2,11 @@
using Ryujinx.Common.Configuration.Hid;
using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
+using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Hid;
using System;
+using System.Collections.Generic;
+using System.Collections.Concurrent;
using System.Numerics;
using System.Runtime.CompilerServices;
@@ -534,5 +537,29 @@ namespace Ryujinx.Input.HLE
{
Dispose(true);
}
+
+ public void UpdateRumble(ConcurrentQueue<(HidVibrationValue, HidVibrationValue)> queue)
+ {
+ if (queue.TryDequeue(out (HidVibrationValue, HidVibrationValue) dualVibrationValue))
+ {
+ if (_config is StandardControllerInputConfig controllerConfig && controllerConfig.Rumble.EnableRumble)
+ {
+ HidVibrationValue leftVibrationValue = dualVibrationValue.Item1;
+ HidVibrationValue rightVibrationValue = dualVibrationValue.Item2;
+
+ 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);
+
+ Logger.Debug?.Print(LogClass.Hid, $"Effect for {controllerConfig.PlayerIndex} " +
+ $"L.low.amp={leftVibrationValue.AmplitudeLow}, " +
+ $"L.high.amp={leftVibrationValue.AmplitudeHigh}, " +
+ $"R.low.amp={rightVibrationValue.AmplitudeLow}, " +
+ $"R.high.amp={rightVibrationValue.AmplitudeHigh} " +
+ $"--> ({low}, {high})");
+ }
+ }
+ }
}
}
diff --git a/Ryujinx.Input/HLE/NpadManager.cs b/Ryujinx.Input/HLE/NpadManager.cs
index 03bde64b7e..a0d2e513f0 100644
--- a/Ryujinx.Input/HLE/NpadManager.cs
+++ b/Ryujinx.Input/HLE/NpadManager.cs
@@ -4,6 +4,7 @@ using Ryujinx.Common.Configuration.Hid.Controller.Motion;
using Ryujinx.Common.Configuration.Hid.Keyboard;
using Ryujinx.HLE.HOS.Services.Hid;
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
@@ -167,6 +168,7 @@ namespace Ryujinx.Input.HLE
(SixAxisInput, SixAxisInput) motionState = default;
NpadController controller = _controllers[(int)inputConfig.PlayerIndex];
+ Ryujinx.HLE.HOS.Services.Hid.PlayerIndex playerIndex = (Ryujinx.HLE.HOS.Services.Hid.PlayerIndex)inputConfig.PlayerIndex;
bool isJoyconPair = false;
@@ -177,6 +179,7 @@ namespace Ryujinx.Input.HLE
controller.UpdateUserConfiguration(inputConfig);
controller.Update();
+ controller.UpdateRumble(_device.Hid.Npads.GetRumbleQueue(playerIndex));
inputState = controller.GetHLEInputState();
@@ -199,15 +202,15 @@ namespace Ryujinx.Input.HLE
motionState.Item1.Orientation = new float[9];
}
- inputState.PlayerId = (Ryujinx.HLE.HOS.Services.Hid.PlayerIndex)inputConfig.PlayerIndex;
- motionState.Item1.PlayerId = (Ryujinx.HLE.HOS.Services.Hid.PlayerIndex)inputConfig.PlayerIndex;
+ inputState.PlayerId = playerIndex;
+ motionState.Item1.PlayerId = playerIndex;
hleInputStates.Add(inputState);
hleMotionStates.Add(motionState.Item1);
if (isJoyconPair && !motionState.Item2.Equals(default))
{
- motionState.Item2.PlayerId = (Ryujinx.HLE.HOS.Services.Hid.PlayerIndex)inputConfig.PlayerIndex;
+ motionState.Item2.PlayerId = playerIndex;
hleMotionStates.Add(motionState.Item2);
}
diff --git a/Ryujinx.Input/IGamepad.cs b/Ryujinx.Input/IGamepad.cs
index cc788333b0..c83ad5f822 100644
--- a/Ryujinx.Input/IGamepad.cs
+++ b/Ryujinx.Input/IGamepad.cs
@@ -66,7 +66,7 @@ namespace Ryujinx.Input
void SetConfiguration(InputConfig configuration);
///
- /// Starts a rumble effect on the gampead.
+ /// Starts a rumble effect on the gamepad.
///
/// The intensity of the low frequency from 0.0f to 1.0f
/// The intensity of the high frequency from 0.0f to 1.0f
diff --git a/Ryujinx/Configuration/ConfigurationFileFormat.cs b/Ryujinx/Configuration/ConfigurationFileFormat.cs
index 65165d8db3..ae43d587db 100644
--- a/Ryujinx/Configuration/ConfigurationFileFormat.cs
+++ b/Ryujinx/Configuration/ConfigurationFileFormat.cs
@@ -14,7 +14,7 @@ namespace Ryujinx.Configuration
///
/// The current version of the file format
///
- public const int CurrentVersion = 29;
+ public const int CurrentVersion = 30;
public int Version { get; set; }
diff --git a/Ryujinx/Configuration/ConfigurationState.cs b/Ryujinx/Configuration/ConfigurationState.cs
index 41bd64a7b0..fe4ff77466 100644
--- a/Ryujinx/Configuration/ConfigurationState.cs
+++ b/Ryujinx/Configuration/ConfigurationState.cs
@@ -1,6 +1,7 @@
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Configuration.Hid;
+using Ryujinx.Common.Configuration.Hid.Controller;
using Ryujinx.Common.Configuration.Hid.Keyboard;
using Ryujinx.Common.Logging;
using Ryujinx.Configuration.System;
@@ -874,6 +875,26 @@ namespace Ryujinx.Configuration
configurationFileUpdated = true;
}
+ if (configurationFileFormat.Version < 30)
+ {
+ Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 30.");
+
+ foreach(InputConfig config in configurationFileFormat.InputConfig)
+ {
+ if (config is StandardControllerInputConfig controllerConfig)
+ {
+ controllerConfig.Rumble = new RumbleConfigController
+ {
+ EnableRumble = false,
+ StrongRumble = 1f,
+ WeakRumble = 1f
+ };
+ }
+ }
+
+ configurationFileUpdated = true;
+ }
+
Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog;
Graphics.ResScale.Value = configurationFileFormat.ResScale;
Graphics.ResScaleCustom.Value = configurationFileFormat.ResScaleCustom;
diff --git a/Ryujinx/Ui/Windows/ControllerWindow.cs b/Ryujinx/Ui/Windows/ControllerWindow.cs
index c57a62c70b..36c2a7aafb 100644
--- a/Ryujinx/Ui/Windows/ControllerWindow.cs
+++ b/Ryujinx/Ui/Windows/ControllerWindow.cs
@@ -34,6 +34,8 @@ namespace Ryujinx.Ui.Windows
private bool _isWaitingForInput;
#pragma warning disable CS0649, IDE0044
+ [GUI] Adjustment _controllerStrongRumble;
+ [GUI] Adjustment _controllerWeakRumble;
[GUI] Adjustment _controllerDeadzoneLeft;
[GUI] Adjustment _controllerDeadzoneRight;
[GUI] Adjustment _controllerTriggerThreshold;
@@ -99,6 +101,8 @@ namespace Ryujinx.Ui.Windows
[GUI] ToggleButton _rSl;
[GUI] ToggleButton _rSr;
[GUI] Image _controllerImage;
+ [GUI] CheckButton _enableRumble;
+ [GUI] Box _rumbleBox;
#pragma warning restore CS0649, IDE0044
private MainWindow _mainWindow;
@@ -314,6 +318,7 @@ namespace Ryujinx.Ui.Windows
_deadZoneRightBox.Hide();
_triggerThresholdBox.Hide();
_motionBox.Hide();
+ _rumbleBox.Hide();
}
else if (_inputDevice.ActiveId != null && _inputDevice.ActiveId.StartsWith("controller"))
{
@@ -407,6 +412,8 @@ namespace Ryujinx.Ui.Windows
_zR.Label = "Unbound";
_rSl.Label = "Unbound";
_rSr.Label = "Unbound";
+ _controllerStrongRumble.Value = 1;
+ _controllerWeakRumble.Value = 1;
_controllerDeadzoneLeft.Value = 0;
_controllerDeadzoneRight.Value = 0;
_controllerTriggerThreshold.Value = 0;
@@ -419,6 +426,7 @@ namespace Ryujinx.Ui.Windows
_gyroDeadzone.Value = 1;
_dsuServerHost.Buffer.Text = "";
_dsuServerPort.Buffer.Text = "";
+ _enableRumble.Active = false;
}
private void SetValues(InputConfig config)
@@ -497,6 +505,9 @@ namespace Ryujinx.Ui.Windows
_zR.Label = controllerConfig.RightJoycon.ButtonZr.ToString();
_rSl.Label = controllerConfig.RightJoycon.ButtonSl.ToString();
_rSr.Label = controllerConfig.RightJoycon.ButtonSr.ToString();
+ _controllerStrongRumble.Value = controllerConfig.Rumble.StrongRumble;
+ _controllerWeakRumble.Value = controllerConfig.Rumble.WeakRumble;
+ _enableRumble.Active = controllerConfig.Rumble.EnableRumble;
_controllerDeadzoneLeft.Value = controllerConfig.DeadzoneLeft;
_controllerDeadzoneRight.Value = controllerConfig.DeadzoneRight;
_controllerTriggerThreshold.Value = controllerConfig.TriggerThreshold;
@@ -706,7 +717,13 @@ namespace Ryujinx.Ui.Windows
InvertStickY = _invertRStickY.Active,
StickButton = rStickButton,
},
- Motion = motionConfig
+ Motion = motionConfig,
+ Rumble = new RumbleConfigController
+ {
+ StrongRumble = (float)_controllerStrongRumble.Value,
+ WeakRumble = (float)_controllerWeakRumble.Value,
+ EnableRumble = _enableRumble.Active
+ }
};
}
@@ -1045,6 +1062,12 @@ namespace Ryujinx.Ui.Windows
EnableMotion = true,
Sensitivity = 100,
GyroDeadzone = 1,
+ },
+ Rumble = new RumbleConfigController
+ {
+ StrongRumble = 1f,
+ WeakRumble = 1f,
+ EnableRumble = false
}
};
}
diff --git a/Ryujinx/Ui/Windows/ControllerWindow.glade b/Ryujinx/Ui/Windows/ControllerWindow.glade
index 8e897795a6..a396df03cb 100644
--- a/Ryujinx/Ui/Windows/ControllerWindow.glade
+++ b/Ryujinx/Ui/Windows/ControllerWindow.glade
@@ -7,6 +7,20 @@
1
4
+
+
False