/ src / Ryujinx.Input / HLE / NpadController.cs
NpadController.cs
  1  using Ryujinx.Common;
  2  using Ryujinx.Common.Configuration.Hid;
  3  using Ryujinx.Common.Configuration.Hid.Controller;
  4  using Ryujinx.Common.Configuration.Hid.Controller.Motion;
  5  using Ryujinx.Common.Logging;
  6  using Ryujinx.HLE.HOS.Services.Hid;
  7  using System;
  8  using System.Collections.Concurrent;
  9  using System.Numerics;
 10  using System.Runtime.CompilerServices;
 11  using CemuHookClient = Ryujinx.Input.Motion.CemuHook.Client;
 12  using ConfigControllerType = Ryujinx.Common.Configuration.Hid.ControllerType;
 13  
 14  namespace Ryujinx.Input.HLE
 15  {
 16      public class NpadController : IDisposable
 17      {
 18          private class HLEButtonMappingEntry
 19          {
 20              public readonly GamepadButtonInputId DriverInputId;
 21              public readonly ControllerKeys HLEInput;
 22  
 23              public HLEButtonMappingEntry(GamepadButtonInputId driverInputId, ControllerKeys hleInput)
 24              {
 25                  DriverInputId = driverInputId;
 26                  HLEInput = hleInput;
 27              }
 28          }
 29  
 30          private static readonly HLEButtonMappingEntry[] _hleButtonMapping = {
 31              new(GamepadButtonInputId.A, ControllerKeys.A),
 32              new(GamepadButtonInputId.B, ControllerKeys.B),
 33              new(GamepadButtonInputId.X, ControllerKeys.X),
 34              new(GamepadButtonInputId.Y, ControllerKeys.Y),
 35              new(GamepadButtonInputId.LeftStick, ControllerKeys.LStick),
 36              new(GamepadButtonInputId.RightStick, ControllerKeys.RStick),
 37              new(GamepadButtonInputId.LeftShoulder, ControllerKeys.L),
 38              new(GamepadButtonInputId.RightShoulder, ControllerKeys.R),
 39              new(GamepadButtonInputId.LeftTrigger, ControllerKeys.Zl),
 40              new(GamepadButtonInputId.RightTrigger, ControllerKeys.Zr),
 41              new(GamepadButtonInputId.DpadUp, ControllerKeys.DpadUp),
 42              new(GamepadButtonInputId.DpadDown, ControllerKeys.DpadDown),
 43              new(GamepadButtonInputId.DpadLeft, ControllerKeys.DpadLeft),
 44              new(GamepadButtonInputId.DpadRight, ControllerKeys.DpadRight),
 45              new(GamepadButtonInputId.Minus, ControllerKeys.Minus),
 46              new(GamepadButtonInputId.Plus, ControllerKeys.Plus),
 47  
 48              new(GamepadButtonInputId.SingleLeftTrigger0, ControllerKeys.SlLeft),
 49              new(GamepadButtonInputId.SingleRightTrigger0, ControllerKeys.SrLeft),
 50              new(GamepadButtonInputId.SingleLeftTrigger1, ControllerKeys.SlRight),
 51              new(GamepadButtonInputId.SingleRightTrigger1, ControllerKeys.SrRight),
 52          };
 53  
 54          private class HLEKeyboardMappingEntry
 55          {
 56              public readonly Key TargetKey;
 57              public readonly byte Target;
 58  
 59              public HLEKeyboardMappingEntry(Key targetKey, byte target)
 60              {
 61                  TargetKey = targetKey;
 62                  Target = target;
 63              }
 64          }
 65  
 66          private static readonly HLEKeyboardMappingEntry[] _keyMapping = {
 67              new(Key.A, 0x4),
 68              new(Key.B, 0x5),
 69              new(Key.C, 0x6),
 70              new(Key.D, 0x7),
 71              new(Key.E, 0x8),
 72              new(Key.F, 0x9),
 73              new(Key.G, 0xA),
 74              new(Key.H, 0xB),
 75              new(Key.I, 0xC),
 76              new(Key.J, 0xD),
 77              new(Key.K, 0xE),
 78              new(Key.L, 0xF),
 79              new(Key.M, 0x10),
 80              new(Key.N, 0x11),
 81              new(Key.O, 0x12),
 82              new(Key.P, 0x13),
 83              new(Key.Q, 0x14),
 84              new(Key.R, 0x15),
 85              new(Key.S, 0x16),
 86              new(Key.T, 0x17),
 87              new(Key.U, 0x18),
 88              new(Key.V, 0x19),
 89              new(Key.W, 0x1A),
 90              new(Key.X, 0x1B),
 91              new(Key.Y, 0x1C),
 92              new(Key.Z, 0x1D),
 93  
 94              new(Key.Number1, 0x1E),
 95              new(Key.Number2, 0x1F),
 96              new(Key.Number3, 0x20),
 97              new(Key.Number4, 0x21),
 98              new(Key.Number5, 0x22),
 99              new(Key.Number6, 0x23),
100              new(Key.Number7, 0x24),
101              new(Key.Number8, 0x25),
102              new(Key.Number9, 0x26),
103              new(Key.Number0, 0x27),
104  
105              new(Key.Enter,        0x28),
106              new(Key.Escape,       0x29),
107              new(Key.BackSpace,    0x2A),
108              new(Key.Tab,          0x2B),
109              new(Key.Space,        0x2C),
110              new(Key.Minus,        0x2D),
111              new(Key.Plus,         0x2E),
112              new(Key.BracketLeft,  0x2F),
113              new(Key.BracketRight, 0x30),
114              new(Key.BackSlash,    0x31),
115              new(Key.Tilde,        0x32),
116              new(Key.Semicolon,    0x33),
117              new(Key.Quote,        0x34),
118              new(Key.Grave,        0x35),
119              new(Key.Comma,        0x36),
120              new(Key.Period,       0x37),
121              new(Key.Slash,        0x38),
122              new(Key.CapsLock,     0x39),
123  
124              new(Key.F1,  0x3a),
125              new(Key.F2,  0x3b),
126              new(Key.F3,  0x3c),
127              new(Key.F4,  0x3d),
128              new(Key.F5,  0x3e),
129              new(Key.F6,  0x3f),
130              new(Key.F7,  0x40),
131              new(Key.F8,  0x41),
132              new(Key.F9,  0x42),
133              new(Key.F10, 0x43),
134              new(Key.F11, 0x44),
135              new(Key.F12, 0x45),
136  
137              new(Key.PrintScreen, 0x46),
138              new(Key.ScrollLock,  0x47),
139              new(Key.Pause,       0x48),
140              new(Key.Insert,      0x49),
141              new(Key.Home,        0x4A),
142              new(Key.PageUp,      0x4B),
143              new(Key.Delete,      0x4C),
144              new(Key.End,         0x4D),
145              new(Key.PageDown,    0x4E),
146              new(Key.Right,       0x4F),
147              new(Key.Left,        0x50),
148              new(Key.Down,        0x51),
149              new(Key.Up,          0x52),
150  
151              new(Key.NumLock,        0x53),
152              new(Key.KeypadDivide,   0x54),
153              new(Key.KeypadMultiply, 0x55),
154              new(Key.KeypadSubtract, 0x56),
155              new(Key.KeypadAdd,      0x57),
156              new(Key.KeypadEnter,    0x58),
157              new(Key.Keypad1,        0x59),
158              new(Key.Keypad2,        0x5A),
159              new(Key.Keypad3,        0x5B),
160              new(Key.Keypad4,        0x5C),
161              new(Key.Keypad5,        0x5D),
162              new(Key.Keypad6,        0x5E),
163              new(Key.Keypad7,        0x5F),
164              new(Key.Keypad8,        0x60),
165              new(Key.Keypad9,        0x61),
166              new(Key.Keypad0,        0x62),
167              new(Key.KeypadDecimal,  0x63),
168  
169              new(Key.F13, 0x68),
170              new(Key.F14, 0x69),
171              new(Key.F15, 0x6A),
172              new(Key.F16, 0x6B),
173              new(Key.F17, 0x6C),
174              new(Key.F18, 0x6D),
175              new(Key.F19, 0x6E),
176              new(Key.F20, 0x6F),
177              new(Key.F21, 0x70),
178              new(Key.F22, 0x71),
179              new(Key.F23, 0x72),
180              new(Key.F24, 0x73),
181  
182              new(Key.ControlLeft,  0xE0),
183              new(Key.ShiftLeft,    0xE1),
184              new(Key.AltLeft,      0xE2),
185              new(Key.WinLeft,      0xE3),
186              new(Key.ControlRight, 0xE4),
187              new(Key.ShiftRight,   0xE5),
188              new(Key.AltRight,     0xE6),
189              new(Key.WinRight,     0xE7),
190          };
191  
192          private static readonly HLEKeyboardMappingEntry[] _keyModifierMapping = {
193              new(Key.ControlLeft,  0),
194              new(Key.ShiftLeft,    1),
195              new(Key.AltLeft,      2),
196              new(Key.WinLeft,      3),
197              new(Key.ControlRight, 4),
198              new(Key.ShiftRight,   5),
199              new(Key.AltRight,     6),
200              new(Key.WinRight,     7),
201              new(Key.CapsLock,     8),
202              new(Key.ScrollLock,   9),
203              new(Key.NumLock,      10),
204          };
205  
206          private MotionInput _leftMotionInput;
207          private MotionInput _rightMotionInput;
208  
209          private IGamepad _gamepad;
210          private InputConfig _config;
211  
212          public IGamepadDriver GamepadDriver { get; private set; }
213          public GamepadStateSnapshot State { get; private set; }
214  
215          public string Id { get; private set; }
216  
217          private readonly CemuHookClient _cemuHookClient;
218  
219          public NpadController(CemuHookClient cemuHookClient)
220          {
221              State = default;
222              Id = null;
223              _cemuHookClient = cemuHookClient;
224          }
225  
226          public bool UpdateDriverConfiguration(IGamepadDriver gamepadDriver, InputConfig config)
227          {
228              GamepadDriver = gamepadDriver;
229  
230              _gamepad?.Dispose();
231  
232              Id = config.Id;
233              _gamepad = GamepadDriver.GetGamepad(Id);
234  
235              UpdateUserConfiguration(config);
236  
237              return _gamepad != null;
238          }
239  
240          public void UpdateUserConfiguration(InputConfig config)
241          {
242              if (config is StandardControllerInputConfig controllerConfig)
243              {
244                  bool needsMotionInputUpdate = _config is not StandardControllerInputConfig oldControllerConfig ||
245                      ((oldControllerConfig.Motion.EnableMotion != controllerConfig.Motion.EnableMotion) &&
246                      (oldControllerConfig.Motion.MotionBackend != controllerConfig.Motion.MotionBackend));
247  
248                  if (needsMotionInputUpdate)
249                  {
250                      UpdateMotionInput(controllerConfig.Motion);
251                  }
252              }
253              else
254              {
255                  // Non-controller doesn't have motions.
256                  _leftMotionInput = null;
257              }
258  
259              _config = config;
260  
261              _gamepad?.SetConfiguration(config);
262          }
263  
264          private void UpdateMotionInput(MotionConfigController motionConfig)
265          {
266              if (motionConfig.MotionBackend != MotionInputBackendType.CemuHook)
267              {
268                  _leftMotionInput = new MotionInput();
269              }
270              else
271              {
272                  _leftMotionInput = null;
273              }
274          }
275  
276          public void Update()
277          {
278              // _gamepad may be altered by other threads
279              var gamepad = _gamepad;
280  
281              if (gamepad != null && GamepadDriver != null)
282              {
283                  State = gamepad.GetMappedStateSnapshot();
284  
285                  if (_config is StandardControllerInputConfig controllerConfig && controllerConfig.Motion.EnableMotion)
286                  {
287                      if (controllerConfig.Motion.MotionBackend == MotionInputBackendType.GamepadDriver)
288                      {
289                          if (gamepad.Features.HasFlag(GamepadFeaturesFlag.Motion))
290                          {
291                              Vector3 accelerometer = gamepad.GetMotionData(MotionInputId.Accelerometer);
292                              Vector3 gyroscope = gamepad.GetMotionData(MotionInputId.Gyroscope);
293  
294                              accelerometer = new Vector3(accelerometer.X, -accelerometer.Z, accelerometer.Y);
295                              gyroscope = new Vector3(gyroscope.X, -gyroscope.Z, gyroscope.Y);
296  
297                              _leftMotionInput.Update(accelerometer, gyroscope, (ulong)PerformanceCounter.ElapsedNanoseconds / 1000, controllerConfig.Motion.Sensitivity, (float)controllerConfig.Motion.GyroDeadzone);
298  
299                              if (controllerConfig.ControllerType == ConfigControllerType.JoyconPair)
300                              {
301                                  _rightMotionInput = _leftMotionInput;
302                              }
303                          }
304                      }
305                      else if (controllerConfig.Motion.MotionBackend == MotionInputBackendType.CemuHook && controllerConfig.Motion is CemuHookMotionConfigController cemuControllerConfig)
306                      {
307                          int clientId = (int)controllerConfig.PlayerIndex;
308  
309                          // First of all ensure we are registered
310                          _cemuHookClient.RegisterClient(clientId, cemuControllerConfig.DsuServerHost, cemuControllerConfig.DsuServerPort);
311  
312                          // Then request and retrieve the data
313                          _cemuHookClient.RequestData(clientId, cemuControllerConfig.Slot);
314                          _cemuHookClient.TryGetData(clientId, cemuControllerConfig.Slot, out _leftMotionInput);
315  
316                          if (controllerConfig.ControllerType == ConfigControllerType.JoyconPair)
317                          {
318                              if (!cemuControllerConfig.MirrorInput)
319                              {
320                                  _cemuHookClient.RequestData(clientId, cemuControllerConfig.AltSlot);
321                                  _cemuHookClient.TryGetData(clientId, cemuControllerConfig.AltSlot, out _rightMotionInput);
322                              }
323                              else
324                              {
325                                  _rightMotionInput = _leftMotionInput;
326                              }
327                          }
328                      }
329                  }
330              }
331              else
332              {
333                  // Reset states
334                  State = default;
335                  _leftMotionInput = null;
336              }
337          }
338  
339          public GamepadInput GetHLEInputState()
340          {
341              GamepadInput state = new();
342  
343              // First update all buttons
344              foreach (HLEButtonMappingEntry entry in _hleButtonMapping)
345              {
346                  if (State.IsPressed(entry.DriverInputId))
347                  {
348                      state.Buttons |= entry.HLEInput;
349                  }
350              }
351  
352              if (_gamepad is IKeyboard)
353              {
354                  (float leftAxisX, float leftAxisY) = State.GetStick(StickInputId.Left);
355                  (float rightAxisX, float rightAxisY) = State.GetStick(StickInputId.Right);
356  
357                  state.LStick = new JoystickPosition
358                  {
359                      Dx = ClampAxis(leftAxisX),
360                      Dy = ClampAxis(leftAxisY),
361                  };
362  
363                  state.RStick = new JoystickPosition
364                  {
365                      Dx = ClampAxis(rightAxisX),
366                      Dy = ClampAxis(rightAxisY),
367                  };
368              }
369              else if (_config is StandardControllerInputConfig controllerConfig)
370              {
371                  (float leftAxisX, float leftAxisY) = State.GetStick(StickInputId.Left);
372                  (float rightAxisX, float rightAxisY) = State.GetStick(StickInputId.Right);
373  
374                  state.LStick = ClampToCircle(ApplyDeadzone(leftAxisX, leftAxisY, controllerConfig.DeadzoneLeft), controllerConfig.RangeLeft);
375                  state.RStick = ClampToCircle(ApplyDeadzone(rightAxisX, rightAxisY, controllerConfig.DeadzoneRight), controllerConfig.RangeRight);
376              }
377  
378              return state;
379          }
380  
381          [MethodImpl(MethodImplOptions.AggressiveInlining)]
382          private static JoystickPosition ApplyDeadzone(float x, float y, float deadzone)
383          {
384              float magnitudeClamped = Math.Min(MathF.Sqrt(x * x + y * y), 1f);
385  
386              if (magnitudeClamped <= deadzone)
387              {
388                  return new JoystickPosition { Dx = 0, Dy = 0 };
389              }
390  
391              return new JoystickPosition
392              {
393                  Dx = ClampAxis((x / magnitudeClamped) * ((magnitudeClamped - deadzone) / (1 - deadzone))),
394                  Dy = ClampAxis((y / magnitudeClamped) * ((magnitudeClamped - deadzone) / (1 - deadzone))),
395              };
396          }
397  
398          [MethodImpl(MethodImplOptions.AggressiveInlining)]
399          private static short ClampAxis(float value)
400          {
401              if (Math.Sign(value) < 0)
402              {
403                  return (short)Math.Max(value * -short.MinValue, short.MinValue);
404              }
405  
406              return (short)Math.Min(value * short.MaxValue, short.MaxValue);
407          }
408  
409          [MethodImpl(MethodImplOptions.AggressiveInlining)]
410          private static JoystickPosition ClampToCircle(JoystickPosition position, float range)
411          {
412              Vector2 point = new Vector2(position.Dx, position.Dy) * range;
413  
414              if (point.Length() > short.MaxValue)
415              {
416                  point = point / point.Length() * short.MaxValue;
417              }
418  
419              return new JoystickPosition
420              {
421                  Dx = (int)point.X,
422                  Dy = (int)point.Y,
423              };
424          }
425  
426          public SixAxisInput GetHLEMotionState(bool isJoyconRightPair = false)
427          {
428              float[] orientationForHLE = new float[9];
429              Vector3 gyroscope;
430              Vector3 accelerometer;
431              Vector3 rotation;
432  
433              MotionInput motionInput = _leftMotionInput;
434  
435              if (isJoyconRightPair)
436              {
437                  if (_rightMotionInput == null)
438                  {
439                      return default;
440                  }
441  
442                  motionInput = _rightMotionInput;
443              }
444  
445              if (motionInput != null)
446              {
447                  gyroscope = Truncate(motionInput.Gyroscrope * 0.0027f, 3);
448                  accelerometer = Truncate(motionInput.Accelerometer, 3);
449                  rotation = Truncate(motionInput.Rotation * 0.0027f, 3);
450  
451                  Matrix4x4 orientation = motionInput.GetOrientation();
452  
453                  orientationForHLE[0] = Math.Clamp(orientation.M11, -1f, 1f);
454                  orientationForHLE[1] = Math.Clamp(orientation.M12, -1f, 1f);
455                  orientationForHLE[2] = Math.Clamp(orientation.M13, -1f, 1f);
456                  orientationForHLE[3] = Math.Clamp(orientation.M21, -1f, 1f);
457                  orientationForHLE[4] = Math.Clamp(orientation.M22, -1f, 1f);
458                  orientationForHLE[5] = Math.Clamp(orientation.M23, -1f, 1f);
459                  orientationForHLE[6] = Math.Clamp(orientation.M31, -1f, 1f);
460                  orientationForHLE[7] = Math.Clamp(orientation.M32, -1f, 1f);
461                  orientationForHLE[8] = Math.Clamp(orientation.M33, -1f, 1f);
462              }
463              else
464              {
465                  gyroscope = new Vector3();
466                  accelerometer = new Vector3();
467                  rotation = new Vector3();
468              }
469  
470              return new SixAxisInput
471              {
472                  Accelerometer = accelerometer,
473                  Gyroscope = gyroscope,
474                  Rotation = rotation,
475                  Orientation = orientationForHLE,
476              };
477          }
478  
479          private static Vector3 Truncate(Vector3 value, int decimals)
480          {
481              float power = MathF.Pow(10, decimals);
482  
483              value.X = float.IsNegative(value.X) ? MathF.Ceiling(value.X * power) / power : MathF.Floor(value.X * power) / power;
484              value.Y = float.IsNegative(value.Y) ? MathF.Ceiling(value.Y * power) / power : MathF.Floor(value.Y * power) / power;
485              value.Z = float.IsNegative(value.Z) ? MathF.Ceiling(value.Z * power) / power : MathF.Floor(value.Z * power) / power;
486  
487              return value;
488          }
489  
490          public static KeyboardInput GetHLEKeyboardInput(IGamepadDriver KeyboardDriver)
491          {
492              var keyboard = KeyboardDriver.GetGamepad("0") as IKeyboard;
493  
494              KeyboardStateSnapshot keyboardState = keyboard.GetKeyboardStateSnapshot();
495  
496              KeyboardInput hidKeyboard = new()
497              {
498                  Modifier = 0,
499                  Keys = new ulong[0x4],
500              };
501  
502              foreach (HLEKeyboardMappingEntry entry in _keyMapping)
503              {
504                  ulong value = keyboardState.IsPressed(entry.TargetKey) ? 1UL : 0UL;
505  
506                  hidKeyboard.Keys[entry.Target / 0x40] |= (value << (entry.Target % 0x40));
507              }
508  
509              foreach (HLEKeyboardMappingEntry entry in _keyModifierMapping)
510              {
511                  int value = keyboardState.IsPressed(entry.TargetKey) ? 1 : 0;
512  
513                  hidKeyboard.Modifier |= value << entry.Target;
514              }
515  
516              return hidKeyboard;
517  
518          }
519  
520          protected virtual void Dispose(bool disposing)
521          {
522              if (disposing)
523              {
524                  _gamepad?.Dispose();
525              }
526          }
527  
528          public void Dispose()
529          {
530              GC.SuppressFinalize(this);
531              Dispose(true);
532          }
533  
534          public void UpdateRumble(ConcurrentQueue<(VibrationValue, VibrationValue)> queue)
535          {
536              if (queue.TryDequeue(out (VibrationValue, VibrationValue) dualVibrationValue))
537              {
538                  if (_config is StandardControllerInputConfig controllerConfig && controllerConfig.Rumble.EnableRumble)
539                  {
540                      VibrationValue leftVibrationValue = dualVibrationValue.Item1;
541                      VibrationValue rightVibrationValue = dualVibrationValue.Item2;
542  
543                      float low = Math.Min(1f, (float)((rightVibrationValue.AmplitudeLow * 0.85 + rightVibrationValue.AmplitudeHigh * 0.15) * controllerConfig.Rumble.StrongRumble));
544                      float high = Math.Min(1f, (float)((leftVibrationValue.AmplitudeLow * 0.15 + leftVibrationValue.AmplitudeHigh * 0.85) * controllerConfig.Rumble.WeakRumble));
545  
546                      _gamepad.Rumble(low, high, uint.MaxValue);
547  
548                      Logger.Debug?.Print(LogClass.Hid, $"Effect for {controllerConfig.PlayerIndex} " +
549                          $"L.low.amp={leftVibrationValue.AmplitudeLow}, " +
550                          $"L.high.amp={leftVibrationValue.AmplitudeHigh}, " +
551                          $"R.low.amp={rightVibrationValue.AmplitudeLow}, " +
552                          $"R.high.amp={rightVibrationValue.AmplitudeHigh} " +
553                          $"--> ({low}, {high})");
554                  }
555              }
556          }
557      }
558  }