aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.HLE/HOS/Services/Hid
diff options
context:
space:
mode:
authorTSR Berry <20988865+TSRBerry@users.noreply.github.com>2023-04-08 01:22:00 +0200
committerMary <thog@protonmail.com>2023-04-27 23:51:14 +0200
commitcee712105850ac3385cd0091a923438167433f9f (patch)
tree4a5274b21d8b7f938c0d0ce18736d3f2993b11b1 /src/Ryujinx.HLE/HOS/Services/Hid
parentcd124bda587ef09668a971fa1cac1c3f0cfc9f21 (diff)
Move solution and projects to src
Diffstat (limited to 'src/Ryujinx.HLE/HOS/Services/Hid')
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Hid.cs107
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/BaseDevice.cs14
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/DebugPadDevice.cs28
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/KeyboardDevice.cs35
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/MouseDevice.cs36
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs635
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/TouchDevice.cs48
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/ControllerConfig.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/GamepadInput.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/JoystickPosition.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/KeyboardInput.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/SixAxisInput.cs13
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/TouchPoint.cs14
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidServer/HidUtils.cs46
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidServer/IActiveVibrationDeviceList.cs16
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidServer/IAppletResource.cs35
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/NpadHandheldActivationMode.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/NpadJoyDeviceType.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/AccelerometerParameters.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/GyroscopeZeroDriftMode.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/SensorFusionParameters.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceHandle.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDevicePosition.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceType.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceValue.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationValue.cs24
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/IHidDebugServer.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs1800
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/IHidSystemServer.cs76
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/IHidbusServer.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/ISystemServer.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Irs/IIrSensorServer.cs240
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Irs/IIrSensorSystemServer.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Irs/ResultCode.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/ImageTransferProcessorState.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/IrCameraHandle.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedClusteringProcessorConfig.cs25
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedImageTransferProcessorConfig.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedMomentProcessorConfig.cs23
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedTeraPluginProcessorConfig.cs14
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/ResultCode.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/AppletFooterUiType.cs30
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/HidVector.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/ControllerKeys.cs45
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/ControllerType.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadColor.cs37
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadIdType.cs16
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadStyleIndex.cs13
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/PlayerIndex.cs17
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/NpadJoyHoldType.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/AnalogStickState.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/AtomicStorage.cs26
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/ISampledDataStruct.cs65
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/RingLifo.cs149
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadAttribute.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadButton.cs24
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadState.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardKey.cs29
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardKeyShift.cs138
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardModifier.cs20
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardState.cs13
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseAttribute.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseButton.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseState.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/DeviceType.cs29
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadAttribute.cs16
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadBatteryLevel.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadButton.cs44
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadColorAttribute.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadCommonState.cs16
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadFullKeyColorState.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadGcTriggerState.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadInternalState.cs65
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadJoyAssignmentMode.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadJoyColorState.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadLarkType.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadLuciaType.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadState.cs18
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadStyleTag.cs76
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadSystemButtonProperties.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadSystemProperties.cs24
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/SixAxisSensorAttribute.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/SixAxisSensorState.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/SharedMemory.cs66
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchAttribute.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchScreenState.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchState.cs19
87 files changed, 4710 insertions, 0 deletions
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Hid.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Hid.cs
new file mode 100644
index 00000000..b1466c78
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Hid.cs
@@ -0,0 +1,107 @@
+using Ryujinx.Common;
+using Ryujinx.Common.Configuration.Hid;
+using Ryujinx.Common.Memory;
+using Ryujinx.HLE.Exceptions;
+using Ryujinx.HLE.HOS.Kernel.Memory;
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory;
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugPad;
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard;
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse;
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad;
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.TouchScreen;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ public class Hid
+ {
+ private readonly Switch _device;
+
+ private readonly SharedMemoryStorage _storage;
+
+ internal ref SharedMemory SharedMemory => ref _storage.GetRef<SharedMemory>(0);
+
+ internal const int SharedMemEntryCount = 17;
+
+ public DebugPadDevice DebugPad;
+ public TouchDevice Touchscreen;
+ public MouseDevice Mouse;
+ public KeyboardDevice Keyboard;
+ public NpadDevices Npads;
+
+ private static void CheckTypeSizeOrThrow<T>(int expectedSize)
+ {
+ if (Unsafe.SizeOf<T>() != expectedSize)
+ {
+ throw new InvalidStructLayoutException<T>(expectedSize);
+ }
+ }
+
+ static Hid()
+ {
+ CheckTypeSizeOrThrow<RingLifo<DebugPadState>>(0x2c8);
+ CheckTypeSizeOrThrow<RingLifo<TouchScreenState>>(0x2C38);
+ CheckTypeSizeOrThrow<RingLifo<MouseState>>(0x350);
+ CheckTypeSizeOrThrow<RingLifo<KeyboardState>>(0x3D8);
+ CheckTypeSizeOrThrow<Array10<NpadState>>(0x32000);
+ CheckTypeSizeOrThrow<SharedMemory>(Horizon.HidSize);
+ }
+
+ internal Hid(in Switch device, SharedMemoryStorage storage)
+ {
+ _device = device;
+ _storage = storage;
+
+ SharedMemory = SharedMemory.Create();
+
+ InitDevices();
+ }
+
+ private void InitDevices()
+ {
+ DebugPad = new DebugPadDevice(_device, true);
+ Touchscreen = new TouchDevice(_device, true);
+ Mouse = new MouseDevice(_device, false);
+ Keyboard = new KeyboardDevice(_device, false);
+ Npads = new NpadDevices(_device, true);
+ }
+
+ public void RefreshInputConfig(List<InputConfig> inputConfig)
+ {
+ ControllerConfig[] npadConfig = new ControllerConfig[inputConfig.Count];
+
+ for (int i = 0; i < npadConfig.Length; ++i)
+ {
+ npadConfig[i].Player = (PlayerIndex)inputConfig[i].PlayerIndex;
+ npadConfig[i].Type = (ControllerType)inputConfig[i].ControllerType;
+ }
+
+ _device.Hid.Npads.Configure(npadConfig);
+ }
+
+ public ControllerKeys UpdateStickButtons(JoystickPosition leftStick, JoystickPosition rightStick)
+ {
+ const int stickButtonThreshold = short.MaxValue / 2;
+ ControllerKeys result = 0;
+
+ result |= (leftStick.Dx < -stickButtonThreshold) ? ControllerKeys.LStickLeft : result;
+ result |= (leftStick.Dx > stickButtonThreshold) ? ControllerKeys.LStickRight : result;
+ result |= (leftStick.Dy < -stickButtonThreshold) ? ControllerKeys.LStickDown : result;
+ result |= (leftStick.Dy > stickButtonThreshold) ? ControllerKeys.LStickUp : result;
+
+ result |= (rightStick.Dx < -stickButtonThreshold) ? ControllerKeys.RStickLeft : result;
+ result |= (rightStick.Dx > stickButtonThreshold) ? ControllerKeys.RStickRight : result;
+ result |= (rightStick.Dy < -stickButtonThreshold) ? ControllerKeys.RStickDown : result;
+ result |= (rightStick.Dy > stickButtonThreshold) ? ControllerKeys.RStickUp : result;
+
+ return result;
+ }
+
+ internal static ulong GetTimestampTicks()
+ {
+ return (ulong)PerformanceCounter.ElapsedMilliseconds * 19200;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/BaseDevice.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/BaseDevice.cs
new file mode 100644
index 00000000..0e3cd18a
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/BaseDevice.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ public abstract class BaseDevice
+ {
+ protected readonly Switch _device;
+ public bool Active;
+
+ public BaseDevice(Switch device, bool active)
+ {
+ _device = device;
+ Active = active;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/DebugPadDevice.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/DebugPadDevice.cs
new file mode 100644
index 00000000..e3b95390
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/DebugPadDevice.cs
@@ -0,0 +1,28 @@
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugPad;
+
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ public class DebugPadDevice : BaseDevice
+ {
+ public DebugPadDevice(Switch device, bool active) : base(device, active) { }
+
+ public void Update()
+ {
+ ref RingLifo<DebugPadState> lifo = ref _device.Hid.SharedMemory.DebugPad;
+
+ ref DebugPadState previousEntry = ref lifo.GetCurrentEntryRef();
+
+ DebugPadState newState = new DebugPadState();
+
+ if (Active)
+ {
+ // TODO: This is a debug device only present in dev environment, do we want to support it?
+ }
+
+ newState.SamplingNumber = previousEntry.SamplingNumber + 1;
+
+ lifo.Write(ref newState);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/KeyboardDevice.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/KeyboardDevice.cs
new file mode 100644
index 00000000..8908b74d
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/KeyboardDevice.cs
@@ -0,0 +1,35 @@
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard;
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ public class KeyboardDevice : BaseDevice
+ {
+ public KeyboardDevice(Switch device, bool active) : base(device, active) { }
+
+ public void Update(KeyboardInput keyState)
+ {
+ ref RingLifo<KeyboardState> lifo = ref _device.Hid.SharedMemory.Keyboard;
+
+ if (!Active)
+ {
+ lifo.Clear();
+
+ return;
+ }
+
+ ref KeyboardState previousEntry = ref lifo.GetCurrentEntryRef();
+
+ KeyboardState newState = new KeyboardState
+ {
+ SamplingNumber = previousEntry.SamplingNumber + 1,
+ };
+
+ keyState.Keys.AsSpan().CopyTo(newState.Keys.RawData.AsSpan());
+ newState.Modifiers = (KeyboardModifier)keyState.Modifier;
+
+ lifo.Write(ref newState);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/MouseDevice.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/MouseDevice.cs
new file mode 100644
index 00000000..66d1b0c4
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/MouseDevice.cs
@@ -0,0 +1,36 @@
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse;
+
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ public class MouseDevice : BaseDevice
+ {
+ public MouseDevice(Switch device, bool active) : base(device, active) { }
+
+ public void Update(int mouseX, int mouseY, uint buttons = 0, int scrollX = 0, int scrollY = 0, bool connected = false)
+ {
+ ref RingLifo<MouseState> lifo = ref _device.Hid.SharedMemory.Mouse;
+
+ ref MouseState previousEntry = ref lifo.GetCurrentEntryRef();
+
+ MouseState newState = new MouseState()
+ {
+ SamplingNumber = previousEntry.SamplingNumber + 1,
+ };
+
+ if (Active)
+ {
+ newState.Buttons = (MouseButton)buttons;
+ newState.X = mouseX;
+ newState.Y = mouseY;
+ newState.DeltaX = mouseX - previousEntry.DeltaX;
+ newState.DeltaY = mouseY - previousEntry.DeltaY;
+ newState.WheelDeltaX = scrollX;
+ newState.WheelDeltaY = scrollY;
+ newState.Attributes = connected ? MouseAttribute.IsConnected : MouseAttribute.None;
+ }
+
+ lifo.Write(ref newState);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs
new file mode 100644
index 00000000..edcc47d8
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/NpadDevices.cs
@@ -0,0 +1,635 @@
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.HLE.HOS.Services.Hid.Types;
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ public class NpadDevices : BaseDevice
+ {
+ private const int NoMatchNotifyFrequencyMs = 2000;
+ private int _activeCount;
+ private long _lastNotifyTimestamp;
+
+ public const int MaxControllers = 9; // Players 1-8 and Handheld
+ private ControllerType[] _configuredTypes;
+ private KEvent[] _styleSetUpdateEvents;
+ private bool[] _supportedPlayers;
+ private static VibrationValue _neutralVibrationValue = new VibrationValue
+ {
+ 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<PlayerIndex, ConcurrentQueue<(VibrationValue, VibrationValue)>> RumbleQueues = new Dictionary<PlayerIndex, ConcurrentQueue<(VibrationValue, VibrationValue)>>();
+ public Dictionary<PlayerIndex, (VibrationValue, VibrationValue)> LastVibrationValues = new Dictionary<PlayerIndex, (VibrationValue, VibrationValue)>();
+
+ public NpadDevices(Switch device, bool active = true) : base(device, active)
+ {
+ _configuredTypes = new ControllerType[MaxControllers];
+
+ SupportedStyleSets = ControllerType.Handheld | ControllerType.JoyconPair |
+ ControllerType.JoyconLeft | ControllerType.JoyconRight |
+ ControllerType.ProController;
+
+ _supportedPlayers = new bool[MaxControllers];
+ _supportedPlayers.AsSpan().Fill(true);
+
+ _styleSetUpdateEvents = new KEvent[MaxControllers];
+ for (int i = 0; i < _styleSetUpdateEvents.Length; ++i)
+ {
+ _styleSetUpdateEvents[i] = new KEvent(_device.System.KernelContext);
+ }
+
+ _activeCount = 0;
+
+ JoyHold = NpadJoyHoldType.Vertical;
+ }
+
+ internal ref KEvent GetStyleSetUpdateEvent(PlayerIndex player)
+ {
+ return ref _styleSetUpdateEvents[(int)player];
+ }
+
+ internal void ClearSupportedPlayers()
+ {
+ _supportedPlayers.AsSpan().Clear();
+ }
+
+ internal void SetSupportedPlayer(PlayerIndex player, bool supported = true)
+ {
+ _supportedPlayers[(int)player] = supported;
+ }
+
+ internal IEnumerable<PlayerIndex> GetSupportedPlayers()
+ {
+ for (int i = 0; i < _supportedPlayers.Length; ++i)
+ {
+ if (_supportedPlayers[i])
+ {
+ yield return (PlayerIndex)i;
+ }
+ }
+ }
+
+ public bool Validate(int playerMin, int playerMax, ControllerType acceptedTypes, out int configuredCount, out PlayerIndex primaryIndex)
+ {
+ primaryIndex = PlayerIndex.Unknown;
+ configuredCount = 0;
+
+ for (int i = 0; i < MaxControllers; ++i)
+ {
+ ControllerType npad = _configuredTypes[i];
+
+ if (npad == ControllerType.Handheld && _device.System.State.DockedMode)
+ {
+ continue;
+ }
+
+ ControllerType currentType = (ControllerType)_device.Hid.SharedMemory.Npads[i].InternalState.StyleSet;
+
+ if (currentType != ControllerType.None && (npad & acceptedTypes) != 0 && _supportedPlayers[i])
+ {
+ configuredCount++;
+ if (primaryIndex == PlayerIndex.Unknown)
+ {
+ primaryIndex = (PlayerIndex)i;
+ }
+ }
+ }
+
+ if (configuredCount < playerMin || configuredCount > playerMax || primaryIndex == PlayerIndex.Unknown)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ public void Configure(params ControllerConfig[] configs)
+ {
+ _configuredTypes = new ControllerType[MaxControllers];
+
+ for (int i = 0; i < configs.Length; ++i)
+ {
+ PlayerIndex player = configs[i].Player;
+ ControllerType controllerType = configs[i].Type;
+
+ if (player > PlayerIndex.Handheld)
+ {
+ throw new ArgumentOutOfRangeException("Player must be Player1-8 or Handheld");
+ }
+
+ if (controllerType == ControllerType.Handheld)
+ {
+ player = PlayerIndex.Handheld;
+ }
+
+ _configuredTypes[(int)player] = controllerType;
+
+ Logger.Info?.Print(LogClass.Hid, $"Configured Controller {controllerType} to {player}");
+ }
+ }
+
+ public void Update(IList<GamepadInput> states)
+ {
+ Remap();
+
+ Span<bool> updated = stackalloc bool[10];
+
+ // Update configured inputs
+ for (int i = 0; i < states.Count; ++i)
+ {
+ GamepadInput state = states[i];
+
+ updated[(int)state.PlayerId] = true;
+
+ UpdateInput(state);
+ }
+
+ for (int i = 0; i < updated.Length; i++)
+ {
+ if (!updated[i])
+ {
+ UpdateDisconnectedInput((PlayerIndex)i);
+ }
+ }
+ }
+
+ private void Remap()
+ {
+ // Remap/Init if necessary
+ for (int i = 0; i < MaxControllers; ++i)
+ {
+ ControllerType config = _configuredTypes[i];
+
+ // Remove Handheld config when Docked
+ if (config == ControllerType.Handheld && _device.System.State.DockedMode)
+ {
+ config = ControllerType.None;
+ }
+
+ // Auto-remap ProController and JoyconPair
+ if (config == ControllerType.JoyconPair && (SupportedStyleSets & ControllerType.JoyconPair) == 0 && (SupportedStyleSets & ControllerType.ProController) != 0)
+ {
+ config = ControllerType.ProController;
+ }
+ else if (config == ControllerType.ProController && (SupportedStyleSets & ControllerType.ProController) == 0 && (SupportedStyleSets & ControllerType.JoyconPair) != 0)
+ {
+ config = ControllerType.JoyconPair;
+ }
+
+ // Check StyleSet and PlayerSet
+ if ((config & SupportedStyleSets) == 0 || !_supportedPlayers[i])
+ {
+ config = ControllerType.None;
+ }
+
+ SetupNpad((PlayerIndex)i, config);
+ }
+
+ if (_activeCount == 0 && PerformanceCounter.ElapsedMilliseconds > _lastNotifyTimestamp + NoMatchNotifyFrequencyMs)
+ {
+ Logger.Warning?.Print(LogClass.Hid, $"No matching controllers found. Application requests '{SupportedStyleSets}' on '{string.Join(", ", GetSupportedPlayers())}'");
+ _lastNotifyTimestamp = PerformanceCounter.ElapsedMilliseconds;
+ }
+ }
+
+ private void SetupNpad(PlayerIndex player, ControllerType type)
+ {
+ ref NpadInternalState controller = ref _device.Hid.SharedMemory.Npads[(int)player].InternalState;
+
+ ControllerType oldType = (ControllerType)controller.StyleSet;
+
+ if (oldType == type)
+ {
+ return; // Already configured
+ }
+
+ controller = NpadInternalState.Create(); // Reset it
+
+ if (type == ControllerType.None)
+ {
+ _styleSetUpdateEvents[(int)player].ReadableEvent.Signal(); // Signal disconnect
+ _activeCount--;
+
+ Logger.Info?.Print(LogClass.Hid, $"Disconnected Controller {oldType} from {player}");
+
+ return;
+ }
+
+ // TODO: Allow customizing colors at config
+ controller.JoyAssignmentMode = NpadJoyAssignmentMode.Dual;
+ controller.FullKeyColor.FullKeyBody = (uint)NpadColor.BodyGray;
+ controller.FullKeyColor.FullKeyButtons = (uint)NpadColor.ButtonGray;
+ controller.JoyColor.LeftBody = (uint)NpadColor.BodyNeonBlue;
+ controller.JoyColor.LeftButtons = (uint)NpadColor.ButtonGray;
+ controller.JoyColor.RightBody = (uint)NpadColor.BodyNeonRed;
+ controller.JoyColor.RightButtons = (uint)NpadColor.ButtonGray;
+
+ controller.SystemProperties = NpadSystemProperties.IsPoweredJoyDual |
+ NpadSystemProperties.IsPoweredJoyLeft |
+ NpadSystemProperties.IsPoweredJoyRight;
+
+ controller.BatteryLevelJoyDual = NpadBatteryLevel.Percent100;
+ controller.BatteryLevelJoyLeft = NpadBatteryLevel.Percent100;
+ controller.BatteryLevelJoyRight = NpadBatteryLevel.Percent100;
+
+ switch (type)
+ {
+ case ControllerType.ProController:
+ controller.StyleSet = NpadStyleTag.FullKey;
+ controller.DeviceType = DeviceType.FullKey;
+ controller.SystemProperties |= NpadSystemProperties.IsAbxyButtonOriented |
+ NpadSystemProperties.IsPlusAvailable |
+ NpadSystemProperties.IsMinusAvailable;
+ controller.AppletFooterUiType = AppletFooterUiType.SwitchProController;
+ break;
+ case ControllerType.Handheld:
+ controller.StyleSet = NpadStyleTag.Handheld;
+ controller.DeviceType = DeviceType.HandheldLeft |
+ DeviceType.HandheldRight;
+ controller.SystemProperties |= NpadSystemProperties.IsAbxyButtonOriented |
+ NpadSystemProperties.IsPlusAvailable |
+ NpadSystemProperties.IsMinusAvailable;
+ controller.AppletFooterUiType = AppletFooterUiType.HandheldJoyConLeftJoyConRight;
+ break;
+ case ControllerType.JoyconPair:
+ controller.StyleSet = NpadStyleTag.JoyDual;
+ controller.DeviceType = DeviceType.JoyLeft |
+ DeviceType.JoyRight;
+ controller.SystemProperties |= NpadSystemProperties.IsAbxyButtonOriented |
+ NpadSystemProperties.IsPlusAvailable |
+ NpadSystemProperties.IsMinusAvailable;
+ controller.AppletFooterUiType = _device.System.State.DockedMode ? AppletFooterUiType.JoyDual : AppletFooterUiType.HandheldJoyConLeftJoyConRight;
+ break;
+ case ControllerType.JoyconLeft:
+ controller.StyleSet = NpadStyleTag.JoyLeft;
+ controller.JoyAssignmentMode = NpadJoyAssignmentMode.Single;
+ controller.DeviceType = DeviceType.JoyLeft;
+ controller.SystemProperties |= NpadSystemProperties.IsSlSrButtonOriented |
+ NpadSystemProperties.IsMinusAvailable;
+ controller.AppletFooterUiType = _device.System.State.DockedMode ? AppletFooterUiType.JoyDualLeftOnly : AppletFooterUiType.HandheldJoyConLeftOnly;
+ break;
+ case ControllerType.JoyconRight:
+ controller.StyleSet = NpadStyleTag.JoyRight;
+ controller.JoyAssignmentMode = NpadJoyAssignmentMode.Single;
+ controller.DeviceType = DeviceType.JoyRight;
+ controller.SystemProperties |= NpadSystemProperties.IsSlSrButtonOriented |
+ NpadSystemProperties.IsPlusAvailable;
+ controller.AppletFooterUiType = _device.System.State.DockedMode ? AppletFooterUiType.JoyDualRightOnly : AppletFooterUiType.HandheldJoyConRightOnly;
+ break;
+ case ControllerType.Pokeball:
+ controller.StyleSet = NpadStyleTag.Palma;
+ controller.DeviceType = DeviceType.Palma;
+ controller.AppletFooterUiType = AppletFooterUiType.None;
+ break;
+ }
+
+ _styleSetUpdateEvents[(int)player].ReadableEvent.Signal();
+ _activeCount++;
+
+ Logger.Info?.Print(LogClass.Hid, $"Connected Controller {type} to {player}");
+ }
+
+ private ref RingLifo<NpadCommonState> GetCommonStateLifo(ref NpadInternalState npad)
+ {
+ switch (npad.StyleSet)
+ {
+ case NpadStyleTag.FullKey:
+ return ref npad.FullKey;
+ case NpadStyleTag.Handheld:
+ return ref npad.Handheld;
+ case NpadStyleTag.JoyDual:
+ return ref npad.JoyDual;
+ case NpadStyleTag.JoyLeft:
+ return ref npad.JoyLeft;
+ case NpadStyleTag.JoyRight:
+ return ref npad.JoyRight;
+ case NpadStyleTag.Palma:
+ return ref npad.Palma;
+ default:
+ return ref npad.SystemExt;
+ }
+ }
+
+ private void UpdateUnusedInputIfNotEqual(ref RingLifo<NpadCommonState> currentlyUsed, ref RingLifo<NpadCommonState> possiblyUnused)
+ {
+ if (!Unsafe.AreSame(ref currentlyUsed, ref possiblyUnused))
+ {
+ NpadCommonState newState = new NpadCommonState();
+
+ WriteNewInputEntry(ref possiblyUnused, ref newState);
+ }
+ }
+
+ private void WriteNewInputEntry(ref RingLifo<NpadCommonState> lifo, ref NpadCommonState state)
+ {
+ ref NpadCommonState previousEntry = ref lifo.GetCurrentEntryRef();
+
+ state.SamplingNumber = previousEntry.SamplingNumber + 1;
+
+ lifo.Write(ref state);
+ }
+
+ private void UpdateUnusedSixInputIfNotEqual(ref RingLifo<SixAxisSensorState> currentlyUsed, ref RingLifo<SixAxisSensorState> possiblyUnused)
+ {
+ if (!Unsafe.AreSame(ref currentlyUsed, ref possiblyUnused))
+ {
+ SixAxisSensorState newState = new SixAxisSensorState();
+
+ WriteNewSixInputEntry(ref possiblyUnused, ref newState);
+ }
+ }
+
+ private void WriteNewSixInputEntry(ref RingLifo<SixAxisSensorState> lifo, ref SixAxisSensorState state)
+ {
+ ref SixAxisSensorState previousEntry = ref lifo.GetCurrentEntryRef();
+
+ state.SamplingNumber = previousEntry.SamplingNumber + 1;
+
+ lifo.Write(ref state);
+ }
+
+ private void UpdateInput(GamepadInput state)
+ {
+ if (state.PlayerId == PlayerIndex.Unknown)
+ {
+ return;
+ }
+
+ ref NpadInternalState currentNpad = ref _device.Hid.SharedMemory.Npads[(int)state.PlayerId].InternalState;
+
+ if (currentNpad.StyleSet == NpadStyleTag.None)
+ {
+ return;
+ }
+
+ ref RingLifo<NpadCommonState> lifo = ref GetCommonStateLifo(ref currentNpad);
+
+ NpadCommonState newState = new NpadCommonState
+ {
+ Buttons = (NpadButton)state.Buttons,
+ AnalogStickL = new AnalogStickState
+ {
+ X = state.LStick.Dx,
+ Y = state.LStick.Dy,
+ },
+ AnalogStickR = new AnalogStickState
+ {
+ X = state.RStick.Dx,
+ Y = state.RStick.Dy,
+ }
+ };
+
+ newState.Attributes = NpadAttribute.IsConnected;
+
+ switch (currentNpad.StyleSet)
+ {
+ case NpadStyleTag.Handheld:
+ case NpadStyleTag.FullKey:
+ newState.Attributes |= NpadAttribute.IsWired;
+ break;
+ case NpadStyleTag.JoyDual:
+ newState.Attributes |= NpadAttribute.IsLeftConnected |
+ NpadAttribute.IsRightConnected;
+ break;
+ case NpadStyleTag.JoyLeft:
+ newState.Attributes |= NpadAttribute.IsLeftConnected;
+ break;
+ case NpadStyleTag.JoyRight:
+ newState.Attributes |= NpadAttribute.IsRightConnected;
+ break;
+ }
+
+ WriteNewInputEntry(ref lifo, ref newState);
+
+ // Mirror data to Default layout just in case
+ if (!currentNpad.StyleSet.HasFlag(NpadStyleTag.SystemExt))
+ {
+ WriteNewInputEntry(ref currentNpad.SystemExt, ref newState);
+ }
+
+ UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.FullKey);
+ UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.Handheld);
+ UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.JoyDual);
+ UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.JoyLeft);
+ UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.JoyRight);
+ UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.Palma);
+ }
+
+ private void UpdateDisconnectedInput(PlayerIndex index)
+ {
+ ref NpadInternalState currentNpad = ref _device.Hid.SharedMemory.Npads[(int)index].InternalState;
+
+ NpadCommonState newState = new NpadCommonState();
+
+ WriteNewInputEntry(ref currentNpad.FullKey, ref newState);
+ WriteNewInputEntry(ref currentNpad.Handheld, ref newState);
+ WriteNewInputEntry(ref currentNpad.JoyDual, ref newState);
+ WriteNewInputEntry(ref currentNpad.JoyLeft, ref newState);
+ WriteNewInputEntry(ref currentNpad.JoyRight, ref newState);
+ WriteNewInputEntry(ref currentNpad.Palma, ref newState);
+ }
+
+ public void UpdateSixAxis(IList<SixAxisInput> states)
+ {
+ Span<bool> updated = stackalloc bool[10];
+
+ for (int i = 0; i < states.Count; ++i)
+ {
+ updated[(int)states[i].PlayerId] = true;
+
+ if (SetSixAxisState(states[i]))
+ {
+ i++;
+
+ if (i >= states.Count)
+ {
+ return;
+ }
+
+ SetSixAxisState(states[i], true);
+ }
+ }
+
+ for (int i = 0; i < updated.Length; i++)
+ {
+ if (!updated[i])
+ {
+ UpdateDisconnectedInputSixAxis((PlayerIndex)i);
+ }
+ }
+ }
+
+ private ref RingLifo<SixAxisSensorState> GetSixAxisSensorLifo(ref NpadInternalState npad, bool isRightPair)
+ {
+ switch (npad.StyleSet)
+ {
+ case NpadStyleTag.FullKey:
+ return ref npad.FullKeySixAxisSensor;
+ case NpadStyleTag.Handheld:
+ return ref npad.HandheldSixAxisSensor;
+ case NpadStyleTag.JoyDual:
+ if (isRightPair)
+ {
+ return ref npad.JoyDualRightSixAxisSensor;
+ }
+ else
+ {
+ return ref npad.JoyDualSixAxisSensor;
+ }
+ case NpadStyleTag.JoyLeft:
+ return ref npad.JoyLeftSixAxisSensor;
+ case NpadStyleTag.JoyRight:
+ return ref npad.JoyRightSixAxisSensor;
+ default:
+ throw new NotImplementedException($"{npad.StyleSet}");
+ }
+ }
+
+ private bool SetSixAxisState(SixAxisInput state, bool isRightPair = false)
+ {
+ if (state.PlayerId == PlayerIndex.Unknown)
+ {
+ return false;
+ }
+
+ ref NpadInternalState currentNpad = ref _device.Hid.SharedMemory.Npads[(int)state.PlayerId].InternalState;
+
+ if (currentNpad.StyleSet == NpadStyleTag.None)
+ {
+ return false;
+ }
+
+ HidVector accel = new HidVector()
+ {
+ X = state.Accelerometer.X,
+ Y = state.Accelerometer.Y,
+ Z = state.Accelerometer.Z
+ };
+
+ HidVector gyro = new HidVector()
+ {
+ X = state.Gyroscope.X,
+ Y = state.Gyroscope.Y,
+ Z = state.Gyroscope.Z
+ };
+
+ HidVector rotation = new HidVector()
+ {
+ X = state.Rotation.X,
+ Y = state.Rotation.Y,
+ Z = state.Rotation.Z
+ };
+
+ SixAxisSensorState newState = new SixAxisSensorState
+ {
+ Acceleration = accel,
+ AngularVelocity = gyro,
+ Angle = rotation,
+ Attributes = SixAxisSensorAttribute.IsConnected
+ };
+
+ state.Orientation.AsSpan().CopyTo(newState.Direction.AsSpan());
+
+ ref RingLifo<SixAxisSensorState> lifo = ref GetSixAxisSensorLifo(ref currentNpad, isRightPair);
+
+ WriteNewSixInputEntry(ref lifo, ref newState);
+
+ bool needUpdateRight = currentNpad.StyleSet == NpadStyleTag.JoyDual && !isRightPair;
+
+ if (!isRightPair)
+ {
+ UpdateUnusedSixInputIfNotEqual(ref lifo, ref currentNpad.FullKeySixAxisSensor);
+ UpdateUnusedSixInputIfNotEqual(ref lifo, ref currentNpad.HandheldSixAxisSensor);
+ UpdateUnusedSixInputIfNotEqual(ref lifo, ref currentNpad.JoyDualSixAxisSensor);
+ UpdateUnusedSixInputIfNotEqual(ref lifo, ref currentNpad.JoyLeftSixAxisSensor);
+ UpdateUnusedSixInputIfNotEqual(ref lifo, ref currentNpad.JoyRightSixAxisSensor);
+ }
+
+ if (!needUpdateRight && !isRightPair)
+ {
+ SixAxisSensorState emptyState = new SixAxisSensorState();
+
+ emptyState.Attributes = SixAxisSensorAttribute.IsConnected;
+
+ WriteNewSixInputEntry(ref currentNpad.JoyDualRightSixAxisSensor, ref emptyState);
+ }
+
+ return needUpdateRight;
+ }
+
+ private void UpdateDisconnectedInputSixAxis(PlayerIndex index)
+ {
+ ref NpadInternalState currentNpad = ref _device.Hid.SharedMemory.Npads[(int)index].InternalState;
+
+ SixAxisSensorState newState = new SixAxisSensorState();
+
+ newState.Attributes = SixAxisSensorAttribute.IsConnected;
+
+ WriteNewSixInputEntry(ref currentNpad.FullKeySixAxisSensor, ref newState);
+ WriteNewSixInputEntry(ref currentNpad.HandheldSixAxisSensor, ref newState);
+ WriteNewSixInputEntry(ref currentNpad.JoyDualSixAxisSensor, ref newState);
+ WriteNewSixInputEntry(ref currentNpad.JoyDualRightSixAxisSensor, ref newState);
+ WriteNewSixInputEntry(ref currentNpad.JoyLeftSixAxisSensor, ref newState);
+ WriteNewSixInputEntry(ref currentNpad.JoyRightSixAxisSensor, ref newState);
+ }
+
+ public void UpdateRumbleQueue(PlayerIndex index, Dictionary<byte, VibrationValue> dualVibrationValues)
+ {
+ if (RumbleQueues.TryGetValue(index, out ConcurrentQueue<(VibrationValue, VibrationValue)> currentQueue))
+ {
+ if (!dualVibrationValues.TryGetValue(0, out VibrationValue leftVibrationValue))
+ {
+ leftVibrationValue = _neutralVibrationValue;
+ }
+
+ if (!dualVibrationValues.TryGetValue(1, out VibrationValue rightVibrationValue))
+ {
+ rightVibrationValue = _neutralVibrationValue;
+ }
+
+ if (!LastVibrationValues.TryGetValue(index, out (VibrationValue, VibrationValue) dualVibrationValue) || !leftVibrationValue.Equals(dualVibrationValue.Item1) || !rightVibrationValue.Equals(dualVibrationValue.Item2))
+ {
+ currentQueue.Enqueue((leftVibrationValue, rightVibrationValue));
+
+ LastVibrationValues[index] = (leftVibrationValue, rightVibrationValue);
+ }
+ }
+ }
+
+ public VibrationValue GetLastVibrationValue(PlayerIndex index, byte position)
+ {
+ if (!LastVibrationValues.TryGetValue(index, out (VibrationValue, VibrationValue) dualVibrationValue))
+ {
+ return _neutralVibrationValue;
+ }
+
+ return (position == 0) ? dualVibrationValue.Item1 : dualVibrationValue.Item2;
+ }
+
+ public ConcurrentQueue<(VibrationValue, VibrationValue)> GetRumbleQueue(PlayerIndex index)
+ {
+ if (!RumbleQueues.TryGetValue(index, out ConcurrentQueue<(VibrationValue, VibrationValue)> rumbleQueue))
+ {
+ rumbleQueue = new ConcurrentQueue<(VibrationValue, VibrationValue)>();
+ _device.Hid.Npads.RumbleQueues[index] = rumbleQueue;
+ }
+
+ return rumbleQueue;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/TouchDevice.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/TouchDevice.cs
new file mode 100644
index 00000000..bb58ee51
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/TouchDevice.cs
@@ -0,0 +1,48 @@
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.TouchScreen;
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ public class TouchDevice : BaseDevice
+ {
+ public TouchDevice(Switch device, bool active) : base(device, active) { }
+
+ public void Update(params TouchPoint[] points)
+ {
+ ref RingLifo<TouchScreenState> lifo = ref _device.Hid.SharedMemory.TouchScreen;
+
+ ref TouchScreenState previousEntry = ref lifo.GetCurrentEntryRef();
+
+ TouchScreenState newState = new TouchScreenState
+ {
+ SamplingNumber = previousEntry.SamplingNumber + 1
+ };
+
+ if (Active)
+ {
+ newState.TouchesCount = points.Length;
+
+ int pointsLength = Math.Min(points.Length, newState.Touches.Length);
+
+ for (int i = 0; i < pointsLength; ++i)
+ {
+ TouchPoint pi = points[i];
+ newState.Touches[i] = new TouchState
+ {
+ DeltaTime = newState.SamplingNumber,
+ Attribute = pi.Attribute,
+ X = pi.X,
+ Y = pi.Y,
+ FingerId = (uint)i,
+ DiameterX = pi.DiameterX,
+ DiameterY = pi.DiameterY,
+ RotationAngle = pi.Angle
+ };
+ }
+ }
+
+ lifo.Write(ref newState);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/ControllerConfig.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/ControllerConfig.cs
new file mode 100644
index 00000000..477e1a84
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/ControllerConfig.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ public struct ControllerConfig
+ {
+ public PlayerIndex Player;
+ public ControllerType Type;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/GamepadInput.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/GamepadInput.cs
new file mode 100644
index 00000000..633671df
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/GamepadInput.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ public struct GamepadInput
+ {
+ public PlayerIndex PlayerId;
+ public ControllerKeys Buttons;
+ public JoystickPosition LStick;
+ public JoystickPosition RStick;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/JoystickPosition.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/JoystickPosition.cs
new file mode 100644
index 00000000..6df477d6
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/JoystickPosition.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ public struct JoystickPosition
+ {
+ public int Dx;
+ public int Dy;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/KeyboardInput.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/KeyboardInput.cs
new file mode 100644
index 00000000..be6857fb
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/KeyboardInput.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ public struct KeyboardInput
+ {
+ public int Modifier;
+ public ulong[] Keys;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/SixAxisInput.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/SixAxisInput.cs
new file mode 100644
index 00000000..4dda82c7
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/SixAxisInput.cs
@@ -0,0 +1,13 @@
+using System.Numerics;
+
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ public struct SixAxisInput
+ {
+ public PlayerIndex PlayerId;
+ public Vector3 Accelerometer;
+ public Vector3 Gyroscope;
+ public Vector3 Rotation;
+ public float[] Orientation;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/TouchPoint.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/TouchPoint.cs
new file mode 100644
index 00000000..457d2b0d
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidDevices/Types/TouchPoint.cs
@@ -0,0 +1,14 @@
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.TouchScreen;
+
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ public struct TouchPoint
+ {
+ public TouchAttribute Attribute;
+ public uint X;
+ public uint Y;
+ public uint DiameterX;
+ public uint DiameterY;
+ public uint Angle;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/HidUtils.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/HidUtils.cs
new file mode 100644
index 00000000..b98f6065
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/HidUtils.cs
@@ -0,0 +1,46 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.HidServer
+{
+ static class HidUtils
+ {
+ public static PlayerIndex GetIndexFromNpadIdType(NpadIdType npadIdType)
+ => npadIdType switch
+ {
+ NpadIdType.Player1 => PlayerIndex.Player1,
+ NpadIdType.Player2 => PlayerIndex.Player2,
+ NpadIdType.Player3 => PlayerIndex.Player3,
+ NpadIdType.Player4 => PlayerIndex.Player4,
+ NpadIdType.Player5 => PlayerIndex.Player5,
+ NpadIdType.Player6 => PlayerIndex.Player6,
+ NpadIdType.Player7 => PlayerIndex.Player7,
+ NpadIdType.Player8 => PlayerIndex.Player8,
+ NpadIdType.Handheld => PlayerIndex.Handheld,
+ NpadIdType.Unknown => PlayerIndex.Unknown,
+ _ => throw new ArgumentOutOfRangeException(nameof(npadIdType))
+ };
+
+ public static NpadIdType GetNpadIdTypeFromIndex(PlayerIndex index)
+ => index switch
+ {
+ PlayerIndex.Player1 => NpadIdType.Player1,
+ PlayerIndex.Player2 => NpadIdType.Player2,
+ PlayerIndex.Player3 => NpadIdType.Player3,
+ PlayerIndex.Player4 => NpadIdType.Player4,
+ PlayerIndex.Player5 => NpadIdType.Player5,
+ PlayerIndex.Player6 => NpadIdType.Player6,
+ PlayerIndex.Player7 => NpadIdType.Player7,
+ PlayerIndex.Player8 => NpadIdType.Player8,
+ PlayerIndex.Handheld => NpadIdType.Handheld,
+ PlayerIndex.Unknown => NpadIdType.Unknown,
+ _ => throw new ArgumentOutOfRangeException(nameof(index))
+ };
+
+ public static bool IsValidNpadIdType(NpadIdType npadIdType)
+ {
+ return (npadIdType >= NpadIdType.Player1 && npadIdType <= NpadIdType.Player8) ||
+ npadIdType == NpadIdType.Handheld ||
+ npadIdType == NpadIdType.Unknown;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/IActiveVibrationDeviceList.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/IActiveVibrationDeviceList.cs
new file mode 100644
index 00000000..56f63e52
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/IActiveVibrationDeviceList.cs
@@ -0,0 +1,16 @@
+namespace Ryujinx.HLE.HOS.Services.Hid.HidServer
+{
+ class IActiveApplicationDeviceList : IpcService
+ {
+ public IActiveApplicationDeviceList() { }
+
+ [CommandCmif(0)]
+ // ActivateVibrationDevice(nn::hid::VibrationDeviceHandle)
+ public ResultCode ActivateVibrationDevice(ServiceCtx context)
+ {
+ int vibrationDeviceHandle = context.RequestData.ReadInt32();
+
+ return ResultCode.Success;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/IAppletResource.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/IAppletResource.cs
new file mode 100644
index 00000000..f0aaf5e3
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/IAppletResource.cs
@@ -0,0 +1,35 @@
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel.Memory;
+using Ryujinx.Horizon.Common;
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.HidServer
+{
+ class IAppletResource : IpcService
+ {
+ private KSharedMemory _hidSharedMem;
+ private int _hidSharedMemHandle;
+
+ public IAppletResource(KSharedMemory hidSharedMem)
+ {
+ _hidSharedMem = hidSharedMem;
+ }
+
+ [CommandCmif(0)]
+ // GetSharedMemoryHandle() -> handle<copy>
+ public ResultCode GetSharedMemoryHandle(ServiceCtx context)
+ {
+ if (_hidSharedMemHandle == 0)
+ {
+ if (context.Process.HandleTable.GenerateHandle(_hidSharedMem, out _hidSharedMemHandle) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_hidSharedMemHandle);
+
+ return ResultCode.Success;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/NpadHandheldActivationMode.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/NpadHandheldActivationMode.cs
new file mode 100644
index 00000000..0cf4a047
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/NpadHandheldActivationMode.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ public enum NpadHandheldActivationMode
+ {
+ Dual,
+ Single,
+ None
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/NpadJoyDeviceType.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/NpadJoyDeviceType.cs
new file mode 100644
index 00000000..05587bfd
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Npad/NpadJoyDeviceType.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ public enum NpadJoyDeviceType
+ {
+ Left,
+ Right
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/AccelerometerParameters.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/AccelerometerParameters.cs
new file mode 100644
index 00000000..4fd0a1b5
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/AccelerometerParameters.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ public struct AccelerometerParameters
+ {
+ public float X;
+ public float Y;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/GyroscopeZeroDriftMode.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/GyroscopeZeroDriftMode.cs
new file mode 100644
index 00000000..db7467fa
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/GyroscopeZeroDriftMode.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ public enum GyroscopeZeroDriftMode
+ {
+ Loose,
+ Standard,
+ Tight
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/SensorFusionParameters.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/SensorFusionParameters.cs
new file mode 100644
index 00000000..2683ffee
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/SixAxis/SensorFusionParameters.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ public struct SensorFusionParameters
+ {
+ public float RevisePower;
+ public float ReviseRange;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceHandle.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceHandle.cs
new file mode 100644
index 00000000..fe50e671
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceHandle.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ public struct VibrationDeviceHandle
+ {
+ public byte DeviceType;
+ public byte PlayerId;
+ public byte Position;
+ public byte Reserved;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDevicePosition.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDevicePosition.cs
new file mode 100644
index 00000000..117451f1
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDevicePosition.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ public enum VibrationDevicePosition
+ {
+ None,
+ Left,
+ Right
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceType.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceType.cs
new file mode 100644
index 00000000..4e5557c9
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceType.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ public enum VibrationDeviceType
+ {
+ None,
+ LinearResonantActuator,
+ GcErm
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceValue.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceValue.cs
new file mode 100644
index 00000000..91a23eb7
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationDeviceValue.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ public struct VibrationDeviceValue
+ {
+ public VibrationDeviceType DeviceType;
+ public VibrationDevicePosition Position;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationValue.cs b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationValue.cs
new file mode 100644
index 00000000..38ac9cca
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/HidServer/Types/Vibration/VibrationValue.cs
@@ -0,0 +1,24 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ public struct VibrationValue
+ {
+ public float AmplitudeLow;
+ public float FrequencyLow;
+ public float AmplitudeHigh;
+ public float FrequencyHigh;
+
+ public override bool Equals(object obj)
+ {
+ return obj is VibrationValue 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/src/Ryujinx.HLE/HOS/Services/Hid/IHidDebugServer.cs b/src/Ryujinx.HLE/HOS/Services/Hid/IHidDebugServer.cs
new file mode 100644
index 00000000..adaaa012
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/IHidDebugServer.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ [Service("hid:dbg")]
+ class IHidDebugServer : IpcService
+ {
+ public IHidDebugServer(ServiceCtx context) { }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs b/src/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs
new file mode 100644
index 00000000..d508aba4
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/IHidServer.cs
@@ -0,0 +1,1800 @@
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Ipc;
+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 Ryujinx.Horizon.Common;
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ [Service("hid")]
+ class IHidServer : IpcService
+ {
+ private KEvent _xpadIdEvent;
+ private KEvent _palmaOperationCompleteEvent;
+
+ private int _xpadIdEventHandle;
+
+ private bool _sixAxisSensorFusionEnabled;
+ private bool _unintendedHomeButtonInputProtectionEnabled;
+ private bool _vibrationPermitted;
+ private bool _usbFullKeyControllerEnabled;
+ private bool _isFirmwareUpdateAvailableForSixAxisSensor;
+ private bool _isSixAxisSensorUnalteredPassthroughEnabled;
+
+ private NpadHandheldActivationMode _npadHandheldActivationMode;
+ private GyroscopeZeroDriftMode _gyroscopeZeroDriftMode;
+
+ private long _npadCommunicationMode;
+ private uint _accelerometerPlayMode;
+#pragma warning disable CS0649
+ private long _vibrationGcErmCommand;
+#pragma warning restore CS0649
+ private float _sevenSixAxisSensorFusionStrength;
+
+ private SensorFusionParameters _sensorFusionParams;
+ private AccelerometerParameters _accelerometerParams;
+
+ public IHidServer(ServiceCtx context) : base(context.Device.System.HidServer)
+ {
+ _xpadIdEvent = new KEvent(context.Device.System.KernelContext);
+ _palmaOperationCompleteEvent = new KEvent(context.Device.System.KernelContext);
+
+ _npadHandheldActivationMode = NpadHandheldActivationMode.Dual;
+ _gyroscopeZeroDriftMode = GyroscopeZeroDriftMode.Standard;
+
+ _isFirmwareUpdateAvailableForSixAxisSensor = false;
+
+ _sensorFusionParams = new SensorFusionParameters();
+ _accelerometerParams = new AccelerometerParameters();
+
+ // TODO: signal event at right place
+ _xpadIdEvent.ReadableEvent.Signal();
+
+ _vibrationPermitted = true;
+ }
+
+ [CommandCmif(0)]
+ // CreateAppletResource(nn::applet::AppletResourceUserId) -> object<nn::hid::IAppletResource>
+ public ResultCode CreateAppletResource(ServiceCtx context)
+ {
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ MakeObject(context, new IAppletResource(context.Device.System.HidSharedMem));
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(1)]
+ // ActivateDebugPad(nn::applet::AppletResourceUserId)
+ public ResultCode ActivateDebugPad(ServiceCtx context)
+ {
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ // Initialize entries to avoid issues with some games.
+
+ for (int entry = 0; entry < Hid.SharedMemEntryCount; entry++)
+ {
+ context.Device.Hid.DebugPad.Update();
+ }
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(11)]
+ // ActivateTouchScreen(nn::applet::AppletResourceUserId)
+ public ResultCode ActivateTouchScreen(ServiceCtx context)
+ {
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ context.Device.Hid.Touchscreen.Active = true;
+
+ // Initialize entries to avoid issues with some games.
+
+ for (int entry = 0; entry < Hid.SharedMemEntryCount; entry++)
+ {
+ context.Device.Hid.Touchscreen.Update();
+ }
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(21)]
+ // ActivateMouse(nn::applet::AppletResourceUserId)
+ public ResultCode ActivateMouse(ServiceCtx context)
+ {
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ context.Device.Hid.Mouse.Active = true;
+
+ // Initialize entries to avoid issues with some games.
+
+ for (int entry = 0; entry < Hid.SharedMemEntryCount; entry++)
+ {
+ context.Device.Hid.Mouse.Update(0, 0);
+ }
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(31)]
+ // ActivateKeyboard(nn::applet::AppletResourceUserId)
+ public ResultCode ActivateKeyboard(ServiceCtx context)
+ {
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ context.Device.Hid.Keyboard.Active = true;
+
+ // Initialize entries to avoid issues with some games.
+
+ KeyboardInput emptyInput = new KeyboardInput();
+ emptyInput.Keys = new ulong[4];
+
+ for (int entry = 0; entry < Hid.SharedMemEntryCount; entry++)
+ {
+ context.Device.Hid.Keyboard.Update(emptyInput);
+ }
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(32)]
+ // SendKeyboardLockKeyEvent(uint flags, pid)
+ public ResultCode SendKeyboardLockKeyEvent(ServiceCtx context)
+ {
+ uint flags = context.RequestData.ReadUInt32();
+
+ // NOTE: This signal the keyboard driver about lock events.
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { flags });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(40)]
+ // AcquireXpadIdEventHandle(ulong XpadId) -> nn::sf::NativeHandle
+ public ResultCode AcquireXpadIdEventHandle(ServiceCtx context)
+ {
+ long xpadId = context.RequestData.ReadInt64();
+
+ if (context.Process.HandleTable.GenerateHandle(_xpadIdEvent.ReadableEvent, out _xpadIdEventHandle) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_xpadIdEventHandle);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { xpadId });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(41)]
+ // ReleaseXpadIdEventHandle(ulong XpadId)
+ public ResultCode ReleaseXpadIdEventHandle(ServiceCtx context)
+ {
+ long xpadId = context.RequestData.ReadInt64();
+
+ context.Process.HandleTable.CloseHandle(_xpadIdEventHandle);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { xpadId });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(51)]
+ // ActivateXpad(nn::hid::BasicXpadId, nn::applet::AppletResourceUserId)
+ public ResultCode ActivateXpad(ServiceCtx context)
+ {
+ int basicXpadId = context.RequestData.ReadInt32();
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, basicXpadId });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(55)]
+ // GetXpadIds() -> long IdsCount, buffer<array<nn::hid::BasicXpadId>, type: 0xa>
+ public ResultCode GetXpadIds(ServiceCtx context)
+ {
+ // There is any Xpad, so we return 0 and write nothing inside the type-0xa buffer.
+ context.ResponseData.Write(0L);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(56)]
+ // ActivateJoyXpad(nn::hid::JoyXpadId)
+ public ResultCode ActivateJoyXpad(ServiceCtx context)
+ {
+ int joyXpadId = context.RequestData.ReadInt32();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { joyXpadId });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(58)]
+ // GetJoyXpadLifoHandle(nn::hid::JoyXpadId) -> nn::sf::NativeHandle
+ public ResultCode GetJoyXpadLifoHandle(ServiceCtx context)
+ {
+ int joyXpadId = context.RequestData.ReadInt32();
+
+ int handle = 0;
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { joyXpadId });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(59)]
+ // GetJoyXpadIds() -> long IdsCount, buffer<array<nn::hid::JoyXpadId>, type: 0xa>
+ public ResultCode GetJoyXpadIds(ServiceCtx context)
+ {
+ // There is any JoyXpad, so we return 0 and write nothing inside the type-0xa buffer.
+ context.ResponseData.Write(0L);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(60)]
+ // ActivateSixAxisSensor(nn::hid::BasicXpadId)
+ public ResultCode ActivateSixAxisSensor(ServiceCtx context)
+ {
+ int basicXpadId = context.RequestData.ReadInt32();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { basicXpadId });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(61)]
+ // DeactivateSixAxisSensor(nn::hid::BasicXpadId)
+ public ResultCode DeactivateSixAxisSensor(ServiceCtx context)
+ {
+ int basicXpadId = context.RequestData.ReadInt32();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { basicXpadId });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(62)]
+ // GetSixAxisSensorLifoHandle(nn::hid::BasicXpadId) -> nn::sf::NativeHandle
+ public ResultCode GetSixAxisSensorLifoHandle(ServiceCtx context)
+ {
+ int basicXpadId = context.RequestData.ReadInt32();
+
+ int handle = 0;
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { basicXpadId });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(63)]
+ // ActivateJoySixAxisSensor(nn::hid::JoyXpadId)
+ public ResultCode ActivateJoySixAxisSensor(ServiceCtx context)
+ {
+ int joyXpadId = context.RequestData.ReadInt32();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { joyXpadId });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(64)]
+ // DeactivateJoySixAxisSensor(nn::hid::JoyXpadId)
+ public ResultCode DeactivateJoySixAxisSensor(ServiceCtx context)
+ {
+ int joyXpadId = context.RequestData.ReadInt32();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { joyXpadId });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(65)]
+ // GetJoySixAxisSensorLifoHandle(nn::hid::JoyXpadId) -> nn::sf::NativeHandle
+ public ResultCode GetJoySixAxisSensorLifoHandle(ServiceCtx context)
+ {
+ int joyXpadId = context.RequestData.ReadInt32();
+
+ int handle = 0;
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { joyXpadId });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(66)]
+ // StartSixAxisSensor(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId)
+ public ResultCode StartSixAxisSensor(ServiceCtx context)
+ {
+ int sixAxisSensorHandle = context.RequestData.ReadInt32();
+ context.RequestData.BaseStream.Position += 4; // Padding
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(67)]
+ // StopSixAxisSensor(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId)
+ public ResultCode StopSixAxisSensor(ServiceCtx context)
+ {
+ int sixAxisSensorHandle = context.RequestData.ReadInt32();
+ context.RequestData.BaseStream.Position += 4; // Padding
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(68)]
+ // IsSixAxisSensorFusionEnabled(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> bool IsEnabled
+ public ResultCode IsSixAxisSensorFusionEnabled(ServiceCtx context)
+ {
+ int sixAxisSensorHandle = context.RequestData.ReadInt32();
+ context.RequestData.BaseStream.Position += 4; // Padding
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ context.ResponseData.Write(_sixAxisSensorFusionEnabled);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _sixAxisSensorFusionEnabled });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(69)]
+ // EnableSixAxisSensorFusion(bool Enabled, nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId)
+ public ResultCode EnableSixAxisSensorFusion(ServiceCtx context)
+ {
+ _sixAxisSensorFusionEnabled = context.RequestData.ReadUInt32() != 0;
+ int sixAxisSensorHandle = context.RequestData.ReadInt32();
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _sixAxisSensorFusionEnabled });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(70)]
+ // SetSixAxisSensorFusionParameters(nn::hid::SixAxisSensorHandle, float RevisePower, float ReviseRange, nn::applet::AppletResourceUserId)
+ public ResultCode SetSixAxisSensorFusionParameters(ServiceCtx context)
+ {
+ int sixAxisSensorHandle = context.RequestData.ReadInt32();
+ context.RequestData.BaseStream.Position += 4; // Padding
+
+ _sensorFusionParams = new SensorFusionParameters
+ {
+ RevisePower = context.RequestData.ReadInt32(),
+ ReviseRange = context.RequestData.ReadInt32()
+ };
+
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _sensorFusionParams.RevisePower, _sensorFusionParams.ReviseRange });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(71)]
+ // GetSixAxisSensorFusionParameters(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> float RevisePower, float ReviseRange)
+ public ResultCode GetSixAxisSensorFusionParameters(ServiceCtx context)
+ {
+ int sixAxisSensorHandle = context.RequestData.ReadInt32();
+ context.RequestData.BaseStream.Position += 4; // Padding
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ context.ResponseData.Write(_sensorFusionParams.RevisePower);
+ context.ResponseData.Write(_sensorFusionParams.ReviseRange);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _sensorFusionParams.RevisePower, _sensorFusionParams.ReviseRange });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(72)]
+ // ResetSixAxisSensorFusionParameters(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId)
+ public ResultCode ResetSixAxisSensorFusionParameters(ServiceCtx context)
+ {
+ int sixAxisSensorHandle = context.RequestData.ReadInt32();
+ context.RequestData.BaseStream.Position += 4; // Padding
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ _sensorFusionParams.RevisePower = 0;
+ _sensorFusionParams.ReviseRange = 0;
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _sensorFusionParams.RevisePower, _sensorFusionParams.ReviseRange });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(73)]
+ // SetAccelerometerParameters(nn::hid::SixAxisSensorHandle, float X, float Y, nn::applet::AppletResourceUserId)
+ public ResultCode SetAccelerometerParameters(ServiceCtx context)
+ {
+ int sixAxisSensorHandle = context.RequestData.ReadInt32();
+ context.RequestData.BaseStream.Position += 4; // Padding
+
+ _accelerometerParams = new AccelerometerParameters
+ {
+ X = context.RequestData.ReadInt32(),
+ Y = context.RequestData.ReadInt32()
+ };
+
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _accelerometerParams.X, _accelerometerParams.Y });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(74)]
+ // GetAccelerometerParameters(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> float X, float Y
+ public ResultCode GetAccelerometerParameters(ServiceCtx context)
+ {
+ int sixAxisSensorHandle = context.RequestData.ReadInt32();
+ context.RequestData.BaseStream.Position += 4; // Padding
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ context.ResponseData.Write(_accelerometerParams.X);
+ context.ResponseData.Write(_accelerometerParams.Y);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _accelerometerParams.X, _accelerometerParams.Y });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(75)]
+ // ResetAccelerometerParameters(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId)
+ public ResultCode ResetAccelerometerParameters(ServiceCtx context)
+ {
+ int sixAxisSensorHandle = context.RequestData.ReadInt32();
+ context.RequestData.BaseStream.Position += 4; // Padding
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ _accelerometerParams.X = 0;
+ _accelerometerParams.Y = 0;
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _accelerometerParams.X, _accelerometerParams.Y });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(76)]
+ // SetAccelerometerPlayMode(nn::hid::SixAxisSensorHandle, uint PlayMode, nn::applet::AppletResourceUserId)
+ public ResultCode SetAccelerometerPlayMode(ServiceCtx context)
+ {
+ int sixAxisSensorHandle = context.RequestData.ReadInt32();
+ context.RequestData.BaseStream.Position += 4; // Padding
+ _accelerometerPlayMode = context.RequestData.ReadUInt32();
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _accelerometerPlayMode });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(77)]
+ // GetAccelerometerPlayMode(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> uint PlayMode
+ public ResultCode GetAccelerometerPlayMode(ServiceCtx context)
+ {
+ int sixAxisSensorHandle = context.RequestData.ReadInt32();
+ context.RequestData.BaseStream.Position += 4; // Padding
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ context.ResponseData.Write(_accelerometerPlayMode);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _accelerometerPlayMode });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(78)]
+ // ResetAccelerometerPlayMode(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId)
+ public ResultCode ResetAccelerometerPlayMode(ServiceCtx context)
+ {
+ int sixAxisSensorHandle = context.RequestData.ReadInt32();
+ context.RequestData.BaseStream.Position += 4; // Padding
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ _accelerometerPlayMode = 0;
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _accelerometerPlayMode });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(79)]
+ // SetGyroscopeZeroDriftMode(nn::hid::SixAxisSensorHandle, uint GyroscopeZeroDriftMode, nn::applet::AppletResourceUserId)
+ public ResultCode SetGyroscopeZeroDriftMode(ServiceCtx context)
+ {
+ int sixAxisSensorHandle = context.RequestData.ReadInt32();
+ _gyroscopeZeroDriftMode = (GyroscopeZeroDriftMode)context.RequestData.ReadInt32();
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _gyroscopeZeroDriftMode });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(80)]
+ // GetGyroscopeZeroDriftMode(nn::applet::AppletResourceUserId, nn::hid::SixAxisSensorHandle) -> int GyroscopeZeroDriftMode
+ public ResultCode GetGyroscopeZeroDriftMode(ServiceCtx context)
+ {
+ int sixAxisSensorHandle = context.RequestData.ReadInt32();
+ context.RequestData.BaseStream.Position += 4; // Padding
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ context.ResponseData.Write((int)_gyroscopeZeroDriftMode);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _gyroscopeZeroDriftMode });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(81)]
+ // ResetGyroscopeZeroDriftMode(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId)
+ public ResultCode ResetGyroscopeZeroDriftMode(ServiceCtx context)
+ {
+ int sixAxisSensorHandle = context.RequestData.ReadInt32();
+ context.RequestData.BaseStream.Position += 4; // Padding
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ _gyroscopeZeroDriftMode = GyroscopeZeroDriftMode.Standard;
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _gyroscopeZeroDriftMode });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(82)]
+ // IsSixAxisSensorAtRest(nn::hid::SixAxisSensorHandle, nn::applet::AppletResourceUserId) -> bool IsAsRest
+ public ResultCode IsSixAxisSensorAtRest(ServiceCtx context)
+ {
+ int sixAxisSensorHandle = context.RequestData.ReadInt32();
+ context.RequestData.BaseStream.Position += 4; // Padding
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ bool isAtRest = true;
+
+ context.ResponseData.Write(isAtRest);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, isAtRest });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(83)] // 6.0.0+
+ // IsFirmwareUpdateAvailableForSixAxisSensor(nn::hid::AppletResourceUserId, nn::hid::SixAxisSensorHandle, pid) -> bool UpdateAvailable
+ public ResultCode IsFirmwareUpdateAvailableForSixAxisSensor(ServiceCtx context)
+ {
+ int sixAxisSensorHandle = context.RequestData.ReadInt32();
+ context.RequestData.BaseStream.Position += 4; // Padding
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ context.ResponseData.Write(_isFirmwareUpdateAvailableForSixAxisSensor);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _isFirmwareUpdateAvailableForSixAxisSensor });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(84)] // 13.0.0+
+ // EnableSixAxisSensorUnalteredPassthrough(nn::applet::AppletResourceUserId, nn::hid::SixAxisSensorHandle, u8 enabled)
+ public ResultCode EnableSixAxisSensorUnalteredPassthrough(ServiceCtx context)
+ {
+ _isSixAxisSensorUnalteredPassthroughEnabled = context.RequestData.ReadUInt32() != 0;
+ int sixAxisSensorHandle = context.RequestData.ReadInt32();
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle, _isSixAxisSensorUnalteredPassthroughEnabled });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(85)] // 13.0.0+
+ // IsSixAxisSensorUnalteredPassthroughEnabled(nn::applet::AppletResourceUserId, nn::hid::SixAxisSensorHandle) -> u8 enabled
+ public ResultCode IsSixAxisSensorUnalteredPassthroughEnabled(ServiceCtx context)
+ {
+ int sixAxisSensorHandle = context.RequestData.ReadInt32();
+ context.RequestData.BaseStream.Position += 4; // Padding
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ context.ResponseData.Write(_isSixAxisSensorUnalteredPassthroughEnabled);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(87)] // 13.0.0+
+ // LoadSixAxisSensorCalibrationParameter(nn::applet::AppletResourceUserId, nn::hid::SixAxisSensorHandle, u64 unknown)
+ public ResultCode LoadSixAxisSensorCalibrationParameter(ServiceCtx context)
+ {
+ int sixAxisSensorHandle = context.RequestData.ReadInt32();
+ context.RequestData.BaseStream.Position += 4; // Padding
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ // TODO: CalibrationParameter have to be determined.
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(88)] // 13.0.0+
+ // GetSixAxisSensorIcInformation(nn::applet::AppletResourceUserId, nn::hid::SixAxisSensorHandle) -> u64 unknown
+ public ResultCode GetSixAxisSensorIcInformation(ServiceCtx context)
+ {
+ int sixAxisSensorHandle = context.RequestData.ReadInt32();
+ context.RequestData.BaseStream.Position += 4; // Padding
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ // TODO: IcInformation have to be determined.
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, sixAxisSensorHandle });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(91)]
+ // ActivateGesture(nn::applet::AppletResourceUserId, int Unknown0)
+ public ResultCode ActivateGesture(ServiceCtx context)
+ {
+ long appletResourceUserId = context.RequestData.ReadInt64();
+ int unknown0 = context.RequestData.ReadInt32();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, unknown0 });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(100)]
+ // SetSupportedNpadStyleSet(pid, nn::applet::AppletResourceUserId, nn::hid::NpadStyleTag)
+ public ResultCode SetSupportedNpadStyleSet(ServiceCtx context)
+ {
+ ulong pid = context.Request.HandleDesc.PId;
+ ControllerType type = (ControllerType)context.RequestData.ReadInt32();
+ context.RequestData.BaseStream.Position += 4; // Padding
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { pid, appletResourceUserId, type });
+
+ context.Device.Hid.Npads.SupportedStyleSets = type;
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(101)]
+ // GetSupportedNpadStyleSet(pid, nn::applet::AppletResourceUserId) -> uint nn::hid::NpadStyleTag
+ public ResultCode GetSupportedNpadStyleSet(ServiceCtx context)
+ {
+ ulong pid = context.Request.HandleDesc.PId;
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ context.ResponseData.Write((int)context.Device.Hid.Npads.SupportedStyleSets);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, context.Device.Hid.Npads.SupportedStyleSets });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(102)]
+ // SetSupportedNpadIdType(nn::applet::AppletResourceUserId, array<NpadIdType, 9>)
+ public ResultCode SetSupportedNpadIdType(ServiceCtx context)
+ {
+ long appletResourceUserId = context.RequestData.ReadInt64();
+ ulong arrayPosition = context.Request.PtrBuff[0].Position;
+ ulong arraySize = context.Request.PtrBuff[0].Size;
+
+ ReadOnlySpan<NpadIdType> supportedPlayerIds = MemoryMarshal.Cast<byte, NpadIdType>(context.Memory.GetSpan(arrayPosition, (int)arraySize));
+
+ context.Device.Hid.Npads.ClearSupportedPlayers();
+
+ for (int i = 0; i < supportedPlayerIds.Length; ++i)
+ {
+ if (HidUtils.IsValidNpadIdType(supportedPlayerIds[i]))
+ {
+ context.Device.Hid.Npads.SetSupportedPlayer(HidUtils.GetIndexFromNpadIdType(supportedPlayerIds[i]));
+ }
+ }
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, $"{supportedPlayerIds.Length} Players: " + string.Join(",", supportedPlayerIds.ToArray()));
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(103)]
+ // ActivateNpad(nn::applet::AppletResourceUserId)
+ public ResultCode ActivateNpad(ServiceCtx context)
+ {
+ return ActiveNpadImpl(context);
+ }
+
+ [CommandCmif(104)]
+ // DeactivateNpad(nn::applet::AppletResourceUserId)
+ public ResultCode DeactivateNpad(ServiceCtx context)
+ {
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ context.Device.Hid.Npads.Active = false;
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(106)]
+ // AcquireNpadStyleSetUpdateEventHandle(nn::applet::AppletResourceUserId, uint, ulong) -> nn::sf::NativeHandle
+ public ResultCode AcquireNpadStyleSetUpdateEventHandle(ServiceCtx context)
+ {
+ PlayerIndex npadId = HidUtils.GetIndexFromNpadIdType((NpadIdType)context.RequestData.ReadInt32());
+ long appletResourceUserId = context.RequestData.ReadInt64();
+ long npadStyleSet = context.RequestData.ReadInt64();
+
+ KEvent evnt = context.Device.Hid.Npads.GetStyleSetUpdateEvent(npadId);
+ if (context.Process.HandleTable.GenerateHandle(evnt.ReadableEvent, out int handle) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+
+ // Games expect this event to be signaled after calling this function
+ evnt.ReadableEvent.Signal();
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, npadId, npadStyleSet });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(107)]
+ // DisconnectNpad(nn::applet::AppletResourceUserId, uint NpadIdType)
+ public ResultCode DisconnectNpad(ServiceCtx context)
+ {
+ NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadInt32();
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, npadIdType });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(108)]
+ // GetPlayerLedPattern(u32 npad_id) -> u64 led_pattern
+ public ResultCode GetPlayerLedPattern(ServiceCtx context)
+ {
+ NpadIdType npadId = (NpadIdType)context.RequestData.ReadUInt32();
+
+ ulong ledPattern = npadId switch
+ {
+ NpadIdType.Player1 => 0b0001,
+ NpadIdType.Player2 => 0b0011,
+ NpadIdType.Player3 => 0b0111,
+ NpadIdType.Player4 => 0b1111,
+ NpadIdType.Player5 => 0b1001,
+ NpadIdType.Player6 => 0b0101,
+ NpadIdType.Player7 => 0b1101,
+ NpadIdType.Player8 => 0b0110,
+ NpadIdType.Unknown => 0b0000,
+ NpadIdType.Handheld => 0b0000,
+ _ => throw new ArgumentOutOfRangeException(nameof(npadId))
+ };
+
+ context.ResponseData.Write(ledPattern);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(109)] // 5.0.0+
+ // ActivateNpadWithRevision(nn::applet::AppletResourceUserId, ulong revision)
+ public ResultCode ActivateNpadWithRevision(ServiceCtx context)
+ {
+ ulong revision = context.RequestData.ReadUInt64();
+
+ return ActiveNpadImpl(context, revision);
+ }
+
+ private ResultCode ActiveNpadImpl(ServiceCtx context, ulong revision = 0)
+ {
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ context.Device.Hid.Npads.Active = true;
+
+ // Initialize entries to avoid issues with some games.
+
+ List<GamepadInput> emptyGamepadInputs = new List<GamepadInput>();
+ List<SixAxisInput> emptySixAxisInputs = new List<SixAxisInput>();
+
+ for (int player = 0; player < NpadDevices.MaxControllers; player++)
+ {
+ GamepadInput gamepadInput = new GamepadInput();
+ SixAxisInput sixaxisInput = new SixAxisInput();
+
+ gamepadInput.PlayerId = (PlayerIndex)player;
+ sixaxisInput.PlayerId = (PlayerIndex)player;
+
+ sixaxisInput.Orientation = new float[9];
+
+ emptyGamepadInputs.Add(gamepadInput);
+ emptySixAxisInputs.Add(sixaxisInput);
+ }
+
+ for (int entry = 0; entry < Hid.SharedMemEntryCount; entry++)
+ {
+ context.Device.Hid.Npads.Update(emptyGamepadInputs);
+ context.Device.Hid.Npads.UpdateSixAxis(emptySixAxisInputs);
+ }
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, revision });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(120)]
+ // SetNpadJoyHoldType(nn::applet::AppletResourceUserId, ulong NpadJoyHoldType)
+ public ResultCode SetNpadJoyHoldType(ServiceCtx context)
+ {
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ NpadJoyHoldType npadJoyHoldType = (NpadJoyHoldType)context.RequestData.ReadUInt64();
+
+ if (npadJoyHoldType > NpadJoyHoldType.Horizontal)
+ {
+ throw new ArgumentOutOfRangeException(nameof(npadJoyHoldType));
+ }
+
+ foreach (PlayerIndex playerIndex in context.Device.Hid.Npads.GetSupportedPlayers())
+ {
+ if (HidUtils.GetNpadIdTypeFromIndex(playerIndex) > NpadIdType.Handheld)
+ {
+ return ResultCode.InvalidNpadIdType;
+ }
+ }
+
+ context.Device.Hid.Npads.JoyHold = npadJoyHoldType;
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(121)]
+ // GetNpadJoyHoldType(nn::applet::AppletResourceUserId) -> ulong NpadJoyHoldType
+ public ResultCode GetNpadJoyHoldType(ServiceCtx context)
+ {
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ foreach (PlayerIndex playerIndex in context.Device.Hid.Npads.GetSupportedPlayers())
+ {
+ if (HidUtils.GetNpadIdTypeFromIndex(playerIndex) > NpadIdType.Handheld)
+ {
+ return ResultCode.InvalidNpadIdType;
+ }
+ }
+
+ context.ResponseData.Write((ulong)context.Device.Hid.Npads.JoyHold);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(122)]
+ // SetNpadJoyAssignmentModeSingleByDefault(uint HidControllerId, nn::applet::AppletResourceUserId)
+ public ResultCode SetNpadJoyAssignmentModeSingleByDefault(ServiceCtx context)
+ {
+ NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadUInt32();
+ context.RequestData.BaseStream.Position += 4; // Padding
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ if (HidUtils.IsValidNpadIdType(npadIdType))
+ {
+ context.Device.Hid.SharedMemory.Npads[(int)HidUtils.GetIndexFromNpadIdType(npadIdType)].InternalState.JoyAssignmentMode = NpadJoyAssignmentMode.Single;
+ }
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(123)]
+ // SetNpadJoyAssignmentModeSingle(uint npadIdType, nn::applet::AppletResourceUserId, uint npadJoyDeviceType)
+ public ResultCode SetNpadJoyAssignmentModeSingle(ServiceCtx context)
+ {
+ NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadUInt32();
+ context.RequestData.BaseStream.Position += 4; // Padding
+ long appletResourceUserId = context.RequestData.ReadInt64();
+ NpadJoyDeviceType npadJoyDeviceType = (NpadJoyDeviceType)context.RequestData.ReadUInt32();
+
+ if (HidUtils.IsValidNpadIdType(npadIdType))
+ {
+ SetNpadJoyAssignmentModeSingleWithDestinationImpl(context, npadIdType, appletResourceUserId, npadJoyDeviceType, out _, out _);
+ }
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(124)]
+ // SetNpadJoyAssignmentModeDual(uint npadIdType, nn::applet::AppletResourceUserId)
+ public ResultCode SetNpadJoyAssignmentModeDual(ServiceCtx context)
+ {
+ NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadUInt32();
+ context.RequestData.BaseStream.Position += 4; // Padding
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ if (HidUtils.IsValidNpadIdType(npadIdType))
+ {
+ context.Device.Hid.SharedMemory.Npads[(int)HidUtils.GetIndexFromNpadIdType(npadIdType)].InternalState.JoyAssignmentMode = NpadJoyAssignmentMode.Dual;
+ }
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(125)]
+ // MergeSingleJoyAsDualJoy(uint npadIdType0, uint npadIdType1, nn::applet::AppletResourceUserId)
+ public ResultCode MergeSingleJoyAsDualJoy(ServiceCtx context)
+ {
+ NpadIdType npadIdType0 = (NpadIdType)context.RequestData.ReadUInt32();
+ NpadIdType npadIdType1 = (NpadIdType)context.RequestData.ReadUInt32();
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ if (HidUtils.IsValidNpadIdType(npadIdType0) && HidUtils.IsValidNpadIdType(npadIdType1))
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, npadIdType0, npadIdType1 });
+ }
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(126)]
+ // StartLrAssignmentMode(nn::applet::AppletResourceUserId)
+ public ResultCode StartLrAssignmentMode(ServiceCtx context)
+ {
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(127)]
+ // StopLrAssignmentMode(nn::applet::AppletResourceUserId)
+ public ResultCode StopLrAssignmentMode(ServiceCtx context)
+ {
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(128)]
+ // SetNpadHandheldActivationMode(nn::applet::AppletResourceUserId, long HidNpadHandheldActivationMode)
+ public ResultCode SetNpadHandheldActivationMode(ServiceCtx context)
+ {
+ long appletResourceUserId = context.RequestData.ReadInt64();
+ _npadHandheldActivationMode = (NpadHandheldActivationMode)context.RequestData.ReadInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, _npadHandheldActivationMode });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(129)]
+ // GetNpadHandheldActivationMode(nn::applet::AppletResourceUserId) -> long HidNpadHandheldActivationMode
+ public ResultCode GetNpadHandheldActivationMode(ServiceCtx context)
+ {
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ context.ResponseData.Write((long)_npadHandheldActivationMode);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, _npadHandheldActivationMode });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(130)]
+ // SwapNpadAssignment(uint OldNpadAssignment, uint NewNpadAssignment, nn::applet::AppletResourceUserId)
+ public ResultCode SwapNpadAssignment(ServiceCtx context)
+ {
+ int oldNpadAssignment = context.RequestData.ReadInt32();
+ int newNpadAssignment = context.RequestData.ReadInt32();
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, oldNpadAssignment, newNpadAssignment });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(131)]
+ // IsUnintendedHomeButtonInputProtectionEnabled(uint Unknown0, nn::applet::AppletResourceUserId) -> bool IsEnabled
+ public ResultCode IsUnintendedHomeButtonInputProtectionEnabled(ServiceCtx context)
+ {
+ uint unknown0 = context.RequestData.ReadUInt32();
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ context.ResponseData.Write(_unintendedHomeButtonInputProtectionEnabled);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, unknown0, _unintendedHomeButtonInputProtectionEnabled });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(132)]
+ // EnableUnintendedHomeButtonInputProtection(bool Enable, uint Unknown0, nn::applet::AppletResourceUserId)
+ public ResultCode EnableUnintendedHomeButtonInputProtection(ServiceCtx context)
+ {
+ _unintendedHomeButtonInputProtectionEnabled = context.RequestData.ReadBoolean();
+ uint unknown0 = context.RequestData.ReadUInt32();
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, unknown0, _unintendedHomeButtonInputProtectionEnabled });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(133)] // 5.0.0+
+ // SetNpadJoyAssignmentModeSingleWithDestination(uint npadIdType, uint npadJoyDeviceType, nn::applet::AppletResourceUserId) -> bool npadIdTypeIsSet, uint npadIdTypeSet
+ public ResultCode SetNpadJoyAssignmentModeSingleWithDestination(ServiceCtx context)
+ {
+ NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadInt32();
+ NpadJoyDeviceType npadJoyDeviceType = (NpadJoyDeviceType)context.RequestData.ReadInt32();
+ context.RequestData.BaseStream.Position += 4; // Padding
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ if (HidUtils.IsValidNpadIdType(npadIdType))
+ {
+ SetNpadJoyAssignmentModeSingleWithDestinationImpl(context, npadIdType, appletResourceUserId, npadJoyDeviceType, out NpadIdType npadIdTypeSet, out bool npadIdTypeIsSet);
+
+ if (npadIdTypeIsSet)
+ {
+ context.ResponseData.Write(npadIdTypeIsSet);
+ context.ResponseData.Write((uint)npadIdTypeSet);
+ }
+ }
+
+ return ResultCode.Success;
+ }
+
+ private void SetNpadJoyAssignmentModeSingleWithDestinationImpl(ServiceCtx context, NpadIdType npadIdType, long appletResourceUserId, NpadJoyDeviceType npadJoyDeviceType, out NpadIdType npadIdTypeSet, out bool npadIdTypeIsSet)
+ {
+ npadIdTypeSet = default;
+ npadIdTypeIsSet = false;
+
+ context.Device.Hid.SharedMemory.Npads[(int)HidUtils.GetIndexFromNpadIdType(npadIdType)].InternalState.JoyAssignmentMode = NpadJoyAssignmentMode.Single;
+
+ // TODO: Service seems to use the npadJoyDeviceType to find the nearest other Npad available and merge them to dual.
+ // If one is found, it returns the npadIdType of the other Npad and a bool.
+ // If not, it returns nothing.
+ }
+
+ [CommandCmif(200)]
+ // GetVibrationDeviceInfo(nn::hid::VibrationDeviceHandle) -> nn::hid::VibrationDeviceInfo
+ public ResultCode GetVibrationDeviceInfo(ServiceCtx context)
+ {
+ VibrationDeviceHandle deviceHandle = context.RequestData.ReadStruct<VibrationDeviceHandle>();
+ NpadStyleIndex deviceType = (NpadStyleIndex)deviceHandle.DeviceType;
+ NpadIdType npadIdType = (NpadIdType)deviceHandle.PlayerId;
+
+ if (deviceType < NpadStyleIndex.System || deviceType >= NpadStyleIndex.FullKey)
+ {
+ if (!HidUtils.IsValidNpadIdType(npadIdType))
+ {
+ return ResultCode.InvalidNpadIdType;
+ }
+
+ if (deviceHandle.Position > 1)
+ {
+ return ResultCode.InvalidDeviceIndex;
+ }
+
+ VibrationDeviceType vibrationDeviceType = VibrationDeviceType.None;
+
+ if (Enum.IsDefined(deviceType))
+ {
+ vibrationDeviceType = VibrationDeviceType.LinearResonantActuator;
+ }
+ else if ((uint)deviceType == 8)
+ {
+ vibrationDeviceType = VibrationDeviceType.GcErm;
+ }
+
+ VibrationDevicePosition vibrationDevicePosition = VibrationDevicePosition.None;
+
+ if (vibrationDeviceType == VibrationDeviceType.LinearResonantActuator)
+ {
+ if (deviceHandle.Position == 0)
+ {
+ vibrationDevicePosition = VibrationDevicePosition.Left;
+ }
+ else if (deviceHandle.Position == 1)
+ {
+ vibrationDevicePosition = VibrationDevicePosition.Right;
+ }
+ else
+ {
+ throw new ArgumentOutOfRangeException(nameof(deviceHandle.Position));
+ }
+ }
+
+ VibrationDeviceValue deviceInfo = new VibrationDeviceValue
+ {
+ DeviceType = vibrationDeviceType,
+ Position = vibrationDevicePosition
+ };
+
+ context.ResponseData.WriteStruct(deviceInfo);
+
+ return ResultCode.Success;
+ }
+
+ return ResultCode.InvalidNpadDeviceType;
+ }
+
+ [CommandCmif(201)]
+ // SendVibrationValue(nn::hid::VibrationDeviceHandle, nn::hid::VibrationValue, nn::applet::AppletResourceUserId)
+ public ResultCode SendVibrationValue(ServiceCtx context)
+ {
+ VibrationDeviceHandle deviceHandle = new VibrationDeviceHandle
+ {
+ DeviceType = context.RequestData.ReadByte(),
+ PlayerId = context.RequestData.ReadByte(),
+ Position = context.RequestData.ReadByte(),
+ Reserved = context.RequestData.ReadByte()
+ };
+
+ VibrationValue vibrationValue = new VibrationValue
+ {
+ AmplitudeLow = context.RequestData.ReadSingle(),
+ FrequencyLow = context.RequestData.ReadSingle(),
+ AmplitudeHigh = context.RequestData.ReadSingle(),
+ FrequencyHigh = context.RequestData.ReadSingle()
+ };
+
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ Dictionary<byte, VibrationValue> dualVibrationValues = new Dictionary<byte, VibrationValue>();
+
+ dualVibrationValues[deviceHandle.Position] = vibrationValue;
+
+ context.Device.Hid.Npads.UpdateRumbleQueue((PlayerIndex)deviceHandle.PlayerId, dualVibrationValues);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(202)]
+ // GetActualVibrationValue(nn::hid::VibrationDeviceHandle, nn::applet::AppletResourceUserId) -> nn::hid::VibrationValue
+ public ResultCode GetActualVibrationValue(ServiceCtx context)
+ {
+ VibrationDeviceHandle deviceHandle = new VibrationDeviceHandle
+ {
+ DeviceType = context.RequestData.ReadByte(),
+ PlayerId = context.RequestData.ReadByte(),
+ Position = context.RequestData.ReadByte(),
+ Reserved = context.RequestData.ReadByte()
+ };
+
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ VibrationValue vibrationValue = context.Device.Hid.Npads.GetLastVibrationValue((PlayerIndex)deviceHandle.PlayerId, deviceHandle.Position);
+
+ context.ResponseData.Write(vibrationValue.AmplitudeLow);
+ context.ResponseData.Write(vibrationValue.FrequencyLow);
+ context.ResponseData.Write(vibrationValue.AmplitudeHigh);
+ context.ResponseData.Write(vibrationValue.FrequencyHigh);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(203)]
+ // CreateActiveVibrationDeviceList() -> object<nn::hid::IActiveVibrationDeviceList>
+ public ResultCode CreateActiveVibrationDeviceList(ServiceCtx context)
+ {
+ MakeObject(context, new IActiveApplicationDeviceList());
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(204)]
+ // PermitVibration(bool Enable)
+ public ResultCode PermitVibration(ServiceCtx context)
+ {
+ _vibrationPermitted = context.RequestData.ReadBoolean();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { _vibrationPermitted });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(205)]
+ // IsVibrationPermitted() -> bool IsEnabled
+ public ResultCode IsVibrationPermitted(ServiceCtx context)
+ {
+ context.ResponseData.Write(_vibrationPermitted);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(206)]
+ // SendVibrationValues(nn::applet::AppletResourceUserId, buffer<array<nn::hid::VibrationDeviceHandle>, type: 9>, buffer<array<nn::hid::VibrationValue>, type: 9>)
+ public ResultCode SendVibrationValues(ServiceCtx context)
+ {
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ byte[] vibrationDeviceHandleBuffer = new byte[context.Request.PtrBuff[0].Size];
+
+ context.Memory.Read(context.Request.PtrBuff[0].Position, vibrationDeviceHandleBuffer);
+
+ byte[] vibrationValueBuffer = new byte[context.Request.PtrBuff[1].Size];
+
+ context.Memory.Read(context.Request.PtrBuff[1].Position, vibrationValueBuffer);
+
+ Span<VibrationDeviceHandle> deviceHandles = MemoryMarshal.Cast<byte, VibrationDeviceHandle>(vibrationDeviceHandleBuffer);
+ Span<VibrationValue> vibrationValues = MemoryMarshal.Cast<byte, VibrationValue>(vibrationValueBuffer);
+
+ if (!deviceHandles.IsEmpty && vibrationValues.Length == deviceHandles.Length)
+ {
+ Dictionary<byte, VibrationValue> dualVibrationValues = new Dictionary<byte, VibrationValue>();
+ 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<byte, VibrationValue>();
+ }
+
+ dualVibrationValues[position] = vibrationValues[deviceCounter];
+ currentIndex = index;
+ }
+
+ context.Device.Hid.Npads.UpdateRumbleQueue(currentIndex, dualVibrationValues);
+ }
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(207)] // 4.0.0+
+ // SendVibrationGcErmCommand(nn::hid::VibrationDeviceHandle, nn::hid::VibrationGcErmCommand, nn::applet::AppletResourceUserId)
+ public ResultCode SendVibrationGcErmCommand(ServiceCtx context)
+ {
+ int vibrationDeviceHandle = context.RequestData.ReadInt32();
+ long vibrationGcErmCommand = context.RequestData.ReadInt64();
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, vibrationDeviceHandle, vibrationGcErmCommand });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(208)] // 4.0.0+
+ // GetActualVibrationGcErmCommand(nn::hid::VibrationDeviceHandle, nn::applet::AppletResourceUserId) -> nn::hid::VibrationGcErmCommand
+ public ResultCode GetActualVibrationGcErmCommand(ServiceCtx context)
+ {
+ int vibrationDeviceHandle = context.RequestData.ReadInt32();
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ context.ResponseData.Write(_vibrationGcErmCommand);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, vibrationDeviceHandle, _vibrationGcErmCommand });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(209)] // 4.0.0+
+ // BeginPermitVibrationSession(nn::applet::AppletResourceUserId)
+ public ResultCode BeginPermitVibrationSession(ServiceCtx context)
+ {
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(210)] // 4.0.0+
+ // EndPermitVibrationSession()
+ public ResultCode EndPermitVibrationSession(ServiceCtx context)
+ {
+ Logger.Stub?.PrintStub(LogClass.ServiceHid);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(211)] // 7.0.0+
+ // IsVibrationDeviceMounted(nn::hid::VibrationDeviceHandle, nn::applet::AppletResourceUserId)
+ public ResultCode IsVibrationDeviceMounted(ServiceCtx context)
+ {
+ int vibrationDeviceHandle = context.RequestData.ReadInt32();
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ // NOTE: Service use vibrationDeviceHandle to get the PlayerIndex.
+ // And return false if (npadIdType >= (NpadIdType)8 && npadIdType != NpadIdType.Handheld && npadIdType != NpadIdType.Unknown)
+
+ context.ResponseData.Write(true);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(300)]
+ // ActivateConsoleSixAxisSensor(nn::applet::AppletResourceUserId)
+ public ResultCode ActivateConsoleSixAxisSensor(ServiceCtx context)
+ {
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(301)]
+ // StartConsoleSixAxisSensor(nn::hid::ConsoleSixAxisSensorHandle, nn::applet::AppletResourceUserId)
+ public ResultCode StartConsoleSixAxisSensor(ServiceCtx context)
+ {
+ int consoleSixAxisSensorHandle = context.RequestData.ReadInt32();
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, consoleSixAxisSensorHandle });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(302)]
+ // StopConsoleSixAxisSensor(nn::hid::ConsoleSixAxisSensorHandle, nn::applet::AppletResourceUserId)
+ public ResultCode StopConsoleSixAxisSensor(ServiceCtx context)
+ {
+ int consoleSixAxisSensorHandle = context.RequestData.ReadInt32();
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, consoleSixAxisSensorHandle });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(303)] // 5.0.0+
+ // ActivateSevenSixAxisSensor(nn::applet::AppletResourceUserId)
+ public ResultCode ActivateSevenSixAxisSensor(ServiceCtx context)
+ {
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(304)] // 5.0.0+
+ // StartSevenSixAxisSensor(nn::applet::AppletResourceUserId)
+ public ResultCode StartSevenSixAxisSensor(ServiceCtx context)
+ {
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(305)] // 5.0.0+
+ // StopSevenSixAxisSensor(nn::applet::AppletResourceUserId)
+ public ResultCode StopSevenSixAxisSensor(ServiceCtx context)
+ {
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(306)] // 5.0.0+
+ // InitializeSevenSixAxisSensor(array<nn::sf::NativeHandle>, ulong Counter0, array<nn::sf::NativeHandle>, ulong Counter1, nn::applet::AppletResourceUserId)
+ public ResultCode InitializeSevenSixAxisSensor(ServiceCtx context)
+ {
+ long appletResourceUserId = context.RequestData.ReadInt64();
+ long counter0 = context.RequestData.ReadInt64();
+ long counter1 = context.RequestData.ReadInt64();
+
+ // TODO: Determine if array<nn::sf::NativeHandle> is a buffer or not...
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, counter0, counter1 });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(307)] // 5.0.0+
+ // FinalizeSevenSixAxisSensor(nn::applet::AppletResourceUserId)
+ public ResultCode FinalizeSevenSixAxisSensor(ServiceCtx context)
+ {
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(308)] // 5.0.0+
+ // SetSevenSixAxisSensorFusionStrength(float Strength, nn::applet::AppletResourceUserId)
+ public ResultCode SetSevenSixAxisSensorFusionStrength(ServiceCtx context)
+ {
+ _sevenSixAxisSensorFusionStrength = context.RequestData.ReadSingle();
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, _sevenSixAxisSensorFusionStrength });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(309)] // 5.0.0+
+ // GetSevenSixAxisSensorFusionStrength(nn::applet::AppletResourceUserId) -> float Strength
+ public ResultCode GetSevenSixAxisSensorFusionStrength(ServiceCtx context)
+ {
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ context.ResponseData.Write(_sevenSixAxisSensorFusionStrength);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, _sevenSixAxisSensorFusionStrength });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(310)] // 6.0.0+
+ // ResetSevenSixAxisSensorTimestamp(pid, nn::applet::AppletResourceUserId)
+ public ResultCode ResetSevenSixAxisSensorTimestamp(ServiceCtx context)
+ {
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(400)]
+ // IsUsbFullKeyControllerEnabled() -> bool IsEnabled
+ public ResultCode IsUsbFullKeyControllerEnabled(ServiceCtx context)
+ {
+ context.ResponseData.Write(_usbFullKeyControllerEnabled);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { _usbFullKeyControllerEnabled });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(401)]
+ // EnableUsbFullKeyController(bool Enable)
+ public ResultCode EnableUsbFullKeyController(ServiceCtx context)
+ {
+ _usbFullKeyControllerEnabled = context.RequestData.ReadBoolean();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { _usbFullKeyControllerEnabled });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(402)]
+ // IsUsbFullKeyControllerConnected(uint Unknown0) -> bool Connected
+ public ResultCode IsUsbFullKeyControllerConnected(ServiceCtx context)
+ {
+ int unknown0 = context.RequestData.ReadInt32();
+
+ context.ResponseData.Write(true); //FullKeyController is always connected ?
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { unknown0, Connected = true });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(403)] // 4.0.0+
+ // HasBattery(uint NpadId) -> bool HasBattery
+ public ResultCode HasBattery(ServiceCtx context)
+ {
+ int npadId = context.RequestData.ReadInt32();
+
+ context.ResponseData.Write(true); //Npad always got a battery ?
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { npadId, HasBattery = true });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(404)] // 4.0.0+
+ // HasLeftRightBattery(uint NpadId) -> bool HasLeftBattery, bool HasRightBattery
+ public ResultCode HasLeftRightBattery(ServiceCtx context)
+ {
+ int npadId = context.RequestData.ReadInt32();
+
+ context.ResponseData.Write(true); //Npad always got a left battery ?
+ context.ResponseData.Write(true); //Npad always got a right battery ?
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { npadId, HasLeftBattery = true, HasRightBattery = true });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(405)] // 4.0.0+
+ // GetNpadInterfaceType(uint NpadId) -> uchar InterfaceType
+ public ResultCode GetNpadInterfaceType(ServiceCtx context)
+ {
+ int npadId = context.RequestData.ReadInt32();
+
+ context.ResponseData.Write((byte)0);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { npadId, NpadInterfaceType = 0 });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(406)] // 4.0.0+
+ // GetNpadLeftRightInterfaceType(uint NpadId) -> uchar LeftInterfaceType, uchar RightInterfaceType
+ public ResultCode GetNpadLeftRightInterfaceType(ServiceCtx context)
+ {
+ int npadId = context.RequestData.ReadInt32();
+
+ context.ResponseData.Write((byte)0);
+ context.ResponseData.Write((byte)0);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { npadId, LeftInterfaceType = 0, RightInterfaceType = 0 });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(500)] // 5.0.0+
+ // GetPalmaConnectionHandle(uint Unknown0, nn::applet::AppletResourceUserId) -> nn::hid::PalmaConnectionHandle
+ public ResultCode GetPalmaConnectionHandle(ServiceCtx context)
+ {
+ int unknown0 = context.RequestData.ReadInt32();
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ int palmaConnectionHandle = 0;
+
+ context.ResponseData.Write(palmaConnectionHandle);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId , unknown0, palmaConnectionHandle });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(501)] // 5.0.0+
+ // InitializePalma(nn::hid::PalmaConnectionHandle)
+ public ResultCode InitializePalma(ServiceCtx context)
+ {
+ int palmaConnectionHandle = context.RequestData.ReadInt32();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle });
+
+ _palmaOperationCompleteEvent.ReadableEvent.Signal();
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(502)] // 5.0.0+
+ // AcquirePalmaOperationCompleteEvent(nn::hid::PalmaConnectionHandle) -> nn::sf::NativeHandle
+ public ResultCode AcquirePalmaOperationCompleteEvent(ServiceCtx context)
+ {
+ int palmaConnectionHandle = context.RequestData.ReadInt32();
+
+ if (context.Process.HandleTable.GenerateHandle(_palmaOperationCompleteEvent.ReadableEvent, out int handle) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(503)] // 5.0.0+
+ // GetPalmaOperationInfo(nn::hid::PalmaConnectionHandle) -> long Unknown0, buffer<Unknown>
+ public ResultCode GetPalmaOperationInfo(ServiceCtx context)
+ {
+ int palmaConnectionHandle = context.RequestData.ReadInt32();
+
+ long unknown0 = 0; //Counter?
+
+ context.ResponseData.Write(unknown0);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle, unknown0 });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(504)] // 5.0.0+
+ // PlayPalmaActivity(nn::hid::PalmaConnectionHandle, ulong Unknown0)
+ public ResultCode PlayPalmaActivity(ServiceCtx context)
+ {
+ int palmaConnectionHandle = context.RequestData.ReadInt32();
+ long unknown0 = context.RequestData.ReadInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle, unknown0 });
+
+ _palmaOperationCompleteEvent.ReadableEvent.Signal();
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(505)] // 5.0.0+
+ // SetPalmaFrModeType(nn::hid::PalmaConnectionHandle, ulong FrModeType)
+ public ResultCode SetPalmaFrModeType(ServiceCtx context)
+ {
+ int palmaConnectionHandle = context.RequestData.ReadInt32();
+ long frModeType = context.RequestData.ReadInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle, frModeType });
+
+ _palmaOperationCompleteEvent.ReadableEvent.Signal();
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(506)] // 5.0.0+
+ // ReadPalmaStep(nn::hid::PalmaConnectionHandle)
+ public ResultCode ReadPalmaStep(ServiceCtx context)
+ {
+ int palmaConnectionHandle = context.RequestData.ReadInt32();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(507)] // 5.0.0+
+ // EnablePalmaStep(nn::hid::PalmaConnectionHandle, bool Enable)
+ public ResultCode EnablePalmaStep(ServiceCtx context)
+ {
+ int palmaConnectionHandle = context.RequestData.ReadInt32();
+ bool enabledPalmaStep = context.RequestData.ReadBoolean();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle, enabledPalmaStep });
+
+ _palmaOperationCompleteEvent.ReadableEvent.Signal();
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(508)] // 5.0.0+
+ // ResetPalmaStep(nn::hid::PalmaConnectionHandle)
+ public ResultCode ResetPalmaStep(ServiceCtx context)
+ {
+ int palmaConnectionHandle = context.RequestData.ReadInt32();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle });
+
+ _palmaOperationCompleteEvent.ReadableEvent.Signal();
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(509)] // 5.0.0+
+ // ReadPalmaApplicationSection(nn::hid::PalmaConnectionHandle, ulong Unknown0, ulong Unknown1)
+ public ResultCode ReadPalmaApplicationSection(ServiceCtx context)
+ {
+ int palmaConnectionHandle = context.RequestData.ReadInt32();
+ long unknown0 = context.RequestData.ReadInt64();
+ long unknown1 = context.RequestData.ReadInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle, unknown0, unknown1 });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(510)] // 5.0.0+
+ // WritePalmaApplicationSection(nn::hid::PalmaConnectionHandle, ulong Unknown0, ulong Unknown1, nn::hid::PalmaApplicationSectionAccessBuffer)
+ public ResultCode WritePalmaApplicationSection(ServiceCtx context)
+ {
+ int palmaConnectionHandle = context.RequestData.ReadInt32();
+ long unknown0 = context.RequestData.ReadInt64();
+ long unknown1 = context.RequestData.ReadInt64();
+ // nn::hid::PalmaApplicationSectionAccessBuffer cast is unknown
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle, unknown0, unknown1 });
+
+ _palmaOperationCompleteEvent.ReadableEvent.Signal();
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(511)] // 5.0.0+
+ // ReadPalmaUniqueCode(nn::hid::PalmaConnectionHandle)
+ public ResultCode ReadPalmaUniqueCode(ServiceCtx context)
+ {
+ int palmaConnectionHandle = context.RequestData.ReadInt32();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(512)] // 5.0.0+
+ // SetPalmaUniqueCodeInvalid(nn::hid::PalmaConnectionHandle)
+ public ResultCode SetPalmaUniqueCodeInvalid(ServiceCtx context)
+ {
+ int palmaConnectionHandle = context.RequestData.ReadInt32();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { palmaConnectionHandle });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(522)] // 5.1.0+
+ // SetIsPalmaAllConnectable(nn::applet::AppletResourceUserId, bool, pid)
+ public ResultCode SetIsPalmaAllConnectable(ServiceCtx context)
+ {
+ long appletResourceUserId = context.RequestData.ReadInt64();
+ long unknownBool = context.RequestData.ReadInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, unknownBool });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(525)] // 5.1.0+
+ // SetPalmaBoostMode(bool)
+ public ResultCode SetPalmaBoostMode(ServiceCtx context)
+ {
+ // NOTE: Stubbed in system module.
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(1000)]
+ // SetNpadCommunicationMode(long CommunicationMode, nn::applet::AppletResourceUserId)
+ public ResultCode SetNpadCommunicationMode(ServiceCtx context)
+ {
+ _npadCommunicationMode = context.RequestData.ReadInt64();
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, _npadCommunicationMode });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(1001)]
+ // GetNpadCommunicationMode() -> long CommunicationMode
+ public ResultCode GetNpadCommunicationMode(ServiceCtx context)
+ {
+ context.ResponseData.Write(_npadCommunicationMode);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { _npadCommunicationMode });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(1002)] // 9.0.0+
+ // SetTouchScreenConfiguration(nn::hid::TouchScreenConfigurationForNx, nn::applet::AppletResourceUserId)
+ public ResultCode SetTouchScreenConfiguration(ServiceCtx context)
+ {
+ long touchScreenConfigurationForNx = context.RequestData.ReadInt64();
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, touchScreenConfigurationForNx });
+
+ return ResultCode.Success;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/IHidSystemServer.cs b/src/Ryujinx.HLE/HOS/Services/Hid/IHidSystemServer.cs
new file mode 100644
index 00000000..4a5d0e9b
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/IHidSystemServer.cs
@@ -0,0 +1,76 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Services.Hid.HidServer;
+using Ryujinx.HLE.HOS.Services.Hid.Types;
+
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ [Service("hid:sys")]
+ class IHidSystemServer : IpcService
+ {
+ public IHidSystemServer(ServiceCtx context) { }
+
+ [CommandCmif(303)]
+ // ApplyNpadSystemCommonPolicy(u64)
+ public ResultCode ApplyNpadSystemCommonPolicy(ServiceCtx context)
+ {
+ ulong commonPolicy = context.RequestData.ReadUInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceHid, new { commonPolicy });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(306)]
+ // GetLastActiveNpad(u32) -> u8, u8
+ public ResultCode GetLastActiveNpad(ServiceCtx context)
+ {
+ // TODO: RequestData seems to have garbage data, reading an extra uint seems to fix the issue.
+ context.RequestData.ReadUInt32();
+
+ ResultCode resultCode = GetAppletFooterUiTypeImpl(context, out AppletFooterUiType appletFooterUiType);
+
+ context.ResponseData.Write((byte)appletFooterUiType);
+ context.ResponseData.Write((byte)0);
+
+ return resultCode;
+ }
+
+ [CommandCmif(307)]
+ // GetNpadSystemExtStyle() -> u64
+ public ResultCode GetNpadSystemExtStyle(ServiceCtx context)
+ {
+ foreach (PlayerIndex playerIndex in context.Device.Hid.Npads.GetSupportedPlayers())
+ {
+ if (HidUtils.GetNpadIdTypeFromIndex(playerIndex) > NpadIdType.Handheld)
+ {
+ return ResultCode.InvalidNpadIdType;
+ }
+ }
+
+ context.ResponseData.Write((ulong)context.Device.Hid.Npads.SupportedStyleSets);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(314)] // 9.0.0+
+ // GetAppletFooterUiType(u32) -> u8
+ public ResultCode GetAppletFooterUiType(ServiceCtx context)
+ {
+ ResultCode resultCode = GetAppletFooterUiTypeImpl(context, out AppletFooterUiType appletFooterUiType);
+
+ context.ResponseData.Write((byte)appletFooterUiType);
+
+ return resultCode;
+ }
+
+ private ResultCode GetAppletFooterUiTypeImpl(ServiceCtx context, out AppletFooterUiType appletFooterUiType)
+ {
+ NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadUInt32();
+ PlayerIndex playerIndex = HidUtils.GetIndexFromNpadIdType(npadIdType);
+
+ appletFooterUiType = context.Device.Hid.SharedMemory.Npads[(int)playerIndex].InternalState.AppletFooterUiType;
+
+ return ResultCode.Success;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/IHidbusServer.cs b/src/Ryujinx.HLE/HOS/Services/Hid/IHidbusServer.cs
new file mode 100644
index 00000000..bfd1d4dc
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/IHidbusServer.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ [Service("hidbus")]
+ class IHidbusServer : IpcService
+ {
+ public IHidbusServer(ServiceCtx context) { }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/ISystemServer.cs b/src/Ryujinx.HLE/HOS/Services/Hid/ISystemServer.cs
new file mode 100644
index 00000000..71353344
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/ISystemServer.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ [Service("xcd:sys")]
+ class ISystemServer : IpcService
+ {
+ public ISystemServer(ServiceCtx context) { }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Irs/IIrSensorServer.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/IIrSensorServer.cs
new file mode 100644
index 00000000..130fcf68
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/IIrSensorServer.cs
@@ -0,0 +1,240 @@
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Services.Hid.HidServer;
+using Ryujinx.HLE.HOS.Services.Hid.Irs.Types;
+using Ryujinx.Horizon.Common;
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.Irs
+{
+ [Service("irs")]
+ class IIrSensorServer : IpcService
+ {
+ private int _irsensorSharedMemoryHandle = 0;
+
+ public IIrSensorServer(ServiceCtx context) { }
+
+ [CommandCmif(302)]
+ // ActivateIrsensor(nn::applet::AppletResourceUserId, pid)
+ public ResultCode ActivateIrsensor(ServiceCtx context)
+ {
+ ulong appletResourceUserId = context.RequestData.ReadUInt64();
+
+ // NOTE: This seems to initialize the shared memory for irs service.
+
+ Logger.Stub?.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(303)]
+ // DeactivateIrsensor(nn::applet::AppletResourceUserId, pid)
+ public ResultCode DeactivateIrsensor(ServiceCtx context)
+ {
+ ulong appletResourceUserId = context.RequestData.ReadUInt64();
+
+ // NOTE: This seems to deinitialize the shared memory for irs service.
+
+ Logger.Stub?.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(304)]
+ // GetIrsensorSharedMemoryHandle(nn::applet::AppletResourceUserId, pid) -> handle<copy>
+ public ResultCode GetIrsensorSharedMemoryHandle(ServiceCtx context)
+ {
+ // NOTE: Shared memory should use the appletResourceUserId.
+ // ulong appletResourceUserId = context.RequestData.ReadUInt64();
+
+ if (_irsensorSharedMemoryHandle == 0)
+ {
+ if (context.Process.HandleTable.GenerateHandle(context.Device.System.IirsSharedMem, out _irsensorSharedMemoryHandle) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_irsensorSharedMemoryHandle);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(305)]
+ // StopImageProcessor(pid, nn::irsensor::IrCameraHandle, nn::applet::AppletResourceUserId)
+ public ResultCode StopImageProcessor(ServiceCtx context)
+ {
+ IrCameraHandle irCameraHandle = context.RequestData.ReadStruct<IrCameraHandle>();
+ ulong appletResourceUserId = context.RequestData.ReadUInt64();
+
+ CheckCameraHandle(irCameraHandle);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId, irCameraHandle.PlayerNumber, irCameraHandle.DeviceType });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(306)]
+ // RunMomentProcessor(pid, nn::irsensor::IrCameraHandle, nn::applet::AppletResourceUserId, PackedMomentProcessorConfig)
+ public ResultCode RunMomentProcessor(ServiceCtx context)
+ {
+ IrCameraHandle irCameraHandle = context.RequestData.ReadStruct<IrCameraHandle>();
+ ulong appletResourceUserId = context.RequestData.ReadUInt64();
+ var packedMomentProcessorConfig = context.RequestData.ReadStruct<PackedMomentProcessorConfig>();
+
+ CheckCameraHandle(irCameraHandle);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId, irCameraHandle.PlayerNumber, irCameraHandle.DeviceType, packedMomentProcessorConfig.ExposureTime });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(307)]
+ // RunClusteringProcessor(pid, nn::irsensor::IrCameraHandle, nn::applet::AppletResourceUserId, PackedClusteringProcessorConfig)
+ public ResultCode RunClusteringProcessor(ServiceCtx context)
+ {
+ IrCameraHandle irCameraHandle = context.RequestData.ReadStruct<IrCameraHandle>();
+ ulong appletResourceUserId = context.RequestData.ReadUInt64();
+ var packedClusteringProcessorConfig = context.RequestData.ReadStruct<PackedClusteringProcessorConfig>();
+
+ CheckCameraHandle(irCameraHandle);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId, irCameraHandle.PlayerNumber, irCameraHandle.DeviceType, packedClusteringProcessorConfig.ExposureTime });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(308)]
+ // RunImageTransferProcessor(pid, nn::irsensor::IrCameraHandle, nn::applet::AppletResourceUserId, PackedImageTransferProcessorConfig, u64 TransferMemorySize, TransferMemoryHandle)
+ public ResultCode RunImageTransferProcessor(ServiceCtx context)
+ {
+ IrCameraHandle irCameraHandle = context.RequestData.ReadStruct<IrCameraHandle>();
+ ulong appletResourceUserId = context.RequestData.ReadUInt64();
+ var packedImageTransferProcessorConfig = context.RequestData.ReadStruct<PackedImageTransferProcessorConfig>();
+
+ CheckCameraHandle(irCameraHandle);
+
+ // TODO: Handle the Transfer Memory.
+
+ Logger.Stub?.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId, irCameraHandle.PlayerNumber, irCameraHandle.DeviceType, packedImageTransferProcessorConfig.ExposureTime });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(309)]
+ // GetImageTransferProcessorState(pid, nn::irsensor::IrCameraHandle, nn::applet::AppletResourceUserId)
+ public ResultCode GetImageTransferProcessorState(ServiceCtx context)
+ {
+ IrCameraHandle irCameraHandle = context.RequestData.ReadStruct<IrCameraHandle>();
+ ulong appletResourceUserId = context.RequestData.ReadUInt64();
+
+ // ulong imageTransferBufferAddress = context.Request.ReceiveBuff[0].Position;
+ ulong imageTransferBufferSize = context.Request.ReceiveBuff[0].Size;
+
+ if (imageTransferBufferSize == 0)
+ {
+ return ResultCode.InvalidBufferSize;
+ }
+
+ CheckCameraHandle(irCameraHandle);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId, irCameraHandle.PlayerNumber, irCameraHandle.DeviceType });
+
+ // TODO: Uses the buffer to copy the JoyCon IR data (by using a JoyCon driver) and update the following struct.
+ context.ResponseData.WriteStruct(new ImageTransferProcessorState()
+ {
+ SamplingNumber = 0,
+ AmbientNoiseLevel = 0
+ });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(310)]
+ // RunTeraPluginProcessor(pid, nn::irsensor::IrCameraHandle, nn::applet::AppletResourceUserId, PackedTeraPluginProcessorConfig)
+ public ResultCode RunTeraPluginProcessor(ServiceCtx context)
+ {
+ IrCameraHandle irCameraHandle = context.RequestData.ReadStruct<IrCameraHandle>();
+ ulong appletResourceUserId = context.RequestData.ReadUInt64();
+ var packedTeraPluginProcessorConfig = context.RequestData.ReadStruct<PackedTeraPluginProcessorConfig>();
+
+ CheckCameraHandle(irCameraHandle);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId, irCameraHandle.PlayerNumber, irCameraHandle.DeviceType, packedTeraPluginProcessorConfig.RequiredMcuVersion });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(311)]
+ // GetNpadIrCameraHandle(u32) -> nn::irsensor::IrCameraHandle
+ public ResultCode GetNpadIrCameraHandle(ServiceCtx context)
+ {
+ NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadUInt32();
+
+ if (npadIdType > NpadIdType.Player8 &&
+ npadIdType != NpadIdType.Unknown &&
+ npadIdType != NpadIdType.Handheld)
+ {
+ return ResultCode.NpadIdOutOfRange;
+ }
+
+ PlayerIndex irCameraHandle = HidUtils.GetIndexFromNpadIdType(npadIdType);
+
+ context.ResponseData.Write((int)irCameraHandle);
+
+ // NOTE: If the irCameraHandle pointer is null this error is returned, Doesn't occur in our case.
+ // return ResultCode.HandlePointerIsNull;
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(314)] // 3.0.0+
+ // CheckFirmwareVersion(nn::irsensor::IrCameraHandle, nn::irsensor::PackedMcuVersion, nn::applet::AppletResourceUserId, pid)
+ public ResultCode CheckFirmwareVersion(ServiceCtx context)
+ {
+ int irCameraHandle = context.RequestData.ReadInt32();
+ short packedMcuVersionMajor = context.RequestData.ReadInt16();
+ short packedMcuVersionMinor = context.RequestData.ReadInt16();
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId, irCameraHandle, packedMcuVersionMajor, packedMcuVersionMinor });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(318)] // 4.0.0+
+ // StopImageProcessorAsync(nn::irsensor::IrCameraHandle, nn::applet::AppletResourceUserId, pid)
+ public ResultCode StopImageProcessorAsync(ServiceCtx context)
+ {
+ int irCameraHandle = context.RequestData.ReadInt32();
+ long appletResourceUserId = context.RequestData.ReadInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId, irCameraHandle });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(319)] // 4.0.0+
+ // ActivateIrsensorWithFunctionLevel(nn::applet::AppletResourceUserId, nn::irsensor::PackedFunctionLevel, pid)
+ public ResultCode ActivateIrsensorWithFunctionLevel(ServiceCtx context)
+ {
+ long appletResourceUserId = context.RequestData.ReadInt64();
+ long packedFunctionLevel = context.RequestData.ReadInt64();
+
+ Logger.Stub?.PrintStub(LogClass.ServiceIrs, new { appletResourceUserId, packedFunctionLevel });
+
+ return ResultCode.Success;
+ }
+
+ private ResultCode CheckCameraHandle(IrCameraHandle irCameraHandle)
+ {
+ if (irCameraHandle.DeviceType == 1 || (PlayerIndex)irCameraHandle.PlayerNumber >= PlayerIndex.Unknown)
+ {
+ return ResultCode.InvalidCameraHandle;
+ }
+
+ return ResultCode.Success;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Irs/IIrSensorSystemServer.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/IIrSensorSystemServer.cs
new file mode 100644
index 00000000..99fcd541
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/IIrSensorSystemServer.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Hid.Irs
+{
+ [Service("irs:sys")]
+ class IIrSensorSystemServer : IpcService
+ {
+ public IIrSensorSystemServer(ServiceCtx context) { }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Irs/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/ResultCode.cs
new file mode 100644
index 00000000..3afc03c2
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/ResultCode.cs
@@ -0,0 +1,15 @@
+namespace Ryujinx.HLE.HOS.Services.Hid.Irs
+{
+ public enum ResultCode
+ {
+ ModuleId = 205,
+ ErrorCodeShift = 9,
+
+ Success = 0,
+
+ InvalidCameraHandle = (204 << ErrorCodeShift) | ModuleId,
+ InvalidBufferSize = (207 << ErrorCodeShift) | ModuleId,
+ HandlePointerIsNull = (212 << ErrorCodeShift) | ModuleId,
+ NpadIdOutOfRange = (709 << ErrorCodeShift) | ModuleId
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/ImageTransferProcessorState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/ImageTransferProcessorState.cs
new file mode 100644
index 00000000..647aef64
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/ImageTransferProcessorState.cs
@@ -0,0 +1,12 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.Irs.Types
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x10)]
+ struct ImageTransferProcessorState
+ {
+ public ulong SamplingNumber;
+ public uint AmbientNoiseLevel;
+ public uint Reserved;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/IrCameraHandle.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/IrCameraHandle.cs
new file mode 100644
index 00000000..8ed7201e
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/IrCameraHandle.cs
@@ -0,0 +1,12 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.Irs.Types
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x4)]
+ struct IrCameraHandle
+ {
+ public byte PlayerNumber;
+ public byte DeviceType;
+ public ushort Reserved;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedClusteringProcessorConfig.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedClusteringProcessorConfig.cs
new file mode 100644
index 00000000..735f7822
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedClusteringProcessorConfig.cs
@@ -0,0 +1,25 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.Irs.Types
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x28)]
+ struct PackedClusteringProcessorConfig
+ {
+ public long ExposureTime;
+ public byte LightTarget;
+ public byte Gain;
+ public byte IsNegativeImageUsed;
+ public byte Reserved1;
+ public uint Reserved2;
+ public ushort WindowOfInterestX;
+ public ushort WindowOfInterestY;
+ public ushort WindowOfInterestWidth;
+ public ushort WindowOfInterestHeight;
+ public uint RequiredMcuVersion;
+ public uint ObjectPixelCountMin;
+ public uint ObjectPixelCountMax;
+ public byte ObjectIntensityMin;
+ public byte IsExternalLightFilterEnabled;
+ public ushort Reserved3;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedImageTransferProcessorConfig.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedImageTransferProcessorConfig.cs
new file mode 100644
index 00000000..094413e0
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedImageTransferProcessorConfig.cs
@@ -0,0 +1,19 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.Irs.Types
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x18)]
+ struct PackedImageTransferProcessorConfig
+ {
+ public long ExposureTime;
+ public byte LightTarget;
+ public byte Gain;
+ public byte IsNegativeImageUsed;
+ public byte Reserved1;
+ public uint Reserved2;
+ public uint RequiredMcuVersion;
+ public byte Format;
+ public byte Reserved3;
+ public ushort Reserved4;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedMomentProcessorConfig.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedMomentProcessorConfig.cs
new file mode 100644
index 00000000..a1b70b40
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedMomentProcessorConfig.cs
@@ -0,0 +1,23 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.Irs.Types
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x20)]
+ struct PackedMomentProcessorConfig
+ {
+ public long ExposureTime;
+ public byte LightTarget;
+ public byte Gain;
+ public byte IsNegativeImageUsed;
+ public byte Reserved1;
+ public uint Reserved2;
+ public ushort WindowOfInterestX;
+ public ushort WindowOfInterestY;
+ public ushort WindowOfInterestWidth;
+ public ushort WindowOfInterestHeight;
+ public uint RequiredMcuVersion;
+ public byte Preprocess;
+ public byte PreprocessIntensityThreshold;
+ public ushort Reserved3;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedTeraPluginProcessorConfig.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedTeraPluginProcessorConfig.cs
new file mode 100644
index 00000000..808b0b72
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Irs/Types/PackedTeraPluginProcessorConfig.cs
@@ -0,0 +1,14 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.Irs.Types
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x8)]
+ struct PackedTeraPluginProcessorConfig
+ {
+ public uint RequiredMcuVersion;
+ public byte Mode;
+ public byte Unknown1;
+ public byte Unknown2;
+ public byte Unknown3;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Hid/ResultCode.cs
new file mode 100644
index 00000000..9c87ac1d
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/ResultCode.cs
@@ -0,0 +1,15 @@
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ enum ResultCode
+ {
+ ModuleId = 202,
+ ErrorCodeShift = 9,
+
+ Success = 0,
+
+ 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/src/Ryujinx.HLE/HOS/Services/Hid/Types/AppletFooterUiType.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/AppletFooterUiType.cs
new file mode 100644
index 00000000..c4ff8d7e
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/AppletFooterUiType.cs
@@ -0,0 +1,30 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.Types
+{
+ [Flags]
+ enum AppletFooterUiType : byte
+ {
+ None,
+ HandheldNone,
+ HandheldJoyConLeftOnly,
+ HandheldJoyConRightOnly,
+ HandheldJoyConLeftJoyConRight,
+ JoyDual,
+ JoyDualLeftOnly,
+ JoyDualRightOnly,
+ JoyLeftHorizontal,
+ JoyLeftVertical,
+ JoyRightHorizontal,
+ JoyRightVertical,
+ SwitchProController,
+ CompatibleProController,
+ CompatibleJoyCon,
+ LarkHvc1,
+ LarkHvc2,
+ LarkNesLeft,
+ LarkNesRight,
+ Lucia,
+ Verification
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/HidVector.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/HidVector.cs
new file mode 100644
index 00000000..18d9fd9c
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/HidVector.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Services.Hid.Types
+{
+ struct HidVector
+ {
+ public float X;
+ public float Y;
+ public float Z;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/ControllerKeys.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/ControllerKeys.cs
new file mode 100644
index 00000000..c91636b2
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/ControllerKeys.cs
@@ -0,0 +1,45 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ [Flags]
+ public enum ControllerKeys : long
+ {
+ A = 1 << 0,
+ B = 1 << 1,
+ X = 1 << 2,
+ Y = 1 << 3,
+ LStick = 1 << 4,
+ RStick = 1 << 5,
+ L = 1 << 6,
+ R = 1 << 7,
+ Zl = 1 << 8,
+ Zr = 1 << 9,
+ Plus = 1 << 10,
+ Minus = 1 << 11,
+ DpadLeft = 1 << 12,
+ DpadUp = 1 << 13,
+ DpadRight = 1 << 14,
+ DpadDown = 1 << 15,
+ LStickLeft = 1 << 16,
+ LStickUp = 1 << 17,
+ LStickRight = 1 << 18,
+ LStickDown = 1 << 19,
+ RStickLeft = 1 << 20,
+ RStickUp = 1 << 21,
+ RStickRight = 1 << 22,
+ RStickDown = 1 << 23,
+ SlLeft = 1 << 24,
+ SrLeft = 1 << 25,
+ SlRight = 1 << 26,
+ SrRight = 1 << 27,
+
+ // Generic Catch-all
+ Up = DpadUp | LStickUp | RStickUp,
+ Down = DpadDown | LStickDown | RStickDown,
+ Left = DpadLeft | LStickLeft | RStickLeft,
+ Right = DpadRight | LStickRight | RStickRight,
+ Sl = SlLeft | SlRight,
+ Sr = SrLeft | SrRight
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/ControllerType.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/ControllerType.cs
new file mode 100644
index 00000000..b2d34e8e
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/ControllerType.cs
@@ -0,0 +1,19 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ [Flags]
+ public enum ControllerType : int
+ {
+ None,
+ ProController = 1 << 0,
+ Handheld = 1 << 1,
+ JoyconPair = 1 << 2,
+ JoyconLeft = 1 << 3,
+ JoyconRight = 1 << 4,
+ Invalid = 1 << 5,
+ Pokeball = 1 << 6,
+ SystemExternal = 1 << 29,
+ System = 1 << 30
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadColor.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadColor.cs
new file mode 100644
index 00000000..3c311e21
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadColor.cs
@@ -0,0 +1,37 @@
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ public enum NpadColor : uint
+ {
+ BodyGray = 0x828282,
+ BodyNeonRed = 0xFF3C28,
+ BodyNeonBlue = 0x0AB9E6,
+ BodyNeonYellow = 0xE6FF00,
+ BodyNeonGreen = 0x1EDC00,
+ BodyNeonPink = 0xFF3278,
+ BodyRed = 0xE10F00,
+ BodyBlue = 0x4655F5,
+ BodyNeonPurple = 0xB400E6,
+ BodyNeonOrange = 0xFAA005,
+ BodyPokemonLetsGoPikachu = 0xFFDC00,
+ BodyPokemonLetsGoEevee = 0xC88C32,
+ BodyNintendoLaboCreatorsContestEdition = 0xD7AA73,
+ BodyAnimalCrossingSpecialEditionLeftJoyCon = 0x82FF96,
+ BodyAnimalCrossingSpecialEditionRightJoyCon = 0x96F5F5,
+
+ ButtonGray = 0x0F0F0F,
+ ButtonNeonRed = 0x1E0A0A,
+ ButtonNeonBlue = 0x001E1E,
+ ButtonNeonYellow = 0x142800,
+ ButtonNeonGreen = 0x002800,
+ ButtonNeonPink = 0x28001E,
+ ButtonRed = 0x280A0A,
+ ButtonBlue = 0x00000A,
+ ButtonNeonPurple = 0x140014,
+ ButtonNeonOrange = 0x0F0A00,
+ ButtonPokemonLetsGoPikachu = 0x322800,
+ ButtonPokemonLetsGoEevee = 0x281900,
+ ButtonNintendoLaboCreatorsContestEdition = 0x1E1914,
+ ButtonAnimalCrossingSpecialEditionLeftJoyCon = 0x0A1E0A,
+ ButtonAnimalCrossingSpecialEditionRightJoyCon = 0x0A1E28
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadIdType.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadIdType.cs
new file mode 100644
index 00000000..1abd8468
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadIdType.cs
@@ -0,0 +1,16 @@
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ public enum NpadIdType : int
+ {
+ Player1 = 0,
+ Player2 = 1,
+ Player3 = 2,
+ Player4 = 3,
+ Player5 = 4,
+ Player6 = 5,
+ Player7 = 6,
+ Player8 = 7,
+ Unknown = 16,
+ Handheld = 32
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadStyleIndex.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/NpadStyleIndex.cs
new file mode 100644
index 00000000..ddf5d97f
--- /dev/null
+++ b/src/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/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/PlayerIndex.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/PlayerIndex.cs
new file mode 100644
index 00000000..f4ced5df
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/Npad/PlayerIndex.cs
@@ -0,0 +1,17 @@
+namespace Ryujinx.HLE.HOS.Services.Hid
+{
+ public enum PlayerIndex : int
+ {
+ Player1 = 0,
+ Player2 = 1,
+ Player3 = 2,
+ Player4 = 3,
+ Player5 = 4,
+ Player6 = 5,
+ Player7 = 6,
+ Player8 = 7,
+ Handheld = 8,
+ Unknown = 9,
+ Auto = 10 // Shouldn't be used directly
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/NpadJoyHoldType.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/NpadJoyHoldType.cs
new file mode 100644
index 00000000..d3b51a24
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/NpadJoyHoldType.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Hid.Types
+{
+ enum NpadJoyHoldType
+ {
+ Vertical,
+ Horizontal
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/AnalogStickState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/AnalogStickState.cs
new file mode 100644
index 00000000..bf4b5888
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/AnalogStickState.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common
+{
+ struct AnalogStickState
+ {
+ public int X;
+ public int Y;
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/AtomicStorage.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/AtomicStorage.cs
new file mode 100644
index 00000000..da53e421
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/AtomicStorage.cs
@@ -0,0 +1,26 @@
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common
+{
+ struct AtomicStorage<T> where T: unmanaged, ISampledDataStruct
+ {
+ public ulong SamplingNumber;
+ public T Object;
+
+ public ulong ReadSamplingNumberAtomic()
+ {
+ return Interlocked.Read(ref SamplingNumber);
+ }
+
+ public void SetObject(ref T obj)
+ {
+ ulong samplingNumber = ISampledDataStruct.GetSamplingNumber(ref obj);
+
+ Interlocked.Exchange(ref SamplingNumber, samplingNumber);
+
+ Thread.MemoryBarrier();
+
+ Object = obj;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/ISampledDataStruct.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/ISampledDataStruct.cs
new file mode 100644
index 00000000..a382c0c2
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/ISampledDataStruct.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Buffers.Binary;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common
+{
+ /// <summary>
+ /// This is a "marker interface" to add some compile-time safety to a convention-based optimization.
+ ///
+ /// Any struct implementing this interface should:
+ /// - use <c>StructLayoutAttribute</c> (and related attributes) to explicity control how the struct is laid out in memory.
+ /// - ensure that the method <c>ISampledDataStruct.GetSamplingNumberFieldOffset()</c> correctly returns the offset, in bytes,
+ /// to the ulong "Sampling Number" field within the struct. Most types have it as the first field, so the default offset is 0.
+ ///
+ /// Example:
+ ///
+ /// <c>
+ /// [StructLayout(LayoutKind.Sequential, Pack = 8)]
+ /// struct DebugPadState : ISampledDataStruct
+ /// {
+ /// public ulong SamplingNumber; // 1st field, so no need to add special handling to GetSamplingNumberFieldOffset()
+ /// // other members...
+ /// }
+ ///
+ /// [StructLayout(LayoutKind.Sequential, Pack = 8)]
+ /// struct SixAxisSensorState : ISampledDataStruct
+ /// {
+ /// public ulong DeltaTime;
+ /// public ulong SamplingNumber; // Not the first field - needs special handling in GetSamplingNumberFieldOffset()
+ /// // other members...
+ /// }
+ /// </c>
+ /// </summary>
+ internal interface ISampledDataStruct
+ {
+ // No Instance Members - marker interface only
+
+ public static ulong GetSamplingNumber<T>(ref T sampledDataStruct) where T : unmanaged, ISampledDataStruct
+ {
+ ReadOnlySpan<T> structSpan = MemoryMarshal.CreateReadOnlySpan(ref sampledDataStruct, 1);
+
+ ReadOnlySpan<byte> byteSpan = MemoryMarshal.Cast<T, byte>(structSpan);
+
+ int fieldOffset = GetSamplingNumberFieldOffset(ref sampledDataStruct);
+
+ if (fieldOffset > 0)
+ {
+ byteSpan = byteSpan.Slice(fieldOffset);
+ }
+
+ ulong value = BinaryPrimitives.ReadUInt64LittleEndian(byteSpan);
+
+ return value;
+ }
+
+ private static int GetSamplingNumberFieldOffset<T>(ref T sampledDataStruct) where T : unmanaged, ISampledDataStruct
+ {
+ return sampledDataStruct switch
+ {
+ Npad.SixAxisSensorState _ => sizeof(ulong),
+ _ => 0
+ };
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/RingLifo.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/RingLifo.cs
new file mode 100644
index 00000000..ae654d6f
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Common/RingLifo.cs
@@ -0,0 +1,149 @@
+using Ryujinx.Common.Memory;
+using System;
+using System.Runtime.CompilerServices;
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common
+{
+ struct RingLifo<T> where T: unmanaged, ISampledDataStruct
+ {
+ private const ulong MaxEntries = 17;
+
+#pragma warning disable CS0169
+ private ulong _unused;
+#pragma warning restore CS0169
+#pragma warning disable CS0414
+ private ulong _bufferCount;
+#pragma warning restore CS0414
+ private ulong _index;
+ private ulong _count;
+ private Array17<AtomicStorage<T>> _storage;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private ulong ReadCurrentIndex()
+ {
+ return Interlocked.Read(ref _index);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private ulong ReadCurrentCount()
+ {
+ return Interlocked.Read(ref _count);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static ulong GetNextIndexForWrite(ulong index)
+ {
+ return (index + 1) % MaxEntries;
+ }
+
+ public ref AtomicStorage<T> GetCurrentAtomicEntryRef()
+ {
+ ulong countAvailaible = Math.Min(Math.Max(0, ReadCurrentCount()), 1);
+
+ if (countAvailaible == 0)
+ {
+ _storage[0] = default;
+
+ return ref _storage[0];
+ }
+
+ ulong index = ReadCurrentIndex();
+
+ while (true)
+ {
+ int inputEntryIndex = (int)((index + MaxEntries + 1 - countAvailaible) % MaxEntries);
+
+ ref AtomicStorage<T> result = ref _storage[inputEntryIndex];
+
+ ulong samplingNumber0 = result.ReadSamplingNumberAtomic();
+ ulong samplingNumber1 = result.ReadSamplingNumberAtomic();
+
+ if (samplingNumber0 != samplingNumber1 && (result.SamplingNumber - result.SamplingNumber) != 1)
+ {
+ ulong tempCount = Math.Min(ReadCurrentCount(), countAvailaible);
+
+ countAvailaible = Math.Min(tempCount, 1);
+ index = ReadCurrentIndex();
+
+ continue;
+ }
+
+ return ref result;
+ }
+ }
+
+ public ref T GetCurrentEntryRef()
+ {
+ return ref GetCurrentAtomicEntryRef().Object;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ReadOnlySpan<AtomicStorage<T>> ReadEntries(uint maxCount)
+ {
+ ulong countAvailaible = Math.Min(Math.Max(0, ReadCurrentCount()), maxCount);
+
+ if (countAvailaible == 0)
+ {
+ return ReadOnlySpan<AtomicStorage<T>>.Empty;
+ }
+
+ ulong index = ReadCurrentIndex();
+
+ AtomicStorage<T>[] result = new AtomicStorage<T>[countAvailaible];
+
+ for (ulong i = 0; i < countAvailaible; i++)
+ {
+ int inputEntryIndex = (int)((index + MaxEntries + 1 - countAvailaible + i) % MaxEntries);
+ int outputEntryIndex = (int)(countAvailaible - i - 1);
+
+ ulong samplingNumber0 = _storage[inputEntryIndex].ReadSamplingNumberAtomic();
+ result[outputEntryIndex] = _storage[inputEntryIndex];
+ ulong samplingNumber1 = _storage[inputEntryIndex].ReadSamplingNumberAtomic();
+
+ if (samplingNumber0 != samplingNumber1 && (i > 0 && (result[outputEntryIndex].SamplingNumber - result[outputEntryIndex].SamplingNumber) != 1))
+ {
+ ulong tempCount = Math.Min(ReadCurrentCount(), countAvailaible);
+
+ countAvailaible = Math.Min(tempCount, maxCount);
+ index = ReadCurrentIndex();
+
+ i -= 1;
+ }
+ }
+
+ return result;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Write(ref T value)
+ {
+ ulong targetIndex = GetNextIndexForWrite(ReadCurrentIndex());
+
+ _storage[(int)targetIndex].SetObject(ref value);
+
+ Interlocked.Exchange(ref _index, targetIndex);
+
+ ulong count = ReadCurrentCount();
+
+ if (count < (MaxEntries - 1))
+ {
+ Interlocked.Increment(ref _count);
+ }
+ }
+
+ public void Clear()
+ {
+ Interlocked.Exchange(ref _count, 0);
+ Interlocked.Exchange(ref _index, 0);
+ }
+
+ public static RingLifo<T> Create()
+ {
+ return new RingLifo<T>
+ {
+ _bufferCount = MaxEntries
+ };
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadAttribute.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadAttribute.cs
new file mode 100644
index 00000000..ec5bd3c8
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadAttribute.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugPad
+{
+ [Flags]
+ enum DebugPadAttribute : uint
+ {
+ None = 0,
+ Connected = 1 << 0
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadButton.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadButton.cs
new file mode 100644
index 00000000..e8f28317
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadButton.cs
@@ -0,0 +1,24 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugPad
+{
+ [Flags]
+ enum DebugPadButton : uint
+ {
+ None = 0,
+ A = 1 << 0,
+ B = 1 << 1,
+ X = 1 << 2,
+ Y = 1 << 3,
+ L = 1 << 4,
+ R = 1 << 5,
+ ZL = 1 << 6,
+ ZR = 1 << 7,
+ Start = 1 << 8,
+ Select = 1 << 9,
+ Left = 1 << 10,
+ Up = 1 << 11,
+ Right = 1 << 12,
+ Down = 1 << 13
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadState.cs
new file mode 100644
index 00000000..0846cfc7
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/DebugPad/DebugPadState.cs
@@ -0,0 +1,15 @@
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugPad
+{
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ struct DebugPadState : ISampledDataStruct
+ {
+ public ulong SamplingNumber;
+ public DebugPadAttribute Attributes;
+ public DebugPadButton Buttons;
+ public AnalogStickState AnalogStickR;
+ public AnalogStickState AnalogStickL;
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardKey.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardKey.cs
new file mode 100644
index 00000000..22df7c79
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardKey.cs
@@ -0,0 +1,29 @@
+using Ryujinx.Common.Memory;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard
+{
+ struct KeyboardKey
+ {
+ public Array4<ulong> RawData;
+
+ public bool this[KeyboardKeyShift index]
+ {
+ get
+ {
+ return (RawData[(int)index / 64] & (1UL << ((int)index & 63))) != 0;
+ }
+ set
+ {
+ int arrayIndex = (int)index / 64;
+ ulong mask = 1UL << ((int)index & 63);
+
+ RawData[arrayIndex] &= ~mask;
+
+ if (value)
+ {
+ RawData[arrayIndex] |= mask;
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardKeyShift.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardKeyShift.cs
new file mode 100644
index 00000000..01c2bb30
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardKeyShift.cs
@@ -0,0 +1,138 @@
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard
+{
+ enum KeyboardKeyShift
+ {
+ A = 4,
+ B = 5,
+ C = 6,
+ D = 7,
+ E = 8,
+ F = 9,
+ G = 10,
+ H = 11,
+ I = 12,
+ J = 13,
+ K = 14,
+ L = 15,
+ M = 16,
+ N = 17,
+ O = 18,
+ P = 19,
+ Q = 20,
+ R = 21,
+ S = 22,
+ T = 23,
+ U = 24,
+ V = 25,
+ W = 26,
+ X = 27,
+ Y = 28,
+ Z = 29,
+ D1 = 30,
+ D2 = 31,
+ D3 = 32,
+ D4 = 33,
+ D5 = 34,
+ D6 = 35,
+ D7 = 36,
+ D8 = 37,
+ D9 = 38,
+ D0 = 39,
+ Return = 40,
+ Escape = 41,
+ Backspace = 42,
+ Tab = 43,
+ Space = 44,
+ Minus = 45,
+ Plus = 46,
+ OpenBracket = 47,
+ CloseBracket = 48,
+ Pipe = 49,
+ Tilde = 50,
+ Semicolon = 51,
+ Quote = 52,
+ Backquote = 53,
+ Comma = 54,
+ Period = 55,
+ Slash = 56,
+ CapsLock = 57,
+ F1 = 58,
+ F2 = 59,
+ F3 = 60,
+ F4 = 61,
+ F5 = 62,
+ F6 = 63,
+ F7 = 64,
+ F8 = 65,
+ F9 = 66,
+ F10 = 67,
+ F11 = 68,
+ F12 = 69,
+ PrintScreen = 70,
+ ScrollLock = 71,
+ Pause = 72,
+ Insert = 73,
+ Home = 74,
+ PageUp = 75,
+ Delete = 76,
+ End = 77,
+ PageDown = 78,
+ RightArrow = 79,
+ LeftArrow = 80,
+ DownArrow = 81,
+ UpArrow = 82,
+ NumLock = 83,
+ NumPadDivide = 84,
+ NumPadMultiply = 85,
+ NumPadSubtract = 86,
+ NumPadAdd = 87,
+ NumPadEnter = 88,
+ NumPad1 = 89,
+ NumPad2 = 90,
+ NumPad3 = 91,
+ NumPad4 = 92,
+ NumPad5 = 93,
+ NumPad6 = 94,
+ NumPad7 = 95,
+ NumPad8 = 96,
+ NumPad9 = 97,
+ NumPad0 = 98,
+ NumPadDot = 99,
+ Backslash = 100,
+ Application = 101,
+ Power = 102,
+ NumPadEquals = 103,
+ F13 = 104,
+ F14 = 105,
+ F15 = 106,
+ F16 = 107,
+ F17 = 108,
+ F18 = 109,
+ F19 = 110,
+ F20 = 111,
+ F21 = 112,
+ F22 = 113,
+ F23 = 114,
+ F24 = 115,
+ NumPadComma = 133,
+ Ro = 135,
+ KatakanaHiragana = 136,
+ Yen = 137,
+ Henkan = 138,
+ Muhenkan = 139,
+ NumPadCommaPc98 = 140,
+ HangulEnglish = 144,
+ Hanja = 145,
+ Katakana = 146,
+ Hiragana = 147,
+ ZenkakuHankaku = 148,
+ LeftControl = 224,
+ LeftShift = 225,
+ LeftAlt = 226,
+ LeftGui = 227,
+ RightControl = 228,
+ RightShift = 229,
+ RightAlt = 230,
+ RightGui = 231
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardModifier.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardModifier.cs
new file mode 100644
index 00000000..839a4e82
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardModifier.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard
+{
+ [Flags]
+ enum KeyboardModifier : ulong
+ {
+ None = 0,
+ Control = 1 << 0,
+ Shift = 1 << 1,
+ LeftAlt = 1 << 2,
+ RightAlt = 1 << 3,
+ Gui = 1 << 4,
+ CapsLock = 1 << 8,
+ ScrollLock = 1 << 9,
+ NumLock = 1 << 10,
+ Katakana = 1 << 11,
+ Hiragana = 1 << 12
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardState.cs
new file mode 100644
index 00000000..4de92813
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Keyboard/KeyboardState.cs
@@ -0,0 +1,13 @@
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard
+{
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ struct KeyboardState : ISampledDataStruct
+ {
+ public ulong SamplingNumber;
+ public KeyboardModifier Modifiers;
+ public KeyboardKey Keys;
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseAttribute.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseAttribute.cs
new file mode 100644
index 00000000..5ffba0d7
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseAttribute.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse
+{
+ [Flags]
+ enum MouseAttribute : uint
+ {
+ None = 0,
+ Transferable = 1 << 0,
+ IsConnected = 1 << 1
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseButton.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseButton.cs
new file mode 100644
index 00000000..7e35140c
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseButton.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse
+{
+ [Flags]
+ enum MouseButton : uint
+ {
+ None = 0,
+ Left = 1 << 0,
+ Right = 1 << 1,
+ Middle = 1 << 2,
+ Forward = 1 << 3,
+ Back = 1 << 4
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseState.cs
new file mode 100644
index 00000000..c953c794
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Mouse/MouseState.cs
@@ -0,0 +1,19 @@
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse
+{
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ struct MouseState : ISampledDataStruct
+ {
+ public ulong SamplingNumber;
+ public int X;
+ public int Y;
+ public int DeltaX;
+ public int DeltaY;
+ public int WheelDeltaX;
+ public int WheelDeltaY;
+ public MouseButton Buttons;
+ public MouseAttribute Attributes;
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/DeviceType.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/DeviceType.cs
new file mode 100644
index 00000000..b0201835
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/DeviceType.cs
@@ -0,0 +1,29 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
+{
+ [Flags]
+ enum DeviceType : int
+ {
+ None = 0,
+
+ FullKey = 1 << 0,
+ DebugPad = 1 << 1,
+ HandheldLeft = 1 << 2,
+ HandheldRight = 1 << 3,
+ JoyLeft = 1 << 4,
+ JoyRight = 1 << 5,
+ Palma = 1 << 6,
+ LarkHvcLeft = 1 << 7,
+ LarkHvcRight = 1 << 8,
+ LarkNesLeft = 1 << 9,
+ LarkNesRight = 1 << 10,
+ HandheldLarkHvcLeft = 1 << 11,
+ HandheldLarkHvcRight = 1 << 12,
+ HandheldLarkNesLeft = 1 << 13,
+ HandheldLarkNesRight = 1 << 14,
+ Lucia = 1 << 15,
+
+ System = 1 << 31
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadAttribute.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadAttribute.cs
new file mode 100644
index 00000000..0960b7bf
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadAttribute.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
+{
+ [Flags]
+ enum NpadAttribute : uint
+ {
+ None = 0,
+ IsConnected = 1 << 0,
+ IsWired = 1 << 1,
+ IsLeftConnected = 1 << 2,
+ IsLeftWired = 1 << 3,
+ IsRightConnected = 1 << 4,
+ IsRightWired = 1 << 5
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadBatteryLevel.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadBatteryLevel.cs
new file mode 100644
index 00000000..477dfd10
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadBatteryLevel.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
+{
+ enum NpadBatteryLevel : int
+ {
+ Percent0,
+ Percent25,
+ Percent50,
+ Percent75,
+ Percent100
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadButton.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadButton.cs
new file mode 100644
index 00000000..5b3e13a7
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadButton.cs
@@ -0,0 +1,44 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
+{
+ [Flags]
+ enum NpadButton : ulong
+ {
+ None = 0,
+ A = 1 << 0,
+ B = 1 << 1,
+ X = 1 << 2,
+ Y = 1 << 3,
+ StickL = 1 << 4,
+ StickR = 1 << 5,
+ L = 1 << 6,
+ R = 1 << 7,
+ ZL = 1 << 8,
+ ZR = 1 << 9,
+ Plus = 1 << 10,
+ Minus = 1 << 11,
+ Left = 1 << 12,
+ Up = 1 << 13,
+ Right = 1 << 14,
+ Down = 1 << 15,
+ StickLLeft = 1 << 16,
+ StickLUp = 1 << 17,
+ StickLRight = 1 << 18,
+ StickLDown = 1 << 19,
+ StickRLeft = 1 << 20,
+ StickRUp = 1 << 21,
+ StickRRight = 1 << 22,
+ StickRDown = 1 << 23,
+ LeftSL = 1 << 24,
+ LeftSR = 1 << 25,
+ RightSL = 1 << 26,
+ RightSR = 1 << 27,
+ Palma = 1 << 28,
+
+ // FIXME: Probably a button on Lark.
+ Unknown29 = 1 << 29,
+
+ HandheldLeftB = 1 << 30
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadColorAttribute.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadColorAttribute.cs
new file mode 100644
index 00000000..1e547cc8
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadColorAttribute.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
+{
+ enum NpadColorAttribute : uint
+ {
+ Ok,
+ ReadError,
+ NoController
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadCommonState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadCommonState.cs
new file mode 100644
index 00000000..64f75ce9
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadCommonState.cs
@@ -0,0 +1,16 @@
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
+{
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ struct NpadCommonState : ISampledDataStruct
+ {
+ public ulong SamplingNumber;
+ public NpadButton Buttons;
+ public AnalogStickState AnalogStickL;
+ public AnalogStickState AnalogStickR;
+ public NpadAttribute Attributes;
+ private uint _reserved;
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadFullKeyColorState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadFullKeyColorState.cs
new file mode 100644
index 00000000..990eafb2
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadFullKeyColorState.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
+{
+ struct NpadFullKeyColorState
+ {
+ public NpadColorAttribute Attribute;
+ public uint FullKeyBody;
+ public uint FullKeyButtons;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadGcTriggerState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadGcTriggerState.cs
new file mode 100644
index 00000000..bddd6212
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadGcTriggerState.cs
@@ -0,0 +1,15 @@
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
+{
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ struct NpadGcTriggerState : ISampledDataStruct
+ {
+#pragma warning disable CS0649
+ public ulong SamplingNumber;
+ public uint TriggerL;
+ public uint TriggerR;
+#pragma warning restore CS0649
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadInternalState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadInternalState.cs
new file mode 100644
index 00000000..b009f95e
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadInternalState.cs
@@ -0,0 +1,65 @@
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
+{
+ struct NpadInternalState
+ {
+ public NpadStyleTag StyleSet;
+ public NpadJoyAssignmentMode JoyAssignmentMode;
+ public NpadFullKeyColorState FullKeyColor;
+ public NpadJoyColorState JoyColor;
+ public RingLifo<NpadCommonState> FullKey;
+ public RingLifo<NpadCommonState> Handheld;
+ public RingLifo<NpadCommonState> JoyDual;
+ public RingLifo<NpadCommonState> JoyLeft;
+ public RingLifo<NpadCommonState> JoyRight;
+ public RingLifo<NpadCommonState> Palma;
+ public RingLifo<NpadCommonState> SystemExt;
+ public RingLifo<SixAxisSensorState> FullKeySixAxisSensor;
+ public RingLifo<SixAxisSensorState> HandheldSixAxisSensor;
+ public RingLifo<SixAxisSensorState> JoyDualSixAxisSensor;
+ public RingLifo<SixAxisSensorState> JoyDualRightSixAxisSensor;
+ public RingLifo<SixAxisSensorState> JoyLeftSixAxisSensor;
+ public RingLifo<SixAxisSensorState> JoyRightSixAxisSensor;
+ public DeviceType DeviceType;
+ private uint _reserved1;
+ public NpadSystemProperties SystemProperties;
+ public NpadSystemButtonProperties SystemButtonProperties;
+ public NpadBatteryLevel BatteryLevelJoyDual;
+ public NpadBatteryLevel BatteryLevelJoyLeft;
+ public NpadBatteryLevel BatteryLevelJoyRight;
+ public uint AppletFooterUiAttributes;
+ public AppletFooterUiType AppletFooterUiType;
+ private Reserved2Struct _reserved2;
+ public RingLifo<NpadGcTriggerState> GcTrigger;
+ public NpadLarkType LarkTypeLeftAndMain;
+ public NpadLarkType LarkTypeRight;
+ public NpadLuciaType LuciaType;
+ public uint Unknown43EC;
+
+ [StructLayout(LayoutKind.Sequential, Size = 123, Pack = 1)]
+ private struct Reserved2Struct {}
+
+ public static NpadInternalState Create()
+ {
+ return new NpadInternalState
+ {
+ FullKey = RingLifo<NpadCommonState>.Create(),
+ Handheld = RingLifo<NpadCommonState>.Create(),
+ JoyDual = RingLifo<NpadCommonState>.Create(),
+ JoyLeft = RingLifo<NpadCommonState>.Create(),
+ JoyRight = RingLifo<NpadCommonState>.Create(),
+ Palma = RingLifo<NpadCommonState>.Create(),
+ SystemExt = RingLifo<NpadCommonState>.Create(),
+ FullKeySixAxisSensor = RingLifo<SixAxisSensorState>.Create(),
+ HandheldSixAxisSensor = RingLifo<SixAxisSensorState>.Create(),
+ JoyDualSixAxisSensor = RingLifo<SixAxisSensorState>.Create(),
+ JoyDualRightSixAxisSensor = RingLifo<SixAxisSensorState>.Create(),
+ JoyLeftSixAxisSensor = RingLifo<SixAxisSensorState>.Create(),
+ JoyRightSixAxisSensor = RingLifo<SixAxisSensorState>.Create(),
+ GcTrigger = RingLifo<NpadGcTriggerState>.Create(),
+ };
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadJoyAssignmentMode.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadJoyAssignmentMode.cs
new file mode 100644
index 00000000..871c4c5a
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadJoyAssignmentMode.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
+{
+ enum NpadJoyAssignmentMode : uint
+ {
+ Dual,
+ Single
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadJoyColorState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadJoyColorState.cs
new file mode 100644
index 00000000..3986dd5e
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadJoyColorState.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
+{
+ struct NpadJoyColorState
+ {
+ public NpadColorAttribute Attribute;
+ public uint LeftBody;
+ public uint LeftButtons;
+ public uint RightBody;
+ public uint RightButtons;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadLarkType.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadLarkType.cs
new file mode 100644
index 00000000..a487a911
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadLarkType.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
+{
+ enum NpadLarkType : uint
+ {
+ Invalid,
+ H1,
+ H2,
+ NL,
+ NR
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadLuciaType.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadLuciaType.cs
new file mode 100644
index 00000000..95148485
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadLuciaType.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
+{
+ enum NpadLuciaType
+ {
+ Invalid,
+ J,
+ E,
+ U
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadState.cs
new file mode 100644
index 00000000..ed9e7c0d
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadState.cs
@@ -0,0 +1,18 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x5000)]
+ struct NpadState
+ {
+ public NpadInternalState InternalState;
+
+ public static NpadState Create()
+ {
+ return new NpadState
+ {
+ InternalState = NpadInternalState.Create()
+ };
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadStyleTag.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadStyleTag.cs
new file mode 100644
index 00000000..f31978e2
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadStyleTag.cs
@@ -0,0 +1,76 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
+{
+ /// <summary>
+ /// Nintendo pad style
+ /// </summary>
+ [Flags]
+ enum NpadStyleTag : uint
+ {
+ /// <summary>
+ /// No type.
+ /// </summary>
+ None = 0,
+
+ /// <summary>
+ /// Pro controller.
+ /// </summary>
+ FullKey = 1 << 0,
+
+ /// <summary>
+ /// Joy-Con controller in handheld mode.
+ /// </summary>
+ Handheld = 1 << 1,
+
+ /// <summary>
+ /// Joy-Con controller in dual mode.
+ /// </summary>
+ JoyDual = 1 << 2,
+
+ /// <summary>
+ /// Joy-Con left controller in single mode.
+ /// </summary>
+ JoyLeft = 1 << 3,
+
+ /// <summary>
+ /// Joy-Con right controller in single mode.
+ /// </summary>
+ JoyRight = 1 << 4,
+
+ /// <summary>
+ /// GameCube controller.
+ /// </summary>
+ Gc = 1 << 5,
+
+ /// <summary>
+ /// Poké Ball Plus controller.
+ /// </summary>
+ Palma = 1 << 6,
+
+ /// <summary>
+ /// NES and Famicom controller.
+ /// </summary>
+ Lark = 1 << 7,
+
+ /// <summary>
+ /// NES and Famicom controller in handheld mode.
+ /// </summary>
+ HandheldLark = 1 << 8,
+
+ /// <summary>
+ /// SNES controller.
+ /// </summary>
+ Lucia = 1 << 9,
+
+ /// <summary>
+ /// Generic external controller.
+ /// </summary>
+ SystemExt = 1 << 29,
+
+ /// <summary>
+ /// Generic controller.
+ /// </summary>
+ System = 1 << 30
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadSystemButtonProperties.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadSystemButtonProperties.cs
new file mode 100644
index 00000000..68603271
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadSystemButtonProperties.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
+{
+ [Flags]
+ enum NpadSystemButtonProperties : uint
+ {
+ None = 0,
+ IsUnintendedHomeButtonInputProtectionEnabled = 1 << 0
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadSystemProperties.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadSystemProperties.cs
new file mode 100644
index 00000000..13444555
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/NpadSystemProperties.cs
@@ -0,0 +1,24 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
+{
+ [Flags]
+ enum NpadSystemProperties : ulong
+ {
+ None = 0,
+
+ IsChargingJoyDual = 1 << 0,
+ IsChargingJoyLeft = 1 << 1,
+ IsChargingJoyRight = 1 << 2,
+ IsPoweredJoyDual = 1 << 3,
+ IsPoweredJoyLeft = 1 << 4,
+ IsPoweredJoyRight = 1 << 5,
+ IsUnsuportedButtonPressedOnNpadSystem = 1 << 9,
+ IsUnsuportedButtonPressedOnNpadSystemExt = 1 << 10,
+ IsAbxyButtonOriented = 1 << 11,
+ IsSlSrButtonOriented = 1 << 12,
+ IsPlusAvailable = 1 << 13,
+ IsMinusAvailable = 1 << 14,
+ IsDirectionalButtonsAvailable = 1 << 15
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/SixAxisSensorAttribute.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/SixAxisSensorAttribute.cs
new file mode 100644
index 00000000..7ed46d98
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/SixAxisSensorAttribute.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
+{
+ [Flags]
+ enum SixAxisSensorAttribute : uint
+ {
+ None = 0,
+ IsConnected = 1 << 0,
+ IsInterpolated = 1 << 1
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/SixAxisSensorState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/SixAxisSensorState.cs
new file mode 100644
index 00000000..18be3276
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/Npad/SixAxisSensorState.cs
@@ -0,0 +1,19 @@
+using Ryujinx.Common.Memory;
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
+{
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ struct SixAxisSensorState : ISampledDataStruct
+ {
+ public ulong DeltaTime;
+ public ulong SamplingNumber;
+ public HidVector Acceleration;
+ public HidVector AngularVelocity;
+ public HidVector Angle;
+ public Array9<float> Direction;
+ public SixAxisSensorAttribute Attributes;
+ private uint _reserved;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/SharedMemory.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/SharedMemory.cs
new file mode 100644
index 00000000..48acfc3f
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/SharedMemory.cs
@@ -0,0 +1,66 @@
+using Ryujinx.Common.Memory;
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugPad;
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard;
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse;
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad;
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.TouchScreen;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory
+{
+ /// <summary>
+ /// Represent the shared memory shared between applications for input.
+ /// </summary>
+ [StructLayout(LayoutKind.Explicit, Size = 0x40000)]
+ struct SharedMemory
+ {
+ /// <summary>
+ /// Debug controller.
+ /// </summary>
+ [FieldOffset(0)]
+ public RingLifo<DebugPadState> DebugPad;
+
+ /// <summary>
+ /// Touchscreen.
+ /// </summary>
+ [FieldOffset(0x400)]
+ public RingLifo<TouchScreenState> TouchScreen;
+
+ /// <summary>
+ /// Mouse.
+ /// </summary>
+ [FieldOffset(0x3400)]
+ public RingLifo<MouseState> Mouse;
+
+ /// <summary>
+ /// Keyboard.
+ /// </summary>
+ [FieldOffset(0x3800)]
+ public RingLifo<KeyboardState> Keyboard;
+
+ /// <summary>
+ /// Nintendo Pads.
+ /// </summary>
+ [FieldOffset(0x9A00)]
+ public Array10<NpadState> Npads;
+
+ public static SharedMemory Create()
+ {
+ SharedMemory result = new SharedMemory
+ {
+ DebugPad = RingLifo<DebugPadState>.Create(),
+ TouchScreen = RingLifo<TouchScreenState>.Create(),
+ Mouse = RingLifo<MouseState>.Create(),
+ Keyboard = RingLifo<KeyboardState>.Create(),
+ };
+
+ for (int i = 0; i < result.Npads.Length; i++)
+ {
+ result.Npads[i] = NpadState.Create();
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchAttribute.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchAttribute.cs
new file mode 100644
index 00000000..d2c5726a
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchAttribute.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.TouchScreen
+{
+ [Flags]
+ public enum TouchAttribute : uint
+ {
+ None = 0,
+ Start = 1 << 0,
+ End = 1 << 1
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchScreenState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchScreenState.cs
new file mode 100644
index 00000000..cdd4cc45
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchScreenState.cs
@@ -0,0 +1,15 @@
+using Ryujinx.Common.Memory;
+using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.TouchScreen
+{
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ struct TouchScreenState : ISampledDataStruct
+ {
+ public ulong SamplingNumber;
+ public int TouchesCount;
+ private int _reserved;
+ public Array16<TouchState> Touches;
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchState.cs b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchState.cs
new file mode 100644
index 00000000..ba621a2b
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Hid/Types/SharedMemory/TouchScreen/TouchState.cs
@@ -0,0 +1,19 @@
+namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.TouchScreen
+{
+ struct TouchState
+ {
+ public ulong DeltaTime;
+#pragma warning disable CS0649
+ public TouchAttribute Attribute;
+#pragma warning restore CS0649
+ public uint FingerId;
+ public uint X;
+ public uint Y;
+ public uint DiameterX;
+ public uint DiameterY;
+ public uint RotationAngle;
+#pragma warning disable CS0169
+ private uint _reserved;
+#pragma warning restore CS0169
+ }
+}