diff options
| author | TSR Berry <20988865+TSRBerry@users.noreply.github.com> | 2023-04-08 01:22:00 +0200 |
|---|---|---|
| committer | Mary <thog@protonmail.com> | 2023-04-27 23:51:14 +0200 |
| commit | cee712105850ac3385cd0091a923438167433f9f (patch) | |
| tree | 4a5274b21d8b7f938c0d0ce18736d3f2993b11b1 /src/Ryujinx.HLE/HOS/Services/Hid | |
| parent | cd124bda587ef09668a971fa1cac1c3f0cfc9f21 (diff) | |
Move solution and projects to src
Diffstat (limited to 'src/Ryujinx.HLE/HOS/Services/Hid')
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 + } +} |
