Move solution and projects to src

This commit is contained in:
TSR Berry
2023-04-08 01:22:00 +02:00
committed by Mary
parent cd124bda58
commit cee7121058
3466 changed files with 55 additions and 55 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public struct ControllerConfig
{
public PlayerIndex Player;
public ControllerType Type;
}
}

View File

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

View File

@@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public struct JoystickPosition
{
public int Dx;
public int Dy;
}
}

View File

@@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public struct KeyboardInput
{
public int Modifier;
public ulong[] Keys;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,9 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public enum NpadHandheldActivationMode
{
Dual,
Single,
None
}
}

View File

@@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public enum NpadJoyDeviceType
{
Left,
Right
}
}

View File

@@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public struct AccelerometerParameters
{
public float X;
public float Y;
}
}

View File

@@ -0,0 +1,9 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public enum GyroscopeZeroDriftMode
{
Loose,
Standard,
Tight
}
}

View File

@@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public struct SensorFusionParameters
{
public float RevisePower;
public float ReviseRange;
}
}

View File

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

View File

@@ -0,0 +1,9 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public enum VibrationDevicePosition
{
None,
Left,
Right
}
}

View File

@@ -0,0 +1,9 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public enum VibrationDeviceType
{
None,
LinearResonantActuator,
GcErm
}
}

View File

@@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public struct VibrationDeviceValue
{
public VibrationDeviceType DeviceType;
public VibrationDevicePosition Position;
}
}

View File

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

View File

@@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
[Service("hid:dbg")]
class IHidDebugServer : IpcService
{
public IHidDebugServer(ServiceCtx context) { }
}
}

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
[Service("hidbus")]
class IHidbusServer : IpcService
{
public IHidbusServer(ServiceCtx context) { }
}
}

View File

@@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
[Service("xcd:sys")]
class ISystemServer : IpcService
{
public ISystemServer(ServiceCtx context) { }
}
}

View File

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

View File

@@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Hid.Irs
{
[Service("irs:sys")]
class IIrSensorSystemServer : IpcService
{
public IIrSensorSystemServer(ServiceCtx context) { }
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,9 @@
namespace Ryujinx.HLE.HOS.Services.Hid.Types
{
struct HidVector
{
public float X;
public float Y;
public float Z;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Hid.Types
{
enum NpadJoyHoldType
{
Vertical,
Horizontal
}
}

View File

@@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common
{
struct AnalogStickState
{
public int X;
public int Y;
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,11 @@
using System;
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugPad
{
[Flags]
enum DebugPadAttribute : uint
{
None = 0,
Connected = 1 << 0
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,11 @@
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
{
enum NpadBatteryLevel : int
{
Percent0,
Percent25,
Percent50,
Percent75,
Percent100
}
}

View File

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

View File

@@ -0,0 +1,9 @@
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
{
enum NpadColorAttribute : uint
{
Ok,
ReadError,
NoController
}
}

View File

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

View File

@@ -0,0 +1,9 @@
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
{
struct NpadFullKeyColorState
{
public NpadColorAttribute Attribute;
public uint FullKeyBody;
public uint FullKeyButtons;
}
}

View File

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

View File

@@ -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(),
};
}
}
}

View File

@@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
{
enum NpadJoyAssignmentMode : uint
{
Dual,
Single
}
}

View File

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

View File

@@ -0,0 +1,11 @@
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
{
enum NpadLarkType : uint
{
Invalid,
H1,
H2,
NL,
NR
}
}

View File

@@ -0,0 +1,10 @@
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
{
enum NpadLuciaType
{
Invalid,
J,
E,
U
}
}

View File

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

View File

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

View File

@@ -0,0 +1,11 @@
using System;
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
{
[Flags]
enum NpadSystemButtonProperties : uint
{
None = 0,
IsUnintendedHomeButtonInputProtectionEnabled = 1 << 0
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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