HID SharedMem Rework (#1003)

* Delete old HLE.Input

* Add new HLE Input.

git shows Hid.cs as modified because of the same name. It is new.

* Change HID Service

* Change Ryujinx UI to reflect new Input

* Add basic ControllerApplet

* Add DebugPad

Should fix Kirby Star Allies

* Address Ac_K's comments

* Moved all of HLE.Input to Services.Hid
* Separated all structs and enums each to a file
* Removed vars
* Made some naming changes to align with switchbrew
* Added official joycon colors

As an aside, fixed a mistake in touchscreen headers and added checks to
important SharedMem structs at init time.

* Further address Ac_K's comments

* Addressed gdkchan's and some more Ac_K's comments

* Address AcK's review comments

* Address AcK's second review comments

* Replace missed Marshal.SizeOf and address gdkchan's comments
This commit is contained in:
mageven 2020-04-03 05:40:02 +05:30 committed by GitHub
parent 5b5239ab5b
commit 2365ddfc36
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
105 changed files with 1500 additions and 1044 deletions

View file

@ -1,6 +1,7 @@
using ARMeilleure.Memory; using ARMeilleure.Memory;
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
namespace Ryujinx.HLE namespace Ryujinx.HLE
{ {
@ -64,6 +65,11 @@ namespace Ryujinx.HLE
return Marshal.PtrToStructure<T>((IntPtr)(_ramPtr + position)); return Marshal.PtrToStructure<T>((IntPtr)(_ramPtr + position));
} }
public unsafe ref T GetStructRef<T>(long position)
{
return ref Unsafe.AsRef<T>((void*)(IntPtr)(_ramPtr + position));
}
public void WriteSByte(long position, sbyte value) public void WriteSByte(long position, sbyte value)
{ {
WriteByte(position, (byte)value); WriteByte(position, (byte)value);

View file

@ -0,0 +1,15 @@
using System;
using System.Runtime.CompilerServices;
namespace Ryujinx.HLE.Exceptions
{
public class InvalidStructLayoutException<T> : Exception
{
static readonly Type _structType = typeof(T);
public InvalidStructLayoutException(string message) : base(message) {}
public InvalidStructLayoutException(int expectedSize) :
base($"Type {_structType.Name} has the wrong size. Expected: {expectedSize} bytes, Got: {Unsafe.SizeOf<T>()} bytes") {}
}
}

View file

@ -13,6 +13,7 @@ namespace Ryujinx.HLE.HOS.Applets
_appletMapping = new Dictionary<AppletId, Type> _appletMapping = new Dictionary<AppletId, Type>
{ {
{ AppletId.PlayerSelect, typeof(PlayerSelectApplet) }, { AppletId.PlayerSelect, typeof(PlayerSelectApplet) },
{ AppletId.Controller, typeof(ControllerApplet) },
{ AppletId.SoftwareKeyboard, typeof(SoftwareKeyboardApplet) } { AppletId.SoftwareKeyboard, typeof(SoftwareKeyboardApplet) }
}; };
} }

View file

@ -0,0 +1,114 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Hid;
using Ryujinx.HLE.HOS.Services.Am.AppletAE;
using static Ryujinx.HLE.HOS.Services.Hid.HidServer.HidUtils;
namespace Ryujinx.HLE.HOS.Applets
{
internal class ControllerApplet : IApplet
{
private Horizon _system;
private AppletSession _normalSession;
public event EventHandler AppletStateChanged;
public ControllerApplet(Horizon system)
{
_system = system;
}
unsafe public ResultCode Start(AppletSession normalSession,
AppletSession interactiveSession)
{
_normalSession = normalSession;
byte[] launchParams = _normalSession.Pop();
byte[] controllerSupportArgPrivate = _normalSession.Pop();
ControllerSupportArgPrivate privateArg = IApplet.ReadStruct<ControllerSupportArgPrivate>(controllerSupportArgPrivate);
Logger.PrintStub(LogClass.ServiceHid, $"ControllerApplet ArgPriv {privateArg.PrivateSize} {privateArg.ArgSize} {privateArg.Mode}" +
$"HoldType:{(NpadJoyHoldType)privateArg.NpadJoyHoldType} StyleSets:{(ControllerType)privateArg.NpadStyleSet}");
if (privateArg.Mode != ControllerSupportMode.ShowControllerSupport)
{
_normalSession.Push(BuildResponse()); // Dummy response for other modes
AppletStateChanged?.Invoke(this, null);
return ResultCode.Success;
}
byte[] controllerSupportArg = _normalSession.Pop();
ControllerSupportArgHeader argHeader;
if (privateArg.ArgSize == Marshal.SizeOf<ControllerSupportArg>())
{
ControllerSupportArg arg = IApplet.ReadStruct<ControllerSupportArg>(controllerSupportArg);
argHeader = arg.Header;
// Read enable text here?
}
else
{
Logger.PrintStub(LogClass.ServiceHid, $"Unknown revision of ControllerSupportArg.");
argHeader = IApplet.ReadStruct<ControllerSupportArgHeader>(controllerSupportArg); // Read just the header
}
Logger.PrintStub(LogClass.ServiceHid, $"ControllerApplet Arg {argHeader.PlayerCountMin} {argHeader.PlayerCountMax} {argHeader.EnableTakeOverConnection} {argHeader.EnableSingleMode}");
// Currently, the only purpose of this applet is to help
// choose the primary input controller for the game
// TODO: Ideally should hook back to HID.Controller. When applet is called, can choose appropriate controller and attach to appropriate id.
if (argHeader.PlayerCountMin > 1)
{
Logger.PrintWarning(LogClass.ServiceHid, "More than one controller was requested.");
}
ControllerSupportResultInfo result = new ControllerSupportResultInfo
{
PlayerCount = 1,
SelectedId = (uint)GetNpadIdTypeFromIndex(_system.Device.Hid.Npads.PrimaryController)
};
Logger.PrintStub(LogClass.ServiceHid, $"ControllerApplet ReturnResult {result.PlayerCount} {result.SelectedId}");
_normalSession.Push(BuildResponse(result));
AppletStateChanged?.Invoke(this, null);
return ResultCode.Success;
}
public ResultCode GetResult()
{
return ResultCode.Success;
}
private byte[] BuildResponse(ControllerSupportResultInfo result)
{
using (MemoryStream stream = new MemoryStream())
using (BinaryWriter writer = new BinaryWriter(stream))
{
writer.Write(MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref result, Unsafe.SizeOf<ControllerSupportResultInfo>())));
return stream.ToArray();
}
}
private byte[] BuildResponse()
{
using (MemoryStream stream = new MemoryStream())
using (BinaryWriter writer = new BinaryWriter(stream))
{
writer.Write((ulong)ResultCode.Success);
return stream.ToArray();
}
}
}
}

View file

@ -0,0 +1,11 @@
namespace Ryujinx.HLE.HOS.Applets
{
// (8.0.0+ version)
unsafe struct ControllerSupportArg
{
public ControllerSupportArgHeader Header;
public fixed uint IdentificationColor[8];
public byte EnableExplainText;
public fixed byte ExplainText[8 * 0x81];
}
}

View file

@ -0,0 +1,13 @@
namespace Ryujinx.HLE.HOS.Applets
{
struct ControllerSupportArgHeader
{
public sbyte PlayerCountMin;
public sbyte PlayerCountMax;
public byte EnableTakeOverConnection;
public byte EnableLeftJustify;
public byte EnablePermitJoyDual;
public byte EnableSingleMode;
public byte EnableIdentificationColor;
}
}

View file

@ -0,0 +1,14 @@
namespace Ryujinx.HLE.HOS.Applets
{
struct ControllerSupportArgPrivate
{
public uint PrivateSize;
public uint ArgSize;
public byte Flag0;
public byte Flag1;
public ControllerSupportMode Mode;
public byte ControllerSupportCaller;
public uint NpadStyleSet;
public uint NpadJoyHoldType;
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.HLE.HOS.Applets
{
enum ControllerSupportMode : byte
{
ShowControllerSupport = 0,
ShowControllerStrapGuide = 1,
ShowControllerFirmwareUpdate = 2
}
}

View file

@ -0,0 +1,10 @@
namespace Ryujinx.HLE.HOS.Applets
{
unsafe struct ControllerSupportResultInfo
{
public sbyte PlayerCount;
fixed byte _padding[3];
public uint SelectedId;
public uint Result;
}
}

View file

@ -1,5 +1,6 @@
using Ryujinx.HLE.HOS.Services.Am.AppletAE; using Ryujinx.HLE.HOS.Services.Am.AppletAE;
using System; using System;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Applets namespace Ryujinx.HLE.HOS.Applets
{ {
@ -11,5 +12,10 @@ namespace Ryujinx.HLE.HOS.Applets
AppletSession interactiveSession); AppletSession interactiveSession);
ResultCode GetResult(); ResultCode GetResult();
static T ReadStruct<T>(ReadOnlySpan<byte> data) where T : struct
{
return MemoryMarshal.Cast<byte, T>(data)[0];
}
} }
} }

View file

@ -41,7 +41,7 @@ namespace Ryujinx.HLE.HOS.Applets
var keyboardConfig = _normalSession.Pop(); var keyboardConfig = _normalSession.Pop();
var transferMemory = _normalSession.Pop(); var transferMemory = _normalSession.Pop();
_keyboardConfig = ReadStruct<SoftwareKeyboardConfig>(keyboardConfig); _keyboardConfig = IApplet.ReadStruct<SoftwareKeyboardConfig>(keyboardConfig);
if (_keyboardConfig.UseUtf8) if (_keyboardConfig.UseUtf8)
{ {
@ -176,20 +176,5 @@ namespace Ryujinx.HLE.HOS.Applets
return stream.ToArray(); return stream.ToArray();
} }
} }
private static T ReadStruct<T>(byte[] data)
where T : struct
{
GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned);
try
{
return Marshal.PtrToStructure<T>(handle.AddrOfPinnedObject());
}
finally
{
handle.Free();
}
}
} }
} }

View file

@ -0,0 +1,88 @@
using Ryujinx.Common;
using Ryujinx.HLE.Exceptions;
using System.Runtime.CompilerServices;
namespace Ryujinx.HLE.HOS.Services.Hid
{
public class Hid
{
private readonly Switch _device;
private long _hidMemoryAddress;
internal ref HidSharedMemory SharedMemory => ref _device.Memory.GetStructRef<HidSharedMemory>(_hidMemoryAddress);
internal const int SharedMemEntryCount = 17;
public DebugPadDevice DebugPad;
public TouchDevice Touchscreen;
public MouseDevice Mouse;
public KeyboardDevice Keyboard;
public NpadDevices Npads;
static Hid()
{
if (Unsafe.SizeOf<ShMemDebugPad>() != 0x400)
{
throw new InvalidStructLayoutException<ShMemDebugPad>(0x400);
}
if (Unsafe.SizeOf<ShMemTouchScreen>() != 0x3000)
{
throw new InvalidStructLayoutException<ShMemTouchScreen>(0x3000);
}
if (Unsafe.SizeOf<ShMemKeyboard>() != 0x400)
{
throw new InvalidStructLayoutException<ShMemKeyboard>(0x400);
}
if (Unsafe.SizeOf<ShMemMouse>() != 0x400)
{
throw new InvalidStructLayoutException<ShMemMouse>(0x400);
}
if (Unsafe.SizeOf<ShMemNpad>() != 0x5000)
{
throw new InvalidStructLayoutException<ShMemNpad>(0x5000);
}
if (Unsafe.SizeOf<HidSharedMemory>() != Horizon.HidSize)
{
throw new InvalidStructLayoutException<HidSharedMemory>(Horizon.HidSize);
}
}
public Hid(in Switch device, long sharedHidMemoryAddress)
{
_device = device;
_hidMemoryAddress = sharedHidMemoryAddress;
device.Memory.FillWithZeros(sharedHidMemoryAddress, Horizon.HidSize);
}
public 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 ControllerKeys UpdateStickButtons(JoystickPosition leftStick, JoystickPosition rightStick)
{
ControllerKeys result = 0;
result |= (leftStick.Dx < 0) ? ControllerKeys.LStickLeft : result;
result |= (leftStick.Dx > 0) ? ControllerKeys.LStickRight : result;
result |= (leftStick.Dy < 0) ? ControllerKeys.LStickDown : result;
result |= (leftStick.Dy > 0) ? ControllerKeys.LStickUp : result;
result |= (rightStick.Dx < 0) ? ControllerKeys.RStickLeft : result;
result |= (rightStick.Dx > 0) ? ControllerKeys.RStickRight : result;
result |= (rightStick.Dy < 0) ? ControllerKeys.RStickDown : result;
result |= (rightStick.Dy > 0) ? ControllerKeys.RStickUp : result;
return result;
}
internal static ulong GetTimestampTicks()
{
return (ulong)PerformanceCounter.ElapsedMilliseconds * 19200;
}
}
}

View file

@ -0,0 +1,29 @@
using static Ryujinx.HLE.HOS.Services.Hid.Hid;
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;
}
internal static int UpdateEntriesHeader(ref CommonEntriesHeader header, out int previousEntry)
{
header.NumEntries = SharedMemEntryCount;
header.MaxEntryIndex = SharedMemEntryCount - 1;
previousEntry = (int)header.LatestEntry;
header.LatestEntry = (header.LatestEntry + 1) % SharedMemEntryCount;
header.TimestampTicks = GetTimestampTicks();
return (int)header.LatestEntry; // EntryCount shouldn't overflow int
}
}
}

View file

@ -0,0 +1,24 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public class DebugPadDevice : BaseDevice
{
public DebugPadDevice(Switch device, bool active) : base(device, active) { }
public void Update()
{
ref ShMemDebugPad debugPad = ref _device.Hid.SharedMemory.DebugPad;
int currentIndex = UpdateEntriesHeader(ref debugPad.Header, out int previousIndex);
if (!Active)
{
return;
}
ref DebugPadEntry currentEntry = ref debugPad.Entries[currentIndex];
DebugPadEntry previousEntry = debugPad.Entries[previousIndex];
currentEntry.SampleTimestamp = previousEntry.SampleTimestamp + 1;
}
}
}

View file

@ -0,0 +1,32 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public class KeyboardDevice : BaseDevice
{
public KeyboardDevice(Switch device, bool active) : base(device, active) { }
public unsafe void Update(KeyboardInput keyState)
{
ref ShMemKeyboard keyboard = ref _device.Hid.SharedMemory.Keyboard;
int currentIndex = UpdateEntriesHeader(ref keyboard.Header, out int previousIndex);
if (!Active)
{
return;
}
ref KeyboardState currentEntry = ref keyboard.Entries[currentIndex];
KeyboardState previousEntry = keyboard.Entries[previousIndex];
currentEntry.SampleTimestamp = previousEntry.SampleTimestamp + 1;
currentEntry.SampleTimestamp2 = previousEntry.SampleTimestamp2 + 1;
for (int i = 0; i < 8; ++i)
{
currentEntry.Keys[i] = (uint)keyState.Keys[i];
}
currentEntry.Modifier = (ulong)keyState.Modifier;
}
}
}

View file

@ -0,0 +1,37 @@
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, int buttons = 0, int scrollX = 0, int scrollY = 0)
{
ref ShMemMouse mouse = ref _device.Hid.SharedMemory.Mouse;
int currentIndex = UpdateEntriesHeader(ref mouse.Header, out int previousIndex);
if (!Active)
{
return;
}
ref MouseState currentEntry = ref mouse.Entries[currentIndex];
MouseState previousEntry = mouse.Entries[previousIndex];
currentEntry.SampleTimestamp = previousEntry.SampleTimestamp + 1;
currentEntry.SampleTimestamp2 = previousEntry.SampleTimestamp2 + 1;
currentEntry.Buttons = (ulong)buttons;
currentEntry.Position = new MousePosition
{
X = mouseX,
Y = mouseY,
VelocityX = mouseX - previousEntry.Position.X,
VelocityY = mouseY - previousEntry.Position.Y,
ScrollVelocityX = scrollX,
ScrollVelocityY = scrollY
};
}
}
}

View file

@ -0,0 +1,332 @@
using System;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.Common.Logging;
namespace Ryujinx.HLE.HOS.Services.Hid
{
public class NpadDevices : BaseDevice
{
internal NpadJoyHoldType JoyHold = NpadJoyHoldType.Vertical;
internal bool SixAxisActive = false; // TODO: link to hidserver when implemented
enum FilterState
{
Unconfigured = 0,
Configured = 1,
Accepted = 2
}
struct NpadConfig
{
public ControllerType ConfiguredType;
public FilterState State;
}
private const int _maxControllers = 9; // Players1-8 and Handheld
private NpadConfig[] _configuredNpads;
private ControllerType _supportedStyleSets = ControllerType.ProController |
ControllerType.JoyconPair |
ControllerType.JoyconLeft |
ControllerType.JoyconRight |
ControllerType.Handheld;
public ControllerType SupportedStyleSets
{
get { return _supportedStyleSets; }
set
{
if (_supportedStyleSets != value) // Deal with spamming
{
_supportedStyleSets = value;
MatchControllers();
}
}
}
public PlayerIndex PrimaryController { get; set; } = PlayerIndex.Unknown;
KEvent[] _styleSetUpdateEvents;
static readonly Array3<BatteryCharge> _fullBattery;
public NpadDevices(Switch device, bool active = true) : base(device, active)
{
_configuredNpads = new NpadConfig[_maxControllers];
_styleSetUpdateEvents = new KEvent[_maxControllers];
for (int i = 0; i < _styleSetUpdateEvents.Length; ++i)
{
_styleSetUpdateEvents[i] = new KEvent(_device.System);
}
_fullBattery[0] = _fullBattery[1] = _fullBattery[2] = BatteryCharge.Percent100;
}
public void AddControllers(params ControllerConfig[] configs)
{
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;
}
_configuredNpads[(int)player] = new NpadConfig { ConfiguredType = controllerType, State = FilterState.Configured };
}
MatchControllers();
}
void MatchControllers()
{
PrimaryController = PlayerIndex.Unknown;
for (int i = 0; i < _configuredNpads.Length; ++i)
{
ref NpadConfig config = ref _configuredNpads[i];
if (config.State == FilterState.Unconfigured)
{
continue; // Ignore unconfigured
}
if ((config.ConfiguredType & _supportedStyleSets) == 0)
{
Logger.PrintWarning(LogClass.Hid, $"ControllerType {config.ConfiguredType} (connected to {(PlayerIndex)i}) not supported by game. Removing...");
config.State = FilterState.Configured;
_device.Hid.SharedMemory.Npads[i] = new ShMemNpad(); // Zero it
continue;
}
InitController((PlayerIndex)i, config.ConfiguredType);
}
// Couldn't find any matching configuration. Reassign to something that works.
if (PrimaryController == PlayerIndex.Unknown)
{
ControllerType[] npadsTypeList = (ControllerType[])Enum.GetValues(typeof(ControllerType));
// Skip None Type
for (int i = 1; i < npadsTypeList.Length; ++i)
{
ControllerType controllerType = npadsTypeList[i];
if ((controllerType & _supportedStyleSets) != 0)
{
Logger.PrintWarning(LogClass.Hid, $"No matching controllers found. Reassigning input as ControllerType {controllerType}...");
InitController(controllerType == ControllerType.Handheld ? PlayerIndex.Handheld : PlayerIndex.Player1, controllerType);
return;
}
}
Logger.PrintError(LogClass.Hid, "Couldn't find any appropriate controller.");
}
}
internal ref KEvent GetStyleSetUpdateEvent(PlayerIndex player)
{
return ref _styleSetUpdateEvents[(int)player];
}
void InitController(PlayerIndex player, ControllerType type)
{
if (type == ControllerType.Handheld)
{
player = PlayerIndex.Handheld;
}
ref ShMemNpad controller = ref _device.Hid.SharedMemory.Npads[(int)player];
controller = new ShMemNpad(); // Zero it
// TODO: Allow customizing colors at config
NpadStateHeader defaultHeader = new NpadStateHeader
{
IsHalf = false,
SingleColorBody = NpadColor.BodyGray,
SingleColorButtons = NpadColor.ButtonGray,
LeftColorBody = NpadColor.BodyNeonBlue,
LeftColorButtons = NpadColor.ButtonGray,
RightColorBody = NpadColor.BodyNeonRed,
RightColorButtons = NpadColor.ButtonGray
};
controller.SystemProperties = NpadSystemProperties.PowerInfo0Connected |
NpadSystemProperties.PowerInfo1Connected |
NpadSystemProperties.PowerInfo2Connected;
controller.BatteryState = _fullBattery;
switch (type)
{
case ControllerType.ProController:
defaultHeader.Type = ControllerType.ProController;
controller.DeviceType = DeviceType.FullKey;
controller.SystemProperties |= NpadSystemProperties.AbxyButtonOriented |
NpadSystemProperties.PlusButtonCapability |
NpadSystemProperties.MinusButtonCapability;
break;
case ControllerType.Handheld:
defaultHeader.Type = ControllerType.Handheld;
controller.DeviceType = DeviceType.HandheldLeft |
DeviceType.HandheldRight;
controller.SystemProperties |= NpadSystemProperties.AbxyButtonOriented |
NpadSystemProperties.PlusButtonCapability |
NpadSystemProperties.MinusButtonCapability;
break;
case ControllerType.JoyconPair:
defaultHeader.Type = ControllerType.JoyconPair;
controller.DeviceType = DeviceType.JoyLeft |
DeviceType.JoyRight;
controller.SystemProperties |= NpadSystemProperties.AbxyButtonOriented |
NpadSystemProperties.PlusButtonCapability |
NpadSystemProperties.MinusButtonCapability;
break;
case ControllerType.JoyconLeft:
defaultHeader.Type = ControllerType.JoyconLeft;
defaultHeader.IsHalf = true;
controller.DeviceType = DeviceType.JoyLeft;
controller.SystemProperties |= NpadSystemProperties.SlSrButtonOriented |
NpadSystemProperties.MinusButtonCapability;
break;
case ControllerType.JoyconRight:
defaultHeader.Type = ControllerType.JoyconRight;
defaultHeader.IsHalf = true;
controller.DeviceType = DeviceType.JoyRight;
controller.SystemProperties |= NpadSystemProperties.SlSrButtonOriented |
NpadSystemProperties.PlusButtonCapability;
break;
case ControllerType.Pokeball:
defaultHeader.Type = ControllerType.Pokeball;
controller.DeviceType = DeviceType.Palma;
break;
}
controller.Header = defaultHeader;
if (PrimaryController == PlayerIndex.Unknown)
{
PrimaryController = player;
}
_configuredNpads[(int)player].State = FilterState.Accepted;
_styleSetUpdateEvents[(int)player].ReadableEvent.Signal();
Logger.PrintInfo(LogClass.Hid, $"Connected ControllerType {type} to PlayerIndex {player}");
}
static NpadLayoutsIndex ControllerTypeToLayout(ControllerType controllerType)
=> controllerType switch
{
ControllerType.ProController => NpadLayoutsIndex.ProController,
ControllerType.Handheld => NpadLayoutsIndex.Handheld,
ControllerType.JoyconPair => NpadLayoutsIndex.JoyDual,
ControllerType.JoyconLeft => NpadLayoutsIndex.JoyLeft,
ControllerType.JoyconRight => NpadLayoutsIndex.JoyRight,
ControllerType.Pokeball => NpadLayoutsIndex.Pokeball,
_ => NpadLayoutsIndex.SystemExternal
};
public void SetGamepadsInput(params GamepadInput[] states)
{
UpdateAllEntries();
for (int i = 0; i < states.Length; ++i)
{
SetGamepadState(states[i].PlayerId, states[i].Buttons, states[i].LStick, states[i].RStick);
}
}
void SetGamepadState(PlayerIndex player, ControllerKeys buttons,
JoystickPosition leftJoystick, JoystickPosition rightJoystick)
{
if (player == PlayerIndex.Auto)
{
player = PrimaryController;
}
if (player == PlayerIndex.Unknown)
{
return;
}
if (_configuredNpads[(int)player].State != FilterState.Accepted)
{
return;
}
ref ShMemNpad currentNpad = ref _device.Hid.SharedMemory.Npads[(int)player];
ref NpadLayout currentLayout = ref currentNpad.Layouts[(int)ControllerTypeToLayout(currentNpad.Header.Type)];
ref NpadState currentEntry = ref currentLayout.Entries[(int)currentLayout.Header.LatestEntry];
currentEntry.Buttons = buttons;
currentEntry.LStickX = leftJoystick.Dx;
currentEntry.LStickY = leftJoystick.Dy;
currentEntry.RStickX = rightJoystick.Dx;
currentEntry.RStickY = rightJoystick.Dy;
// Mirror data to Default layout just in case
ref NpadLayout mainLayout = ref currentNpad.Layouts[(int)NpadLayoutsIndex.SystemExternal];
mainLayout.Entries[(int)mainLayout.Header.LatestEntry] = currentEntry;
}
void UpdateAllEntries()
{
ref Array10<ShMemNpad> controllers = ref _device.Hid.SharedMemory.Npads;
for (int i = 0; i < controllers.Length; ++i)
{
ref Array7<NpadLayout> layouts = ref controllers[i].Layouts;
for (int l = 0; l < layouts.Length; ++l)
{
ref NpadLayout currentLayout = ref layouts[l];
int currentIndex = UpdateEntriesHeader(ref currentLayout.Header, out int previousIndex);
ref NpadState currentEntry = ref currentLayout.Entries[currentIndex];
NpadState previousEntry = currentLayout.Entries[previousIndex];
currentEntry.SampleTimestamp = previousEntry.SampleTimestamp + 1;
currentEntry.SampleTimestamp2 = previousEntry.SampleTimestamp2 + 1;
if (controllers[i].Header.Type == ControllerType.None)
{
continue;
}
currentEntry.ConnectionState = NpadConnectionState.ControllerStateConnected;
switch (controllers[i].Header.Type)
{
case ControllerType.Handheld:
case ControllerType.ProController:
currentEntry.ConnectionState |= NpadConnectionState.ControllerStateWired;
break;
case ControllerType.JoyconPair:
currentEntry.ConnectionState |= NpadConnectionState.JoyLeftConnected |
NpadConnectionState.JoyRightConnected;
break;
case ControllerType.JoyconLeft:
currentEntry.ConnectionState |= NpadConnectionState.JoyLeftConnected;
break;
case ControllerType.JoyconRight:
currentEntry.ConnectionState |= NpadConnectionState.JoyRightConnected;
break;
}
}
}
}
}
}

View file

@ -0,0 +1,46 @@
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 ShMemTouchScreen touchscreen = ref _device.Hid.SharedMemory.TouchScreen;
int currentIndex = UpdateEntriesHeader(ref touchscreen.Header, out int previousIndex);
if (!Active)
{
return;
}
ref TouchScreenState currentEntry = ref touchscreen.Entries[currentIndex];
TouchScreenState previousEntry = touchscreen.Entries[previousIndex];
currentEntry.SampleTimestamp = previousEntry.SampleTimestamp + 1;
currentEntry.SampleTimestamp2 = previousEntry.SampleTimestamp2 + 1;
currentEntry.NumTouches = (ulong)points.Length;
int pointsLength = Math.Min(points.Length, currentEntry.Touches.Length);
for (int i = 0; i < pointsLength; ++i)
{
TouchPoint pi = points[i];
currentEntry.Touches[i] = new TouchScreenStateData
{
SampleTimestamp = currentEntry.SampleTimestamp,
X = pi.X,
Y = pi.Y,
TouchIndex = (uint)i,
DiameterX = pi.DiameterX,
DiameterY = pi.DiameterY,
Angle = pi.Angle
};
}
}
}
}

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

@ -1,4 +1,4 @@
namespace Ryujinx.HLE.Input namespace Ryujinx.HLE.HOS.Services.Hid
{ {
public struct JoystickPosition public struct JoystickPosition
{ {

View file

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

View file

@ -0,0 +1,11 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public struct TouchPoint
{
public uint X;
public uint Y;
public uint DiameterX;
public uint DiameterY;
public uint Angle;
}
}

View file

@ -1,46 +1,39 @@
using Ryujinx.HLE.Input; using System;
using System;
namespace Ryujinx.HLE.HOS.Services.Hid.HidServer namespace Ryujinx.HLE.HOS.Services.Hid.HidServer
{ {
static class HidUtils static class HidUtils
{ {
public static ControllerId GetIndexFromNpadIdType(HidNpadIdType npadIdType) public static PlayerIndex GetIndexFromNpadIdType(NpadIdType npadIdType)
=> npadIdType switch
{ {
switch (npadIdType) NpadIdType.Player1 => PlayerIndex.Player1,
{ NpadIdType.Player2 => PlayerIndex.Player2,
case HidNpadIdType.Player1: return ControllerId.ControllerPlayer1; NpadIdType.Player3 => PlayerIndex.Player3,
case HidNpadIdType.Player2: return ControllerId.ControllerPlayer2; NpadIdType.Player4 => PlayerIndex.Player4,
case HidNpadIdType.Player3: return ControllerId.ControllerPlayer3; NpadIdType.Player5 => PlayerIndex.Player5,
case HidNpadIdType.Player4: return ControllerId.ControllerPlayer4; NpadIdType.Player6 => PlayerIndex.Player6,
case HidNpadIdType.Player5: return ControllerId.ControllerPlayer5; NpadIdType.Player7 => PlayerIndex.Player7,
case HidNpadIdType.Player6: return ControllerId.ControllerPlayer6; NpadIdType.Player8 => PlayerIndex.Player8,
case HidNpadIdType.Player7: return ControllerId.ControllerPlayer7; NpadIdType.Handheld => PlayerIndex.Handheld,
case HidNpadIdType.Player8: return ControllerId.ControllerPlayer8; NpadIdType.Unknown => PlayerIndex.Unknown,
case HidNpadIdType.Handheld: return ControllerId.ControllerHandheld; _ => throw new ArgumentOutOfRangeException(nameof(npadIdType))
case HidNpadIdType.Unknown: return ControllerId.ControllerUnknown; };
default: throw new ArgumentOutOfRangeException(nameof(npadIdType)); public static NpadIdType GetNpadIdTypeFromIndex(PlayerIndex index)
} => index switch
}
public static HidNpadIdType GetNpadIdTypeFromIndex(ControllerId index)
{ {
switch (index) PlayerIndex.Player1 => NpadIdType.Player1,
{ PlayerIndex.Player2 => NpadIdType.Player2,
case ControllerId.ControllerPlayer1: return HidNpadIdType.Player1; PlayerIndex.Player3 => NpadIdType.Player3,
case ControllerId.ControllerPlayer2: return HidNpadIdType.Player2; PlayerIndex.Player4 => NpadIdType.Player4,
case ControllerId.ControllerPlayer3: return HidNpadIdType.Player3; PlayerIndex.Player5 => NpadIdType.Player5,
case ControllerId.ControllerPlayer4: return HidNpadIdType.Player4; PlayerIndex.Player6 => NpadIdType.Player6,
case ControllerId.ControllerPlayer5: return HidNpadIdType.Player5; PlayerIndex.Player7 => NpadIdType.Player7,
case ControllerId.ControllerPlayer6: return HidNpadIdType.Player6; PlayerIndex.Player8 => NpadIdType.Player8,
case ControllerId.ControllerPlayer7: return HidNpadIdType.Player7; PlayerIndex.Handheld => NpadIdType.Handheld,
case ControllerId.ControllerPlayer8: return HidNpadIdType.Player8; PlayerIndex.Unknown => NpadIdType.Unknown,
case ControllerId.ControllerHandheld: return HidNpadIdType.Handheld; _ => throw new ArgumentOutOfRangeException(nameof(index))
case ControllerId.ControllerUnknown: return HidNpadIdType.Unknown; };
default: throw new ArgumentOutOfRangeException(nameof(index));
}
}
} }
} }

View file

@ -3,7 +3,6 @@ using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Hid.HidServer; using Ryujinx.HLE.HOS.Services.Hid.HidServer;
using Ryujinx.HLE.Input;
using System; using System;
namespace Ryujinx.HLE.HOS.Services.Hid namespace Ryujinx.HLE.HOS.Services.Hid
@ -11,7 +10,6 @@ namespace Ryujinx.HLE.HOS.Services.Hid
[Service("hid")] [Service("hid")]
class IHidServer : IpcService class IHidServer : IpcService
{ {
private KEvent _npadStyleSetUpdateEvent;
private KEvent _xpadIdEvent; private KEvent _xpadIdEvent;
private KEvent _palmaOperationCompleteEvent; private KEvent _palmaOperationCompleteEvent;
@ -22,8 +20,6 @@ namespace Ryujinx.HLE.HOS.Services.Hid
private bool _vibrationPermitted; private bool _vibrationPermitted;
private bool _usbFullKeyControllerEnabled; private bool _usbFullKeyControllerEnabled;
private HidNpadJoyHoldType _npadJoyHoldType;
private HidNpadStyle _npadStyleSet;
private HidNpadJoyAssignmentMode _npadJoyAssignmentMode; private HidNpadJoyAssignmentMode _npadJoyAssignmentMode;
private HidNpadHandheldActivationMode _npadHandheldActivationMode; private HidNpadHandheldActivationMode _npadHandheldActivationMode;
private HidGyroscopeZeroDriftMode _gyroscopeZeroDriftMode; private HidGyroscopeZeroDriftMode _gyroscopeZeroDriftMode;
@ -39,12 +35,9 @@ namespace Ryujinx.HLE.HOS.Services.Hid
public IHidServer(ServiceCtx context) public IHidServer(ServiceCtx context)
{ {
_npadStyleSetUpdateEvent = new KEvent(context.Device.System);
_xpadIdEvent = new KEvent(context.Device.System); _xpadIdEvent = new KEvent(context.Device.System);
_palmaOperationCompleteEvent = new KEvent(context.Device.System); _palmaOperationCompleteEvent = new KEvent(context.Device.System);
_npadJoyHoldType = HidNpadJoyHoldType.Vertical;
_npadStyleSet = HidNpadStyle.FullKey | HidNpadStyle.Dual | HidNpadStyle.Left | HidNpadStyle.Right | HidNpadStyle.Handheld;
_npadJoyAssignmentMode = HidNpadJoyAssignmentMode.Dual; _npadJoyAssignmentMode = HidNpadJoyAssignmentMode.Dual;
_npadHandheldActivationMode = HidNpadHandheldActivationMode.Dual; _npadHandheldActivationMode = HidNpadHandheldActivationMode.Dual;
_gyroscopeZeroDriftMode = HidGyroscopeZeroDriftMode.Standard; _gyroscopeZeroDriftMode = HidGyroscopeZeroDriftMode.Standard;
@ -85,6 +78,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
{ {
long appletResourceUserId = context.RequestData.ReadInt64(); long appletResourceUserId = context.RequestData.ReadInt64();
context.Device.Hid.Touchscreen.Active = true;
Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId });
return ResultCode.Success; return ResultCode.Success;
@ -96,6 +90,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
{ {
long appletResourceUserId = context.RequestData.ReadInt64(); long appletResourceUserId = context.RequestData.ReadInt64();
context.Device.Hid.Mouse.Active = true;
Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId });
return ResultCode.Success; return ResultCode.Success;
@ -107,6 +102,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
{ {
long appletResourceUserId = context.RequestData.ReadInt64(); long appletResourceUserId = context.RequestData.ReadInt64();
context.Device.Hid.Keyboard.Active = true;
Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId });
return ResultCode.Success; return ResultCode.Success;
@ -542,13 +538,15 @@ namespace Ryujinx.HLE.HOS.Services.Hid
// SetSupportedNpadStyleSet(nn::applet::AppletResourceUserId, nn::hid::NpadStyleTag) // SetSupportedNpadStyleSet(nn::applet::AppletResourceUserId, nn::hid::NpadStyleTag)
public ResultCode SetSupportedNpadStyleSet(ServiceCtx context) public ResultCode SetSupportedNpadStyleSet(ServiceCtx context)
{ {
_npadStyleSet = (HidNpadStyle)context.RequestData.ReadInt32(); ControllerType type = (ControllerType)context.RequestData.ReadInt32();
long appletResourceUserId = context.RequestData.ReadInt64(); long appletResourceUserId = context.RequestData.ReadInt64();
Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, _npadStyleSet }); Logger.PrintStub(LogClass.ServiceHid, new {
appletResourceUserId,
type
});
_npadStyleSetUpdateEvent.ReadableEvent.Signal(); context.Device.Hid.Npads.SupportedStyleSets = type;
return ResultCode.Success; return ResultCode.Success;
} }
@ -559,9 +557,12 @@ namespace Ryujinx.HLE.HOS.Services.Hid
{ {
long appletResourceUserId = context.RequestData.ReadInt64(); long appletResourceUserId = context.RequestData.ReadInt64();
context.ResponseData.Write((int)_npadStyleSet); context.ResponseData.Write((int)context.Device.Hid.Npads.SupportedStyleSets);
Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, _npadStyleSet }); Logger.PrintStub(LogClass.ServiceHid, new {
appletResourceUserId,
context.Device.Hid.Npads.SupportedStyleSets
});
return ResultCode.Success; return ResultCode.Success;
} }
@ -570,10 +571,17 @@ namespace Ryujinx.HLE.HOS.Services.Hid
// SetSupportedNpadIdType(nn::applet::AppletResourceUserId, array<NpadIdType, 9>) // SetSupportedNpadIdType(nn::applet::AppletResourceUserId, array<NpadIdType, 9>)
public ResultCode SetSupportedNpadIdType(ServiceCtx context) public ResultCode SetSupportedNpadIdType(ServiceCtx context)
{ {
long appletResourceUserId = context.RequestData.ReadInt64(); long appletResourceUserId = context.RequestData.ReadInt64();
ControllerId npadIdType = (ControllerId)context.RequestData.ReadInt64(); long arraySize = context.Request.PtrBuff[0].Size / 4;
Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, npadIdType }); NpadIdType[] supportedPlayerIds = new NpadIdType[arraySize];
for (int i = 0; i < arraySize; ++i)
{
supportedPlayerIds[i] = (NpadIdType)context.Memory.ReadInt32(context.Request.PtrBuff[0].Position + i * 4);
}
Logger.PrintStub(LogClass.ServiceHid, $"{arraySize} " + string.Join(",", supportedPlayerIds));
return ResultCode.Success; return ResultCode.Success;
} }
@ -584,6 +592,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
{ {
long appletResourceUserId = context.RequestData.ReadInt64(); long appletResourceUserId = context.RequestData.ReadInt64();
context.Device.Hid.Npads.Active = true;
Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId });
return ResultCode.Success; return ResultCode.Success;
@ -595,6 +604,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
{ {
long appletResourceUserId = context.RequestData.ReadInt64(); long appletResourceUserId = context.RequestData.ReadInt64();
context.Device.Hid.Npads.Active = false;
Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId }); Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId });
return ResultCode.Success; return ResultCode.Success;
@ -604,11 +614,12 @@ namespace Ryujinx.HLE.HOS.Services.Hid
// AcquireNpadStyleSetUpdateEventHandle(nn::applet::AppletResourceUserId, uint, ulong) -> nn::sf::NativeHandle // AcquireNpadStyleSetUpdateEventHandle(nn::applet::AppletResourceUserId, uint, ulong) -> nn::sf::NativeHandle
public ResultCode AcquireNpadStyleSetUpdateEventHandle(ServiceCtx context) public ResultCode AcquireNpadStyleSetUpdateEventHandle(ServiceCtx context)
{ {
long appletResourceUserId = context.RequestData.ReadInt64(); PlayerIndex npadId = HidUtils.GetIndexFromNpadIdType((NpadIdType)context.RequestData.ReadInt32());
int npadId = context.RequestData.ReadInt32(); long appletResourceUserId = context.RequestData.ReadInt64();
long npadStyleSet = context.RequestData.ReadInt64(); long npadStyleSet = context.RequestData.ReadInt64();
if (context.Process.HandleTable.GenerateHandle(_npadStyleSetUpdateEvent.ReadableEvent, out int handle) != KernelResult.Success) KEvent evnt = context.Device.Hid.Npads.GetStyleSetUpdateEvent(npadId);
if (context.Process.HandleTable.GenerateHandle(evnt.ReadableEvent, out int handle) != KernelResult.Success)
{ {
throw new InvalidOperationException("Out of handles!"); throw new InvalidOperationException("Out of handles!");
} }
@ -624,8 +635,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
// DisconnectNpad(nn::applet::AppletResourceUserId, uint NpadIdType) // DisconnectNpad(nn::applet::AppletResourceUserId, uint NpadIdType)
public ResultCode DisconnectNpad(ServiceCtx context) public ResultCode DisconnectNpad(ServiceCtx context)
{ {
long appletResourceUserId = context.RequestData.ReadInt64(); NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadInt32();
int npadIdType = context.RequestData.ReadInt32(); long appletResourceUserId = context.RequestData.ReadInt64();
Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, npadIdType }); Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, npadIdType });
@ -651,10 +662,10 @@ namespace Ryujinx.HLE.HOS.Services.Hid
// ActivateNpadWithRevision(nn::applet::AppletResourceUserId, int Unknown) // ActivateNpadWithRevision(nn::applet::AppletResourceUserId, int Unknown)
public ResultCode ActivateNpadWithRevision(ServiceCtx context) public ResultCode ActivateNpadWithRevision(ServiceCtx context)
{ {
int revision = context.RequestData.ReadInt32();
long appletResourceUserId = context.RequestData.ReadInt64(); long appletResourceUserId = context.RequestData.ReadInt64();
int unknown = context.RequestData.ReadInt32();
Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, unknown }); Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, revision });
return ResultCode.Success; return ResultCode.Success;
} }
@ -664,9 +675,12 @@ namespace Ryujinx.HLE.HOS.Services.Hid
public ResultCode SetNpadJoyHoldType(ServiceCtx context) public ResultCode SetNpadJoyHoldType(ServiceCtx context)
{ {
long appletResourceUserId = context.RequestData.ReadInt64(); long appletResourceUserId = context.RequestData.ReadInt64();
_npadJoyHoldType = (HidNpadJoyHoldType)context.RequestData.ReadInt64(); context.Device.Hid.Npads.JoyHold = (NpadJoyHoldType)context.RequestData.ReadInt64();
Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, _npadJoyHoldType }); Logger.PrintStub(LogClass.ServiceHid, new {
appletResourceUserId,
context.Device.Hid.Npads.JoyHold
});
return ResultCode.Success; return ResultCode.Success;
} }
@ -677,9 +691,12 @@ namespace Ryujinx.HLE.HOS.Services.Hid
{ {
long appletResourceUserId = context.RequestData.ReadInt64(); long appletResourceUserId = context.RequestData.ReadInt64();
context.ResponseData.Write((long)_npadJoyHoldType); context.ResponseData.Write((long)context.Device.Hid.Npads.JoyHold);
Logger.PrintStub(LogClass.ServiceHid, new { appletResourceUserId, _npadJoyHoldType }); Logger.PrintStub(LogClass.ServiceHid, new {
appletResourceUserId,
context.Device.Hid.Npads.JoyHold
});
return ResultCode.Success; return ResultCode.Success;
} }
@ -688,8 +705,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
// SetNpadJoyAssignmentModeSingleByDefault(uint HidControllerId, nn::applet::AppletResourceUserId) // SetNpadJoyAssignmentModeSingleByDefault(uint HidControllerId, nn::applet::AppletResourceUserId)
public ResultCode SetNpadJoyAssignmentModeSingleByDefault(ServiceCtx context) public ResultCode SetNpadJoyAssignmentModeSingleByDefault(ServiceCtx context)
{ {
ControllerId hidControllerId = (ControllerId)context.RequestData.ReadInt32(); PlayerIndex hidControllerId = (PlayerIndex)context.RequestData.ReadInt32();
long appletResourceUserId = context.RequestData.ReadInt64(); long appletResourceUserId = context.RequestData.ReadInt64();
_npadJoyAssignmentMode = HidNpadJoyAssignmentMode.Single; _npadJoyAssignmentMode = HidNpadJoyAssignmentMode.Single;
@ -702,7 +719,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
// SetNpadJoyAssignmentModeSingle(uint HidControllerId, nn::applet::AppletResourceUserId, long HidNpadJoyDeviceType) // SetNpadJoyAssignmentModeSingle(uint HidControllerId, nn::applet::AppletResourceUserId, long HidNpadJoyDeviceType)
public ResultCode SetNpadJoyAssignmentModeSingle(ServiceCtx context) public ResultCode SetNpadJoyAssignmentModeSingle(ServiceCtx context)
{ {
ControllerId hidControllerId = (ControllerId)context.RequestData.ReadInt32(); PlayerIndex hidControllerId = (PlayerIndex)context.RequestData.ReadInt32();
long appletResourceUserId = context.RequestData.ReadInt64(); long appletResourceUserId = context.RequestData.ReadInt64();
HidNpadJoyDeviceType hidNpadJoyDeviceType = (HidNpadJoyDeviceType)context.RequestData.ReadInt64(); HidNpadJoyDeviceType hidNpadJoyDeviceType = (HidNpadJoyDeviceType)context.RequestData.ReadInt64();
@ -717,8 +734,8 @@ namespace Ryujinx.HLE.HOS.Services.Hid
// SetNpadJoyAssignmentModeDual(uint HidControllerId, nn::applet::AppletResourceUserId) // SetNpadJoyAssignmentModeDual(uint HidControllerId, nn::applet::AppletResourceUserId)
public ResultCode SetNpadJoyAssignmentModeDual(ServiceCtx context) public ResultCode SetNpadJoyAssignmentModeDual(ServiceCtx context)
{ {
ControllerId hidControllerId = (ControllerId)context.RequestData.ReadInt32(); PlayerIndex hidControllerId = HidUtils.GetIndexFromNpadIdType((NpadIdType)context.RequestData.ReadInt32());
long appletResourceUserId = context.RequestData.ReadInt64(); long appletResourceUserId = context.RequestData.ReadInt64();
_npadJoyAssignmentMode = HidNpadJoyAssignmentMode.Dual; _npadJoyAssignmentMode = HidNpadJoyAssignmentMode.Dual;
@ -831,7 +848,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
// SetNpadJoyAssignmentModeSingleWithDestination(uint HidControllerId, long HidNpadJoyDeviceType, nn::applet::AppletResourceUserId) -> bool Unknown0, uint Unknown1 // SetNpadJoyAssignmentModeSingleWithDestination(uint HidControllerId, long HidNpadJoyDeviceType, nn::applet::AppletResourceUserId) -> bool Unknown0, uint Unknown1
public ResultCode SetNpadJoyAssignmentModeSingleWithDestination(ServiceCtx context) public ResultCode SetNpadJoyAssignmentModeSingleWithDestination(ServiceCtx context)
{ {
ControllerId hidControllerId = (ControllerId)context.RequestData.ReadInt32(); PlayerIndex hidControllerId = (PlayerIndex)context.RequestData.ReadInt32();
HidNpadJoyDeviceType hidNpadJoyDeviceType = (HidNpadJoyDeviceType)context.RequestData.ReadInt64(); HidNpadJoyDeviceType hidNpadJoyDeviceType = (HidNpadJoyDeviceType)context.RequestData.ReadInt64();
long appletResourceUserId = context.RequestData.ReadInt64(); long appletResourceUserId = context.RequestData.ReadInt64();

View file

@ -2,7 +2,6 @@
using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Services.Hid.HidServer; using Ryujinx.HLE.HOS.Services.Hid.HidServer;
using Ryujinx.HLE.Input;
using System; using System;
namespace Ryujinx.HLE.HOS.Services.Hid.Irs namespace Ryujinx.HLE.HOS.Services.Hid.Irs
@ -57,16 +56,16 @@ namespace Ryujinx.HLE.HOS.Services.Hid.Irs
// GetNpadIrCameraHandle(u32) -> nn::irsensor::IrCameraHandle // GetNpadIrCameraHandle(u32) -> nn::irsensor::IrCameraHandle
public ResultCode GetNpadIrCameraHandle(ServiceCtx context) public ResultCode GetNpadIrCameraHandle(ServiceCtx context)
{ {
HidNpadIdType npadIdType = (HidNpadIdType)context.RequestData.ReadUInt32(); NpadIdType npadIdType = (NpadIdType)context.RequestData.ReadUInt32();
if (npadIdType > HidNpadIdType.Player8 && if (npadIdType > NpadIdType.Player8 &&
npadIdType != HidNpadIdType.Unknown && npadIdType != NpadIdType.Unknown &&
npadIdType != HidNpadIdType.Handheld) npadIdType != NpadIdType.Handheld)
{ {
return ResultCode.NpadIdOutOfRange; return ResultCode.NpadIdOutOfRange;
} }
ControllerId irCameraHandle = HidUtils.GetIndexFromNpadIdType(npadIdType); PlayerIndex irCameraHandle = HidUtils.GetIndexFromNpadIdType(npadIdType);
context.ResponseData.Write((int)irCameraHandle); context.ResponseData.Write((int)irCameraHandle);

View file

@ -0,0 +1,9 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
struct Boolean32
{
private uint _value;
public static implicit operator bool(Boolean32 value) => (value._value & 1) != 0;
public static implicit operator Boolean32(bool value) => new Boolean32() { _value = value ? 1u : 0u };
}
}

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

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

View file

@ -1,16 +0,0 @@
using System;
namespace Ryujinx.HLE.HOS.Services.Hid
{
[Flags]
public enum HidNpadStyle
{
None,
FullKey = 1 << 0,
Handheld = 1 << 1,
Dual = 1 << 2,
Left = 1 << 3,
Right = 1 << 4,
Invalid = 1 << 5
}
}

View file

@ -0,0 +1,37 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
public enum NpadColor : int
{
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

@ -1,6 +1,6 @@
namespace Ryujinx.HLE.HOS.Services.Hid namespace Ryujinx.HLE.HOS.Services.Hid
{ {
public enum HidNpadIdType public enum NpadIdType
{ {
Player1 = 0, Player1 = 0,
Player2 = 1, Player2 = 1,

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,11 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
struct CommonEntriesHeader
{
public ulong TimestampTicks;
public ulong NumEntries;
public ulong LatestEntry;
public ulong MaxEntryIndex;
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
unsafe struct ShMemDebugPad
{
public CommonEntriesHeader Header;
public Array17<DebugPadEntry> Entries;
fixed byte _padding[0x138];
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
unsafe struct DebugPadEntry
{
public ulong SampleTimestamp;
fixed byte _unknown[0x20];
}
}

View file

@ -0,0 +1,24 @@
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Hid
{
// TODO: Add missing structs
unsafe struct HidSharedMemory
{
public ShMemDebugPad DebugPad;
public ShMemTouchScreen TouchScreen;
public ShMemMouse Mouse;
public ShMemKeyboard Keyboard;
public fixed byte BasicXpad[0x4 * 0x400];
public fixed byte HomeButton[0x200];
public fixed byte SleepButton[0x200];
public fixed byte CaptureButton[0x200];
public fixed byte InputDetector[0x10 * 0x80];
public fixed byte UniquePad[0x10 * 0x400];
public Array10<ShMemNpad> Npads;
public fixed byte Gesture[0x800];
public fixed byte ConsoleSixAxisSensor[0x20];
fixed byte _padding[0x3de0];
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
unsafe struct ShMemKeyboard
{
public CommonEntriesHeader Header;
public Array17<KeyboardState> Entries;
fixed byte _padding[0x28];
}
}

View file

@ -0,0 +1,10 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
unsafe struct KeyboardState
{
public ulong SampleTimestamp;
public ulong SampleTimestamp2;
public ulong Modifier;
public fixed uint Keys[8];
}
}

View file

@ -0,0 +1,10 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
unsafe struct ShMemMouse
{
public CommonEntriesHeader Header;
public Array17<MouseState> Entries;
fixed byte _padding[0xB0];
}
}

View file

@ -0,0 +1,12 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
struct MousePosition
{
public int X;
public int Y;
public int VelocityX;
public int VelocityY;
public int ScrollVelocityX;
public int ScrollVelocityY;
}
}

View file

@ -0,0 +1,10 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
struct MouseState
{
public ulong SampleTimestamp;
public ulong SampleTimestamp2;
public MousePosition Position;
public ulong Buttons;
}
}

View file

@ -0,0 +1,11 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
enum BatteryCharge : int
{
Percent0 = 0,
Percent25 = 1,
Percent50 = 2,
Percent75 = 3,
Percent100 = 4
}
}

View file

@ -0,0 +1,26 @@
using System;
namespace Ryujinx.HLE.HOS.Services.Hid
{
[Flags]
enum DeviceType : int
{
FullKey = 1 << 0,
DebugPad = 1 << 1,
HandheldLeft = 1 << 2,
HandheldRight = 1 << 3,
JoyLeft = 1 << 4,
JoyRight = 1 << 5,
Palma = 1 << 6, // Poké Ball Plus
FamicomLeft = 1 << 7,
FamicomRight = 1 << 8,
NESLeft = 1 << 9,
NESRight = 1 << 10,
HandheldFamicomLeft = 1 << 11,
HandheldFamicomRight = 1 << 12,
HandheldNESLeft = 1 << 13,
HandheldNESRight = 1 << 14,
Lucia = 1 << 15,
System = 1 << 31
}
}

View file

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

View file

@ -0,0 +1,21 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
// TODO: Add missing structs
unsafe struct ShMemNpad
{
public NpadStateHeader Header;
public Array7<NpadLayout> Layouts; // One for each NpadLayoutsIndex
public Array6<NpadSixAxis> Sixaxis;
public DeviceType DeviceType;
uint _padding1;
public NpadSystemProperties SystemProperties;
public uint NpadSystemButtonProperties;
public Array3<BatteryCharge> BatteryState;
public fixed byte NfcXcdDeviceHandleHeader[0x20];
public fixed byte NfcXcdDeviceHandleState[0x20 * 2];
public ulong Mutex;
public fixed byte NpadGcTriggerHeader[0x20];
public fixed byte NpadGcTriggerState[0x18 * 17];
fixed byte _padding2[0xC38];
}
}

View file

@ -1,9 +1,9 @@
using System; using System;
namespace Ryujinx.HLE.Input namespace Ryujinx.HLE.HOS.Services.Hid
{ {
[Flags] [Flags]
public enum ControllerColorDescription : int enum NpadColorDescription : int
{ {
ColorDescriptionColorsNonexistent = (1 << 1) ColorDescriptionColorsNonexistent = (1 << 1)
} }

View file

@ -0,0 +1,13 @@
using System;
namespace Ryujinx.HLE.HOS.Services.Hid
{
[Flags]
enum NpadConnectionState : long
{
ControllerStateConnected = (1 << 0),
ControllerStateWired = (1 << 1),
JoyLeftConnected = (1 << 2),
JoyRightConnected = (1 << 4)
}
}

View file

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

View file

@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
struct NpadLayout
{
public CommonEntriesHeader Header;
public Array17<NpadState> Entries;
}
}

View file

@ -0,0 +1,13 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
enum NpadLayoutsIndex : int
{
ProController = 0,
Handheld = 1,
JoyDual = 2,
JoyLeft = 3,
JoyRight = 4,
Pokeball = 5,
SystemExternal = 6
}
}

View file

@ -0,0 +1,8 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
struct NpadSixAxis
{
public CommonEntriesHeader Header;
public Array17<SixAxisState> Entries;
}
}

View file

@ -0,0 +1,14 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
struct NpadState
{
public ulong SampleTimestamp;
public ulong SampleTimestamp2;
public ControllerKeys Buttons;
public int LStickX;
public int LStickY;
public int RStickX;
public int RStickY;
public NpadConnectionState ConnectionState;
}
}

View file

@ -0,0 +1,16 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
struct NpadStateHeader
{
public ControllerType Type;
public Boolean32 IsHalf;
public NpadColorDescription SingleColorsDescriptor;
public NpadColor SingleColorBody;
public NpadColor SingleColorButtons;
public NpadColorDescription SplitColorsDescriptor;
public NpadColor LeftColorBody;
public NpadColor LeftColorButtons;
public NpadColor RightColorBody;
public NpadColor RightColorButtons;
}
}

View file

@ -0,0 +1,22 @@
using System;
namespace Ryujinx.HLE.HOS.Services.Hid
{
[Flags]
enum NpadSystemProperties : long
{
PowerInfo0Charging = 1 << 0,
PowerInfo1Charging = 1 << 1,
PowerInfo2Charging = 1 << 2,
PowerInfo0Connected = 1 << 3,
PowerInfo1Connected = 1 << 4,
PowerInfo2Connected = 1 << 5,
UnsupportedButtonPressedNpadSystem = 1 << 9,
UnsupportedButtonPressedNpadSystemExt = 1 << 10,
AbxyButtonOriented = 1 << 11,
SlSrButtonOriented = 1 << 12,
PlusButtonCapability = 1 << 13,
MinusButtonCapability = 1 << 14,
DirectionalButtonsSupported = 1 << 15
}
}

View file

@ -0,0 +1,14 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
unsafe struct SixAxisState
{
public ulong SampleTimestamp;
ulong _unknown1;
public ulong SampleTimestamp2;
public HidVector Accelerometer;
public HidVector Gyroscope;
HidVector unknownSensor;
public fixed float Orientation[9];
ulong _unknown2;
}
}

View file

@ -0,0 +1,53 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Hid
{
struct Array2<T> where T : unmanaged
{
T e0, e1;
public ref T this[int index] => ref MemoryMarshal.CreateSpan(ref e0, 2)[index];
public int Length => 2;
}
struct Array3<T> where T : unmanaged
{
T e0, e1, e2;
public ref T this[int index] => ref MemoryMarshal.CreateSpan(ref e0, 3)[index];
public int Length => 3;
}
struct Array6<T> where T : unmanaged
{
T e0, e1, e2, e3, e4, e5;
public ref T this[int index] => ref MemoryMarshal.CreateSpan(ref e0, 6)[index];
public int Length => 6;
}
struct Array7<T> where T : unmanaged
{
T e0, e1, e2, e3, e4, e5, e6;
public ref T this[int index] => ref MemoryMarshal.CreateSpan(ref e0, 7)[index];
public int Length => 7;
}
struct Array10<T> where T : unmanaged
{
T e0, e1, e2, e3, e4, e5, e6, e7, e8, e9;
public ref T this[int index] => ref MemoryMarshal.CreateSpan(ref e0, 10)[index];
public int Length => 10;
}
struct Array16<T> where T : unmanaged
{
T e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15;
public ref T this[int index] => ref MemoryMarshal.CreateSpan(ref e0, 16)[index];
public int Length => 16;
}
struct Array17<T> where T : unmanaged
{
T e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, e16;
public ref T this[int index] => ref MemoryMarshal.CreateSpan(ref e0, 17)[index];
public int Length => 17;
}
}

View file

@ -0,0 +1,9 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
unsafe struct ShMemTouchScreen
{
public CommonEntriesHeader Header;
public Array17<TouchScreenState> Entries;
fixed byte _padding[0x3c8];
}
}

View file

@ -0,0 +1,10 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
struct TouchScreenState
{
public ulong SampleTimestamp;
public ulong SampleTimestamp2;
public ulong NumTouches;
public Array16<TouchScreenStateData> Touches;
}
}

View file

@ -0,0 +1,15 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
struct TouchScreenStateData
{
public ulong SampleTimestamp;
uint _padding;
public uint TouchIndex;
public uint X;
public uint Y;
public uint DiameterX;
public uint DiameterY;
public uint Angle;
uint _padding2;
}
}

View file

@ -43,8 +43,8 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
// TODO: When we will be able to add multiple controllers add one entry by controller here. // TODO: When we will be able to add multiple controllers add one entry by controller here.
Device device1 = new Device Device device1 = new Device
{ {
NpadIdType = HidNpadIdType.Player1, NpadIdType = NpadIdType.Player1,
Handle = HidUtils.GetIndexFromNpadIdType(HidNpadIdType.Player1), Handle = HidUtils.GetIndexFromNpadIdType(NpadIdType.Player1),
State = DeviceState.Initialized State = DeviceState.Initialized
}; };

View file

@ -1,6 +1,5 @@
using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Hid; using Ryujinx.HLE.HOS.Services.Hid;
using Ryujinx.HLE.Input;
namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager
{ {
@ -14,7 +13,7 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager
public DeviceState State = DeviceState.Unavailable; public DeviceState State = DeviceState.Unavailable;
public ControllerId Handle; public PlayerIndex Handle;
public HidNpadIdType NpadIdType; public NpadIdType NpadIdType;
} }
} }

View file

@ -1,142 +0,0 @@
using static Ryujinx.HLE.Input.Hid;
namespace Ryujinx.HLE.Input
{
public abstract class BaseController : IHidDevice
{
protected ControllerStatus HidControllerType;
protected ControllerId ControllerId;
private long _currentLayoutOffset;
private long _mainLayoutOffset;
protected long DeviceStateOffset => Offset + 0x4188;
protected Switch Device { get; }
public long Offset { get; private set; }
public bool Connected { get; protected set; }
public ControllerHeader Header { get; private set; }
public ControllerStateHeader CurrentStateHeader { get; private set; }
public ControllerDeviceState DeviceState { get; private set; }
public ControllerLayouts CurrentLayout { get; private set; }
public ControllerState LastInputState { get; set; }
public ControllerConnectionState ConnectionState { get; protected set; }
public BaseController(Switch device, ControllerStatus controllerType)
{
Device = device;
HidControllerType = controllerType;
}
protected void Initialize(
bool isHalf,
(NpadColor left, NpadColor right) bodyColors,
(NpadColor left, NpadColor right) buttonColors,
ControllerColorDescription singleColorDesc = 0,
ControllerColorDescription splitColorDesc = 0,
NpadColor singleBodyColor = 0,
NpadColor singleButtonColor = 0
)
{
Header = new ControllerHeader()
{
IsJoyConHalf = isHalf ? 1 : 0,
LeftBodyColor = bodyColors.left,
LeftButtonColor = buttonColors.left,
RightBodyColor = bodyColors.right,
RightButtonColor = buttonColors.right,
Status = HidControllerType,
SingleBodyColor = singleBodyColor,
SingleButtonColor = singleButtonColor,
SplitColorDescription = splitColorDesc,
SingleColorDescription = singleColorDesc,
};
CurrentStateHeader = new ControllerStateHeader
{
EntryCount = HidEntryCount,
MaxEntryCount = HidEntryCount - 1,
CurrentEntryIndex = -1
};
DeviceState = new ControllerDeviceState()
{
PowerInfo0BatteryState = BatteryState.Percent100,
PowerInfo1BatteryState = BatteryState.Percent100,
PowerInfo2BatteryState = BatteryState.Percent100,
DeviceType = ControllerDeviceType.NPadLeftController | ControllerDeviceType.NPadRightController,
DeviceFlags = DeviceFlags.PowerInfo0Connected
| DeviceFlags.PowerInfo1Connected
| DeviceFlags.PowerInfo2Connected
};
LastInputState = new ControllerState()
{
SamplesTimestamp = -1,
SamplesTimestamp2 = -1
};
}
public virtual void Connect(ControllerId controllerId)
{
ControllerId = controllerId;
Offset = Device.Hid.HidPosition + HidControllersOffset + (int)controllerId * HidControllerSize;
_mainLayoutOffset = Offset + HidControllerHeaderSize
+ ((int)ControllerLayouts.Main * HidControllerLayoutsSize);
Device.Memory.FillWithZeros(Offset, 0x5000);
Device.Memory.WriteStruct(Offset, Header);
Device.Memory.WriteStruct(DeviceStateOffset, DeviceState);
Connected = true;
}
public void SetLayout(ControllerLayouts controllerLayout)
{
CurrentLayout = controllerLayout;
_currentLayoutOffset = Offset + HidControllerHeaderSize
+ ((int)controllerLayout * HidControllerLayoutsSize);
}
public void SendInput(
ControllerButtons buttons,
JoystickPosition leftStick,
JoystickPosition rightStick)
{
ControllerState currentInput = new ControllerState()
{
SamplesTimestamp = (long)LastInputState.SamplesTimestamp + 1,
SamplesTimestamp2 = (long)LastInputState.SamplesTimestamp + 1,
ButtonState = buttons,
ConnectionState = ConnectionState,
LeftStick = leftStick,
RightStick = rightStick
};
ControllerStateHeader newInputStateHeader = new ControllerStateHeader
{
EntryCount = HidEntryCount,
MaxEntryCount = HidEntryCount - 1,
CurrentEntryIndex = (CurrentStateHeader.CurrentEntryIndex + 1) % HidEntryCount,
Timestamp = GetTimestamp(),
};
Device.Memory.WriteStruct(_currentLayoutOffset, newInputStateHeader);
Device.Memory.WriteStruct(_mainLayoutOffset, newInputStateHeader);
long currentInputStateOffset = HidControllersLayoutHeaderSize
+ newInputStateHeader.CurrentEntryIndex * HidControllersInputEntrySize;
Device.Memory.WriteStruct(_currentLayoutOffset + currentInputStateOffset, currentInput);
Device.Memory.WriteStruct(_mainLayoutOffset + currentInputStateOffset, currentInput);
LastInputState = currentInput;
CurrentStateHeader = newInputStateHeader;
}
}
}

View file

@ -1,68 +0,0 @@
namespace Ryujinx.HLE.Input
{
public class NpadController : BaseController
{
private (NpadColor Left, NpadColor Right) _npadBodyColors;
private (NpadColor Left, NpadColor Right) _npadButtonColors;
private bool _isHalf;
public NpadController(
ControllerStatus controllerStatus,
Switch device,
(NpadColor, NpadColor) npadBodyColors,
(NpadColor, NpadColor) npadButtonColors) : base(device, controllerStatus)
{
_npadBodyColors = npadBodyColors;
_npadButtonColors = npadButtonColors;
}
public override void Connect(ControllerId controllerId)
{
if (HidControllerType != ControllerStatus.NpadLeft && HidControllerType != ControllerStatus.NpadRight)
{
_isHalf = false;
}
ConnectionState = ControllerConnectionState.ControllerStateConnected;
if (controllerId == ControllerId.ControllerHandheld)
ConnectionState |= ControllerConnectionState.ControllerStateWired;
ControllerColorDescription singleColorDesc =
ControllerColorDescription.ColorDescriptionColorsNonexistent;
ControllerColorDescription splitColorDesc = 0;
NpadColor singleBodyColor = NpadColor.Black;
NpadColor singleButtonColor = NpadColor.Black;
Initialize(_isHalf,
(_npadBodyColors.Left, _npadBodyColors.Right),
(_npadButtonColors.Left, _npadButtonColors.Right),
singleColorDesc,
splitColorDesc,
singleBodyColor,
singleButtonColor );
base.Connect(controllerId);
var _currentLayout = ControllerLayouts.HandheldJoined;
switch (HidControllerType)
{
case ControllerStatus.NpadLeft:
_currentLayout = ControllerLayouts.Left;
break;
case ControllerStatus.NpadRight:
_currentLayout = ControllerLayouts.Right;
break;
case ControllerStatus.NpadPair:
_currentLayout = ControllerLayouts.Joined;
break;
}
SetLayout(_currentLayout);
}
}
}

View file

@ -1,42 +0,0 @@
namespace Ryujinx.HLE.Input
{
public class ProController : BaseController
{
private bool _wired = false;
private NpadColor _bodyColor;
private NpadColor _buttonColor;
public ProController(Switch device,
NpadColor bodyColor,
NpadColor buttonColor) : base(device, ControllerStatus.ProController)
{
_wired = true;
_bodyColor = bodyColor;
_buttonColor = buttonColor;
}
public override void Connect(ControllerId controllerId)
{
ControllerColorDescription singleColorDesc =
ControllerColorDescription.ColorDescriptionColorsNonexistent;
ControllerColorDescription splitColorDesc = 0;
ConnectionState = ControllerConnectionState.ControllerStateConnected | ControllerConnectionState.ControllerStateWired;
Initialize(false,
(0, 0),
(0, 0),
singleColorDesc,
splitColorDesc,
_bodyColor,
_buttonColor);
base.Connect(controllerId);
SetLayout(ControllerLayouts.ProController);
}
}
}

View file

@ -1,12 +0,0 @@
namespace Ryujinx.HLE.Input
{
public enum BatteryState : int
{
// TODO : Check if these are the correct states
Percent0 = 0,
Percent25 = 1,
Percent50 = 2,
Percent75 = 3,
Percent100 = 4
}
}

View file

@ -1,35 +0,0 @@
using System;
namespace Ryujinx.HLE.Input
{
[Flags]
public enum ControllerButtons : long
{
A = 1 << 0,
B = 1 << 1,
X = 1 << 2,
Y = 1 << 3,
StickLeft = 1 << 4,
StickRight = 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,
Sl = 1 << 24,
Sr = 1 << 25
}
}

View file

@ -1,11 +0,0 @@
using System;
namespace Ryujinx.HLE.Input
{
[Flags]
public enum ControllerConnectionState : long
{
ControllerStateConnected = (1 << 0),
ControllerStateWired = (1 << 1)
}
}

View file

@ -1,18 +0,0 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.Input
{
[StructLayout(LayoutKind.Sequential)]
public unsafe struct ControllerDeviceState
{
public ControllerDeviceType DeviceType;
public int Padding;
public DeviceFlags DeviceFlags;
public int UnintendedHomeButtonInputProtectionEnabled;
public BatteryState PowerInfo0BatteryState;
public BatteryState PowerInfo1BatteryState;
public BatteryState PowerInfo2BatteryState;
public fixed byte ControllerMac[16];
public fixed byte ControllerMac2[16];
}
}

View file

@ -1,12 +0,0 @@
using System;
namespace Ryujinx.HLE.Input
{
[Flags]
public enum ControllerDeviceType : int
{
ProController = 1 << 0,
NPadLeftController = 1 << 4,
NPadRightController = 1 << 5,
}
}

View file

@ -1,19 +0,0 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.Input
{
[StructLayout(LayoutKind.Sequential)]
public struct ControllerHeader
{
public ControllerStatus Status;
public int IsJoyConHalf;
public ControllerColorDescription SingleColorDescription;
public NpadColor SingleBodyColor;
public NpadColor SingleButtonColor;
public ControllerColorDescription SplitColorDescription;
public NpadColor RightBodyColor;
public NpadColor RightButtonColor;
public NpadColor LeftBodyColor;
public NpadColor LeftButtonColor;
}
}

View file

@ -1,16 +0,0 @@
namespace Ryujinx.HLE.Input
{
public enum ControllerId
{
ControllerPlayer1 = 0,
ControllerPlayer2 = 1,
ControllerPlayer3 = 2,
ControllerPlayer4 = 3,
ControllerPlayer5 = 4,
ControllerPlayer6 = 5,
ControllerPlayer7 = 6,
ControllerPlayer8 = 7,
ControllerHandheld = 8,
ControllerUnknown = 9
}
}

View file

@ -1,13 +0,0 @@
namespace Ryujinx.HLE.Input
{
public enum ControllerLayouts
{
ProController = 0,
HandheldJoined = 1,
Joined = 2,
Left = 3,
Right = 4,
MainNoAnalog = 5,
Main = 6
}
}

View file

@ -1,15 +0,0 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.Input
{
[StructLayout(LayoutKind.Sequential)]
public struct ControllerState
{
public long SamplesTimestamp;
public long SamplesTimestamp2;
public ControllerButtons ButtonState;
public JoystickPosition LeftStick;
public JoystickPosition RightStick;
public ControllerConnectionState ConnectionState;
}
}

View file

@ -1,13 +0,0 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.Input
{
[StructLayout(LayoutKind.Sequential)]
public struct ControllerStateHeader
{
public long Timestamp;
public long EntryCount;
public long CurrentEntryIndex;
public long MaxEntryCount;
}
}

View file

@ -1,14 +0,0 @@
using System;
namespace Ryujinx.HLE.Input
{
[Flags]
public enum ControllerStatus : int
{
ProController = 1 << 0,
Handheld = 1 << 1,
NpadPair = 1 << 2,
NpadLeft = 1 << 3,
NpadRight = 1 << 4
}
}

View file

@ -1,22 +0,0 @@
using System;
namespace Ryujinx.HLE.Input
{
[Flags]
public enum DeviceFlags : long
{
PowerInfo0Charging = 1 << 0,
PowerInfo1Charging = 1 << 1,
PowerInfo2Charging = 1 << 2,
PowerInfo0Connected = 1 << 3,
PowerInfo1Connected = 1 << 4,
PowerInfo2Connected = 1 << 5,
UnsupportedButtonPressedNpadSystem = 1 << 9,
UnsupportedButtonPressedNpadSystemExt = 1 << 10,
AbxyButtonOriented = 1 << 11,
SlSrButtonOriented = 1 << 12,
PlusButtonCapability = 1 << 13,
MinusButtonCapability = 1 << 14,
DirectionalButtonsSupported = 1 << 15
}
}

View file

@ -1,10 +0,0 @@
using System;
namespace Ryujinx.HLE.Input
{
[Flags]
public enum HotkeyButtons
{
ToggleVSync = 1 << 0,
}
}

View file

@ -1,23 +0,0 @@
namespace Ryujinx.HLE.Input
{
public enum NpadColor : int //Thanks to CTCaer
{
Black = 0,
BodyGrey = 0x828282,
BodyNeonBlue = 0x0AB9E6,
BodyNeonRed = 0xFF3C28,
BodyNeonYellow = 0xE6FF00,
BodyNeonPink = 0xFF3278,
BodyNeonGreen = 0x1EDC00,
BodyRed = 0xE10F00,
ButtonsGrey = 0x0F0F0F,
ButtonsNeonBlue = 0x001E1E,
ButtonsNeonRed = 0x1E0A0A,
ButtonsNeonYellow = 0x142800,
ButtonsNeonPink = 0x28001E,
ButtonsNeonGreen = 0x002800,
ButtonsRed = 0x280A0A
}
}

View file

@ -1,218 +0,0 @@
using Ryujinx.Common;
using Ryujinx.Configuration.Hid;
using Ryujinx.HLE.HOS;
using System;
namespace Ryujinx.HLE.Input
{
public partial class Hid
{
private Switch _device;
private long _touchScreenOffset;
private long _touchEntriesOffset;
private long _keyboardOffset;
private TouchHeader _currentTouchHeader;
private KeyboardHeader _currentKeyboardHeader;
private KeyboardEntry _currentKeyboardEntry;
public BaseController PrimaryController { get; private set; }
internal long HidPosition;
public Hid(Switch device, long hidPosition)
{
_device = device;
HidPosition = hidPosition;
device.Memory.FillWithZeros(hidPosition, Horizon.HidSize);
_currentTouchHeader = new TouchHeader()
{
CurrentEntryIndex = -1,
};
_currentKeyboardHeader = new KeyboardHeader()
{
CurrentEntryIndex = -1,
};
_currentKeyboardEntry = new KeyboardEntry()
{
SamplesTimestamp = -1,
SamplesTimestamp2 = -1
};
_touchScreenOffset = HidPosition + HidTouchScreenOffset;
_touchEntriesOffset = _touchScreenOffset + HidTouchHeaderSize;
_keyboardOffset = HidPosition + HidKeyboardOffset;
}
private static ControllerStatus ConvertControllerTypeToState(ControllerType controllerType)
{
switch (controllerType)
{
case ControllerType.Handheld: return ControllerStatus.Handheld;
case ControllerType.NpadLeft: return ControllerStatus.NpadLeft;
case ControllerType.NpadRight: return ControllerStatus.NpadRight;
case ControllerType.NpadPair: return ControllerStatus.NpadPair;
case ControllerType.ProController: return ControllerStatus.ProController;
default: throw new NotImplementedException();
}
}
public void InitializePrimaryController(ControllerType controllerType)
{
ControllerId controllerId = controllerType == ControllerType.Handheld ?
ControllerId.ControllerHandheld : ControllerId.ControllerPlayer1;
if (controllerType == ControllerType.ProController)
{
PrimaryController = new ProController(_device, NpadColor.Black, NpadColor.Black);
}
else
{
PrimaryController = new NpadController(ConvertControllerTypeToState(controllerType),
_device,
(NpadColor.BodyNeonRed, NpadColor.BodyNeonRed),
(NpadColor.ButtonsNeonBlue, NpadColor.ButtonsNeonBlue));
}
PrimaryController.Connect(controllerId);
}
public ControllerButtons UpdateStickButtons(
JoystickPosition leftStick,
JoystickPosition rightStick)
{
ControllerButtons result = 0;
if (rightStick.Dx < 0)
{
result |= ControllerButtons.RStickLeft;
}
if (rightStick.Dx > 0)
{
result |= ControllerButtons.RStickRight;
}
if (rightStick.Dy < 0)
{
result |= ControllerButtons.RStickDown;
}
if (rightStick.Dy > 0)
{
result |= ControllerButtons.RStickUp;
}
if (leftStick.Dx < 0)
{
result |= ControllerButtons.LStickLeft;
}
if (leftStick.Dx > 0)
{
result |= ControllerButtons.LStickRight;
}
if (leftStick.Dy < 0)
{
result |= ControllerButtons.LStickDown;
}
if (leftStick.Dy > 0)
{
result |= ControllerButtons.LStickUp;
}
return result;
}
public void SetTouchPoints(params TouchPoint[] points)
{
long timestamp = GetTimestamp();
long sampleCounter = _currentTouchHeader.SamplesTimestamp + 1;
var newTouchHeader = new TouchHeader
{
CurrentEntryIndex = (_currentTouchHeader.CurrentEntryIndex + 1) % HidEntryCount,
EntryCount = HidEntryCount,
MaxEntries = HidEntryCount - 1,
SamplesTimestamp = sampleCounter,
Timestamp = timestamp,
};
long currentTouchEntryOffset = _touchEntriesOffset + newTouchHeader.CurrentEntryIndex * HidTouchEntrySize;
TouchEntry touchEntry = new TouchEntry()
{
SamplesTimestamp = sampleCounter,
TouchCount = points.Length
};
_device.Memory.WriteStruct(currentTouchEntryOffset, touchEntry);
currentTouchEntryOffset += HidTouchEntryHeaderSize;
for (int i = 0; i < points.Length; i++)
{
TouchData touch = new TouchData()
{
Angle = points[i].Angle,
DiameterX = points[i].DiameterX,
DiameterY = points[i].DiameterY,
Index = i,
SampleTimestamp = sampleCounter,
X = points[i].X,
Y = points[i].Y
};
_device.Memory.WriteStruct(currentTouchEntryOffset, touch);
currentTouchEntryOffset += HidTouchEntryTouchSize;
}
_device.Memory.WriteStruct(_touchScreenOffset, newTouchHeader);
_currentTouchHeader = newTouchHeader;
}
public unsafe void WriteKeyboard(Keyboard keyboard)
{
long timestamp = GetTimestamp();
var newKeyboardHeader = new KeyboardHeader()
{
CurrentEntryIndex = (_currentKeyboardHeader.CurrentEntryIndex + 1) % HidEntryCount,
EntryCount = HidEntryCount,
MaxEntries = HidEntryCount - 1,
Timestamp = timestamp,
};
_device.Memory.WriteStruct(_keyboardOffset, newKeyboardHeader);
long keyboardEntryOffset = _keyboardOffset + HidKeyboardHeaderSize;
keyboardEntryOffset += newKeyboardHeader.CurrentEntryIndex * HidKeyboardEntrySize;
var newkeyboardEntry = new KeyboardEntry()
{
SamplesTimestamp = _currentKeyboardEntry.SamplesTimestamp + 1,
SamplesTimestamp2 = _currentKeyboardEntry.SamplesTimestamp2 + 1,
Keys = keyboard.Keys,
Modifier = keyboard.Modifier,
};
_device.Memory.WriteStruct(keyboardEntryOffset, newkeyboardEntry);
_currentKeyboardEntry = newkeyboardEntry;
_currentKeyboardHeader = newKeyboardHeader;
}
internal static long GetTimestamp()
{
return PerformanceCounter.ElapsedMilliseconds * 19200;
}
}
}

View file

@ -1,63 +0,0 @@
namespace Ryujinx.HLE.Input
{
public partial class Hid
{
/*
* Reference:
* https://github.com/reswitched/libtransistor/blob/development/lib/hid.c
* https://github.com/reswitched/libtransistor/blob/development/include/libtransistor/hid.h
* https://github.com/switchbrew/libnx/blob/master/nx/source/services/hid.c
* https://github.com/switchbrew/libnx/blob/master/nx/include/switch/services/hid.h
*/
internal const int HidHeaderSize = 0x400;
internal const int HidTouchScreenSize = 0x3000;
internal const int HidMouseSize = 0x400;
internal const int HidKeyboardSize = 0x400;
internal const int HidUnkSection1Size = 0x400;
internal const int HidUnkSection2Size = 0x400;
internal const int HidUnkSection3Size = 0x400;
internal const int HidUnkSection4Size = 0x400;
internal const int HidUnkSection5Size = 0x200;
internal const int HidUnkSection6Size = 0x200;
internal const int HidUnkSection7Size = 0x200;
internal const int HidUnkSection8Size = 0x800;
internal const int HidControllerSerialsSize = 0x4000;
internal const int HidControllersSize = 0x32000;
internal const int HidUnkSection9Size = 0x800;
internal const int HidKeyboardHeaderSize = 0x20;
internal const int HidKeyboardEntrySize = 0x38;
internal const int HidTouchHeaderSize = 0x28;
internal const int HidTouchEntrySize = 0x298;
internal const int HidTouchEntryHeaderSize = 0x10;
internal const int HidTouchEntryTouchSize = 0x28;
internal const int HidControllerSize = 0x5000;
internal const int HidControllerHeaderSize = 0x28;
internal const int HidControllerLayoutsSize = 0x350;
internal const int HidControllersLayoutHeaderSize = 0x20;
internal const int HidControllersInputEntrySize = 0x30;
internal const int HidHeaderOffset = 0;
internal const int HidTouchScreenOffset = HidHeaderOffset + HidHeaderSize;
internal const int HidMouseOffset = HidTouchScreenOffset + HidTouchScreenSize;
internal const int HidKeyboardOffset = HidMouseOffset + HidMouseSize;
internal const int HidUnkSection1Offset = HidKeyboardOffset + HidKeyboardSize;
internal const int HidUnkSection2Offset = HidUnkSection1Offset + HidUnkSection1Size;
internal const int HidUnkSection3Offset = HidUnkSection2Offset + HidUnkSection2Size;
internal const int HidUnkSection4Offset = HidUnkSection3Offset + HidUnkSection3Size;
internal const int HidUnkSection5Offset = HidUnkSection4Offset + HidUnkSection4Size;
internal const int HidUnkSection6Offset = HidUnkSection5Offset + HidUnkSection5Size;
internal const int HidUnkSection7Offset = HidUnkSection6Offset + HidUnkSection6Size;
internal const int HidUnkSection8Offset = HidUnkSection7Offset + HidUnkSection7Size;
internal const int HidControllerSerialsOffset = HidUnkSection8Offset + HidUnkSection8Size;
internal const int HidControllersOffset = HidControllerSerialsOffset + HidControllerSerialsSize;
internal const int HidUnkSection9Offset = HidControllersOffset + HidControllersSize;
internal const int HidEntryCount = 17;
}
}

View file

@ -1,8 +0,0 @@
namespace Ryujinx.HLE.Input
{
interface IHidDevice
{
long Offset { get; }
bool Connected { get; }
}
}

View file

@ -1,8 +0,0 @@
namespace Ryujinx.HLE.Input
{
public struct Keyboard
{
public int Modifier;
public int[] Keys;
}
}

View file

@ -1,15 +0,0 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.Input
{
[StructLayout(LayoutKind.Sequential)]
public struct KeyboardEntry
{
public long SamplesTimestamp;
public long SamplesTimestamp2;
public long Modifier;
[MarshalAs(UnmanagedType.ByValArray , SizeConst = 0x8)]
public int[] Keys;
}
}

View file

@ -1,13 +0,0 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.Input
{
[StructLayout(LayoutKind.Sequential)]
public struct KeyboardHeader
{
public long Timestamp;
public long EntryCount;
public long CurrentEntryIndex;
public long MaxEntries;
}
}

View file

@ -1,18 +0,0 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.Input
{
[StructLayout(LayoutKind.Sequential)]
public struct TouchData
{
public long SampleTimestamp;
public int Padding;
public int Index;
public int X;
public int Y;
public int DiameterX;
public int DiameterY;
public int Angle;
public int Padding2;
}
}

View file

@ -1,11 +0,0 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.Input
{
[StructLayout(LayoutKind.Sequential)]
public unsafe struct TouchEntry
{
public long SamplesTimestamp;
public long TouchCount;
}
}

View file

@ -1,14 +0,0 @@
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.Input
{
[StructLayout(LayoutKind.Sequential)]
public struct TouchHeader
{
public long Timestamp;
public long EntryCount;
public long CurrentEntryIndex;
public long MaxEntries;
public long SamplesTimestamp;
}
}

View file

@ -1,11 +0,0 @@
namespace Ryujinx.HLE.Input
{
public struct TouchPoint
{
public int X;
public int Y;
public int DiameterX;
public int DiameterY;
public int Angle;
}
}

Some files were not shown because too many files have changed in this diff Show more