hid: Rewrite shared memory management (#2257)

* hid: Rewrite shared memory management

This entirely rewrite our ancient (and original) HID shared memory
interface to be more usable and accurate.

HID update logics were updated to reflect those changes but should work
still the same way it previously did.

This need heavy testing just in case to avoid possible regressions.

* Silence warnings

* Address gdkchan's comments

* Address Ac_K's comments

* Address one missing nit
This commit is contained in:
Mary 2021-05-02 22:01:30 +02:00 committed by GitHub
parent 0e9823d7e6
commit 3443023a08
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
77 changed files with 1395 additions and 624 deletions

View file

@ -4,6 +4,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Services.Hid; using Ryujinx.HLE.HOS.Services.Hid;
using Ryujinx.HLE.HOS.Services.Hid.Types;
using Ryujinx.HLE.HOS.Services.Am.AppletAE; using Ryujinx.HLE.HOS.Services.Am.AppletAE;
using static Ryujinx.HLE.HOS.Services.Hid.HidServer.HidUtils; using static Ryujinx.HLE.HOS.Services.Hid.HidServer.HidUtils;

View file

@ -3,6 +3,14 @@ using Ryujinx.HLE.Exceptions;
using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Ryujinx.Common.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.Mouse;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugPad;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.TouchScreen;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad;
namespace Ryujinx.HLE.HOS.Services.Hid namespace Ryujinx.HLE.HOS.Services.Hid
{ {
@ -12,7 +20,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
private readonly ulong _hidMemoryAddress; private readonly ulong _hidMemoryAddress;
internal ref HidSharedMemory SharedMemory => ref _device.Memory.GetRef<HidSharedMemory>(_hidMemoryAddress); internal ref SharedMemory SharedMemory => ref _device.Memory.GetRef<SharedMemory>(_hidMemoryAddress);
internal const int SharedMemEntryCount = 17; internal const int SharedMemEntryCount = 17;
@ -22,32 +30,22 @@ namespace Ryujinx.HLE.HOS.Services.Hid
public KeyboardDevice Keyboard; public KeyboardDevice Keyboard;
public NpadDevices Npads; public NpadDevices Npads;
private static void CheckTypeSizeOrThrow<T>(int expectedSize)
{
if (Unsafe.SizeOf<T>() != expectedSize)
{
throw new InvalidStructLayoutException<T>(expectedSize);
}
}
static Hid() static Hid()
{ {
if (Unsafe.SizeOf<ShMemDebugPad>() != 0x400) CheckTypeSizeOrThrow<RingLifo<DebugPadState>>(0x2c8);
{ CheckTypeSizeOrThrow<RingLifo<TouchScreenState>>(0x2C38);
throw new InvalidStructLayoutException<ShMemDebugPad>(0x400); CheckTypeSizeOrThrow<RingLifo<MouseState>>(0x350);
} CheckTypeSizeOrThrow<RingLifo<KeyboardState>>(0x3D8);
if (Unsafe.SizeOf<ShMemTouchScreen>() != 0x3000) CheckTypeSizeOrThrow<Array10<NpadState>>(0x32000);
{ CheckTypeSizeOrThrow<SharedMemory>(Horizon.HidSize);
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, ulong sharedHidMemoryAddress) public Hid(in Switch device, ulong sharedHidMemoryAddress)
@ -55,7 +53,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
_device = device; _device = device;
_hidMemoryAddress = sharedHidMemoryAddress; _hidMemoryAddress = sharedHidMemoryAddress;
device.Memory.ZeroFill(sharedHidMemoryAddress, Horizon.HidSize); SharedMemory = SharedMemory.Create();
} }
public void InitDevices() public void InitDevices()

View file

@ -12,18 +12,5 @@ namespace Ryujinx.HLE.HOS.Services.Hid
_device = device; _device = device;
Active = active; 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

@ -1,3 +1,6 @@
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugPad;
namespace Ryujinx.HLE.HOS.Services.Hid namespace Ryujinx.HLE.HOS.Services.Hid
{ {
public class DebugPadDevice : BaseDevice public class DebugPadDevice : BaseDevice
@ -6,20 +9,20 @@ namespace Ryujinx.HLE.HOS.Services.Hid
public void Update() public void Update()
{ {
ref ShMemDebugPad debugPad = ref _device.Hid.SharedMemory.DebugPad; ref RingLifo<DebugPadState> lifo = ref _device.Hid.SharedMemory.DebugPad;
int currentIndex = UpdateEntriesHeader(ref debugPad.Header, out int previousIndex); ref DebugPadState previousEntry = ref lifo.GetCurrentEntryRef();
if (!Active) DebugPadState newState = new DebugPadState();
if (Active)
{ {
return; // TODO: This is a debug device only present in dev environment, do we want to support it?
} }
ref DebugPadEntry currentEntry = ref debugPad.Entries[currentIndex]; newState.SamplingNumber = previousEntry.SamplingNumber + 1;
DebugPadEntry previousEntry = debugPad.Entries[previousIndex];
currentEntry.SampleTimestamp = previousEntry.SampleTimestamp + 1; lifo.Write(ref newState);
currentEntry.SampleTimestamp2 = previousEntry.SampleTimestamp2 + 1;
} }
} }
} }

View file

@ -1,3 +1,7 @@
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 namespace Ryujinx.HLE.HOS.Services.Hid
{ {
public class KeyboardDevice : BaseDevice public class KeyboardDevice : BaseDevice
@ -6,27 +10,26 @@ namespace Ryujinx.HLE.HOS.Services.Hid
public unsafe void Update(KeyboardInput keyState) public unsafe void Update(KeyboardInput keyState)
{ {
ref ShMemKeyboard keyboard = ref _device.Hid.SharedMemory.Keyboard; ref RingLifo<KeyboardState> lifo = ref _device.Hid.SharedMemory.Keyboard;
int currentIndex = UpdateEntriesHeader(ref keyboard.Header, out int previousIndex);
if (!Active) if (!Active)
{ {
lifo.Clear();
return; return;
} }
ref KeyboardState currentEntry = ref keyboard.Entries[currentIndex]; ref KeyboardState previousEntry = ref lifo.GetCurrentEntryRef();
KeyboardState previousEntry = keyboard.Entries[previousIndex];
currentEntry.SampleTimestamp = previousEntry.SampleTimestamp + 1; KeyboardState newState = new KeyboardState
currentEntry.SampleTimestamp2 = previousEntry.SampleTimestamp2 + 1;
for (int i = 0; i < 8; ++i)
{ {
currentEntry.Keys[i] = (uint)keyState.Keys[i]; SamplingNumber = previousEntry.SamplingNumber + 1,
} };
currentEntry.Modifier = (ulong)keyState.Modifier; keyState.Keys.AsSpan().CopyTo(newState.Keys.RawData.ToSpan());
newState.Modifiers = (KeyboardModifier)keyState.Modifier;
lifo.Write(ref newState);
} }
} }
} }

View file

@ -1,37 +1,35 @@
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse;
namespace Ryujinx.HLE.HOS.Services.Hid namespace Ryujinx.HLE.HOS.Services.Hid
{ {
public class MouseDevice : BaseDevice public class MouseDevice : BaseDevice
{ {
public MouseDevice(Switch device, bool active) : base(device, active) { } 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) public void Update(int mouseX, int mouseY, uint buttons = 0, int scrollX = 0, int scrollY = 0)
{ {
ref ShMemMouse mouse = ref _device.Hid.SharedMemory.Mouse; ref RingLifo<MouseState> lifo = ref _device.Hid.SharedMemory.Mouse;
int currentIndex = UpdateEntriesHeader(ref mouse.Header, out int previousIndex); ref MouseState previousEntry = ref lifo.GetCurrentEntryRef();
if (!Active) MouseState newState = new MouseState()
{ {
return; SamplingNumber = previousEntry.SamplingNumber + 1,
}
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
}; };
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;
}
lifo.Write(ref newState);
} }
} }
} }

View file

@ -1,16 +1,17 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory;
using Ryujinx.HLE.HOS.Kernel.Threading; 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;
namespace Ryujinx.HLE.HOS.Services.Hid namespace Ryujinx.HLE.HOS.Services.Hid
{ {
public class NpadDevices : BaseDevice public class NpadDevices : BaseDevice
{ {
private const BatteryCharge DefaultBatteryCharge = BatteryCharge.Percent100;
private const int NoMatchNotifyFrequencyMs = 2000; private const int NoMatchNotifyFrequencyMs = 2000;
private int _activeCount; private int _activeCount;
private long _lastNotifyTimestamp; private long _lastNotifyTimestamp;
@ -86,7 +87,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
continue; continue;
} }
ControllerType currentType = _device.Hid.SharedMemory.Npads[i].Header.Type; ControllerType currentType = (ControllerType)_device.Hid.SharedMemory.Npads[i].InternalState.StyleSet;
if (currentType != ControllerType.None && (npad & acceptedTypes) != 0 && _supportedPlayers[i]) if (currentType != ControllerType.None && (npad & acceptedTypes) != 0 && _supportedPlayers[i])
{ {
@ -135,12 +136,24 @@ namespace Ryujinx.HLE.HOS.Services.Hid
{ {
Remap(); Remap();
UpdateAllEntries(); Span<bool> updated = stackalloc bool[10];
// Update configured inputs // Update configured inputs
for (int i = 0; i < states.Count; ++i) for (int i = 0; i < states.Count; ++i)
{ {
UpdateInput(states[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);
}
} }
} }
@ -185,16 +198,16 @@ namespace Ryujinx.HLE.HOS.Services.Hid
private void SetupNpad(PlayerIndex player, ControllerType type) private void SetupNpad(PlayerIndex player, ControllerType type)
{ {
ref ShMemNpad controller = ref _device.Hid.SharedMemory.Npads[(int)player]; ref NpadInternalState controller = ref _device.Hid.SharedMemory.Npads[(int)player].InternalState;
ControllerType oldType = controller.Header.Type; ControllerType oldType = (ControllerType)controller.StyleSet;
if (oldType == type) if (oldType == type)
{ {
return; // Already configured return; // Already configured
} }
controller = new ShMemNpad(); // Zero it controller = NpadInternalState.Create(); // Reset it
if (type == ControllerType.None) if (type == ControllerType.None)
{ {
@ -207,87 +220,151 @@ namespace Ryujinx.HLE.HOS.Services.Hid
} }
// TODO: Allow customizing colors at config // TODO: Allow customizing colors at config
NpadStateHeader defaultHeader = new NpadStateHeader controller.JoyAssignmentMode = NpadJoyAssignmentMode.Dual;
{ controller.FullKeyColor.FullKeyBody = (uint)NpadColor.BodyGray;
IsHalf = false, controller.FullKeyColor.FullKeyButtons = (uint)NpadColor.ButtonGray;
SingleColorBody = NpadColor.BodyGray, controller.JoyColor.LeftBody = (uint)NpadColor.BodyNeonBlue;
SingleColorButtons = NpadColor.ButtonGray, controller.JoyColor.LeftButtons = (uint)NpadColor.ButtonGray;
LeftColorBody = NpadColor.BodyNeonBlue, controller.JoyColor.RightBody = (uint)NpadColor.BodyNeonRed;
LeftColorButtons = NpadColor.ButtonGray, controller.JoyColor.RightButtons = (uint)NpadColor.ButtonGray;
RightColorBody = NpadColor.BodyNeonRed,
RightColorButtons = NpadColor.ButtonGray
};
controller.SystemProperties = NpadSystemProperties.PowerInfo0Connected | controller.SystemProperties = NpadSystemProperties.IsPoweredJoyDual |
NpadSystemProperties.PowerInfo1Connected | NpadSystemProperties.IsPoweredJoyLeft |
NpadSystemProperties.PowerInfo2Connected; NpadSystemProperties.IsPoweredJoyRight;
controller.BatteryState.ToSpan().Fill(DefaultBatteryCharge); controller.BatteryLevelJoyDual = NpadBatteryLevel.Percent100;
controller.BatteryLevelJoyLeft = NpadBatteryLevel.Percent100;
controller.BatteryLevelJoyRight = NpadBatteryLevel.Percent100;
switch (type) switch (type)
{ {
case ControllerType.ProController: case ControllerType.ProController:
defaultHeader.Type = ControllerType.ProController; controller.StyleSet = NpadStyleTag.FullKey;
controller.DeviceType = DeviceType.FullKey; controller.DeviceType = DeviceType.FullKey;
controller.SystemProperties |= NpadSystemProperties.AbxyButtonOriented | controller.SystemProperties |= NpadSystemProperties.IsAbxyButtonOriented |
NpadSystemProperties.PlusButtonCapability | NpadSystemProperties.IsPlusAvailable |
NpadSystemProperties.MinusButtonCapability; NpadSystemProperties.IsMinusAvailable;
break; break;
case ControllerType.Handheld: case ControllerType.Handheld:
defaultHeader.Type = ControllerType.Handheld; controller.StyleSet = NpadStyleTag.Handheld;
controller.DeviceType = DeviceType.HandheldLeft | controller.DeviceType = DeviceType.HandheldLeft |
DeviceType.HandheldRight; DeviceType.HandheldRight;
controller.SystemProperties |= NpadSystemProperties.AbxyButtonOriented | controller.SystemProperties |= NpadSystemProperties.IsAbxyButtonOriented |
NpadSystemProperties.PlusButtonCapability | NpadSystemProperties.IsPlusAvailable |
NpadSystemProperties.MinusButtonCapability; NpadSystemProperties.IsMinusAvailable;
break; break;
case ControllerType.JoyconPair: case ControllerType.JoyconPair:
defaultHeader.Type = ControllerType.JoyconPair; controller.StyleSet = NpadStyleTag.JoyDual;
controller.DeviceType = DeviceType.JoyLeft | controller.DeviceType = DeviceType.JoyLeft |
DeviceType.JoyRight; DeviceType.JoyRight;
controller.SystemProperties |= NpadSystemProperties.AbxyButtonOriented | controller.SystemProperties |= NpadSystemProperties.IsAbxyButtonOriented |
NpadSystemProperties.PlusButtonCapability | NpadSystemProperties.IsPlusAvailable |
NpadSystemProperties.MinusButtonCapability; NpadSystemProperties.IsMinusAvailable;
break; break;
case ControllerType.JoyconLeft: case ControllerType.JoyconLeft:
defaultHeader.Type = ControllerType.JoyconLeft; controller.StyleSet = NpadStyleTag.JoyLeft;
defaultHeader.IsHalf = true; controller.JoyAssignmentMode = NpadJoyAssignmentMode.Single;
controller.DeviceType = DeviceType.JoyLeft; controller.DeviceType = DeviceType.JoyLeft;
controller.SystemProperties |= NpadSystemProperties.SlSrButtonOriented | controller.SystemProperties |= NpadSystemProperties.IsSlSrButtonOriented |
NpadSystemProperties.MinusButtonCapability; NpadSystemProperties.IsMinusAvailable;
break; break;
case ControllerType.JoyconRight: case ControllerType.JoyconRight:
defaultHeader.Type = ControllerType.JoyconRight; controller.StyleSet = NpadStyleTag.JoyRight;
defaultHeader.IsHalf = true; controller.JoyAssignmentMode = NpadJoyAssignmentMode.Single;
controller.DeviceType = DeviceType.JoyRight; controller.DeviceType = DeviceType.JoyRight;
controller.SystemProperties |= NpadSystemProperties.SlSrButtonOriented | controller.SystemProperties |= NpadSystemProperties.IsSlSrButtonOriented |
NpadSystemProperties.PlusButtonCapability; NpadSystemProperties.IsPlusAvailable;
break; break;
case ControllerType.Pokeball: case ControllerType.Pokeball:
defaultHeader.Type = ControllerType.Pokeball; controller.StyleSet = NpadStyleTag.Palma;
controller.DeviceType = DeviceType.Palma; controller.DeviceType = DeviceType.Palma;
break; break;
} }
controller.Header = defaultHeader;
_styleSetUpdateEvents[(int)player].ReadableEvent.Signal(); _styleSetUpdateEvents[(int)player].ReadableEvent.Signal();
_activeCount++; _activeCount++;
Logger.Info?.Print(LogClass.Hid, $"Connected Controller {type} to {player}"); Logger.Info?.Print(LogClass.Hid, $"Connected Controller {type} to {player}");
} }
private static NpadLayoutsIndex ControllerTypeToNpadLayout(ControllerType controllerType) private ref RingLifo<NpadCommonState> GetCommonStateLifo(ref NpadInternalState npad)
=> controllerType switch
{ {
ControllerType.ProController => NpadLayoutsIndex.ProController, switch (npad.StyleSet)
ControllerType.Handheld => NpadLayoutsIndex.Handheld, {
ControllerType.JoyconPair => NpadLayoutsIndex.JoyDual, case NpadStyleTag.FullKey:
ControllerType.JoyconLeft => NpadLayoutsIndex.JoyLeft, return ref npad.FullKey;
ControllerType.JoyconRight => NpadLayoutsIndex.JoyRight, case NpadStyleTag.Handheld:
ControllerType.Pokeball => NpadLayoutsIndex.Pokeball, return ref npad.Handheld;
_ => NpadLayoutsIndex.SystemExternal 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)
{
bool isEquals;
unsafe
{
var aPointer = Unsafe.AsPointer(ref currentlyUsed);
var bPointer = Unsafe.AsPointer(ref possiblyUnused);
isEquals = aPointer == bPointer;
}
if (!isEquals)
{
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)
{
bool isEquals;
unsafe
{
var aPointer = Unsafe.AsPointer(ref currentlyUsed);
var bPointer = Unsafe.AsPointer(ref possiblyUnused);
isEquals = aPointer == bPointer;
}
if (!isEquals)
{
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) private void UpdateInput(GamepadInput state)
{ {
@ -296,43 +373,88 @@ namespace Ryujinx.HLE.HOS.Services.Hid
return; return;
} }
ref ShMemNpad currentNpad = ref _device.Hid.SharedMemory.Npads[(int)state.PlayerId]; ref NpadInternalState currentNpad = ref _device.Hid.SharedMemory.Npads[(int)state.PlayerId].InternalState;
if (currentNpad.Header.Type == ControllerType.None) if (currentNpad.StyleSet == NpadStyleTag.None)
{ {
return; return;
} }
ref NpadLayout currentLayout = ref currentNpad.Layouts[(int)ControllerTypeToNpadLayout(currentNpad.Header.Type)]; ref RingLifo<NpadCommonState> lifo = ref GetCommonStateLifo(ref currentNpad);
ref NpadState currentEntry = ref currentLayout.Entries[(int)currentLayout.Header.LatestEntry];
currentEntry.Buttons = state.Buttons; NpadCommonState newState = new NpadCommonState
currentEntry.LStickX = state.LStick.Dx; {
currentEntry.LStickY = state.LStick.Dy; Buttons = (NpadButton)state.Buttons,
currentEntry.RStickX = state.RStick.Dx; AnalogStickL = new AnalogStickState
currentEntry.RStickY = state.RStick.Dy; {
X = state.LStick.Dx,
Y = state.LStick.Dy,
},
AnalogStickR = new AnalogStickState
{
X = state.RStick.Dx,
Y = state.RStick.Dy,
}
};
// Mirror data to Default layout just in case newState.Attributes = NpadAttribute.IsConnected;
ref NpadLayout mainLayout = ref currentNpad.Layouts[(int)NpadLayoutsIndex.SystemExternal];
mainLayout.Entries[(int)mainLayout.Header.LatestEntry] = currentEntry; 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;
} }
private static SixAxixLayoutsIndex ControllerTypeToSixAxisLayout(ControllerType controllerType) WriteNewInputEntry(ref lifo, ref newState);
=> controllerType switch
// Mirror data to Default layout just in case
if (!currentNpad.StyleSet.HasFlag(NpadStyleTag.SystemExt))
{ {
ControllerType.ProController => SixAxixLayoutsIndex.ProController, WriteNewInputEntry(ref currentNpad.SystemExt, ref newState);
ControllerType.Handheld => SixAxixLayoutsIndex.Handheld, }
ControllerType.JoyconPair => SixAxixLayoutsIndex.JoyDualLeft,
ControllerType.JoyconLeft => SixAxixLayoutsIndex.JoyLeft, UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.FullKey);
ControllerType.JoyconRight => SixAxixLayoutsIndex.JoyRight, UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.Handheld);
ControllerType.Pokeball => SixAxixLayoutsIndex.Pokeball, UpdateUnusedInputIfNotEqual(ref lifo, ref currentNpad.JoyDual);
_ => SixAxixLayoutsIndex.SystemExternal 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) public void UpdateSixAxis(IList<SixAxisInput> states)
{ {
Span<bool> updated = stackalloc bool[10];
for (int i = 0; i < states.Count; ++i) for (int i = 0; i < states.Count; ++i)
{ {
updated[(int)states[i].PlayerId] = true;
if (SetSixAxisState(states[i])) if (SetSixAxisState(states[i]))
{ {
i++; i++;
@ -345,6 +467,40 @@ namespace Ryujinx.HLE.HOS.Services.Hid
SetSixAxisState(states[i], true); 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) private bool SetSixAxisState(SixAxisInput state, bool isRightPair = false)
@ -354,9 +510,9 @@ namespace Ryujinx.HLE.HOS.Services.Hid
return false; return false;
} }
ref ShMemNpad currentNpad = ref _device.Hid.SharedMemory.Npads[(int)state.PlayerId]; ref NpadInternalState currentNpad = ref _device.Hid.SharedMemory.Npads[(int)state.PlayerId].InternalState;
if (currentNpad.Header.Type == ControllerType.None) if (currentNpad.StyleSet == NpadStyleTag.None)
{ {
return false; return false;
} }
@ -382,87 +538,57 @@ namespace Ryujinx.HLE.HOS.Services.Hid
Z = state.Rotation.Z Z = state.Rotation.Z
}; };
ref NpadSixAxis currentLayout = ref currentNpad.Sixaxis[(int)ControllerTypeToSixAxisLayout(currentNpad.Header.Type) + (isRightPair ? 1 : 0)]; SixAxisSensorState newState = new SixAxisSensorState
ref SixAxisState currentEntry = ref currentLayout.Entries[(int)currentLayout.Header.LatestEntry];
int previousEntryIndex = (int)(currentLayout.Header.LatestEntry == 0 ?
currentLayout.Header.MaxEntryIndex : currentLayout.Header.LatestEntry - 1);
ref SixAxisState previousEntry = ref currentLayout.Entries[previousEntryIndex];
currentEntry.Accelerometer = accel;
currentEntry.Gyroscope = gyro;
currentEntry.Rotations = rotation;
unsafe
{ {
for (int i = 0; i < 9; i++) Acceleration = accel,
AngularVelocity = gyro,
Angle = rotation,
Attributes = SixAxisSensorAttribute.IsConnected
};
state.Orientation.AsSpan().CopyTo(newState.Direction.ToSpan());
ref RingLifo<SixAxisSensorState> lifo = ref GetSixAxisSensorLifo(ref currentNpad, isRightPair);
WriteNewSixInputEntry(ref lifo, ref newState);
bool needUpdateRight = currentNpad.StyleSet == NpadStyleTag.JoyDual && !isRightPair;
if (!isRightPair)
{ {
currentEntry.Orientation[i] = state.Orientation[i]; 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);
} }
return currentNpad.Header.Type == ControllerType.JoyconPair && !isRightPair; if (!needUpdateRight)
}
private void UpdateAllEntries()
{ {
ref Array10<ShMemNpad> controllers = ref _device.Hid.SharedMemory.Npads; SixAxisSensorState emptyState = new SixAxisSensorState();
for (int i = 0; i < controllers.Length; ++i)
emptyState.Attributes = SixAxisSensorAttribute.IsConnected;
WriteNewSixInputEntry(ref currentNpad.JoyDualRightSixAxisSensor, ref emptyState);
}
return needUpdateRight;
}
private void UpdateDisconnectedInputSixAxis(PlayerIndex index)
{ {
ref Array7<NpadLayout> layouts = ref controllers[i].Layouts; ref NpadInternalState currentNpad = ref _device.Hid.SharedMemory.Npads[(int)index].InternalState;
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]; SixAxisSensorState newState = new SixAxisSensorState();
NpadState previousEntry = currentLayout.Entries[previousIndex];
currentEntry.SampleTimestamp = previousEntry.SampleTimestamp + 1; newState.Attributes = SixAxisSensorAttribute.IsConnected;
currentEntry.SampleTimestamp2 = previousEntry.SampleTimestamp2 + 1;
if (controllers[i].Header.Type == ControllerType.None) WriteNewSixInputEntry(ref currentNpad.FullKeySixAxisSensor, ref newState);
{ WriteNewSixInputEntry(ref currentNpad.HandheldSixAxisSensor, ref newState);
continue; WriteNewSixInputEntry(ref currentNpad.JoyDualSixAxisSensor, ref newState);
} WriteNewSixInputEntry(ref currentNpad.JoyDualRightSixAxisSensor, ref newState);
WriteNewSixInputEntry(ref currentNpad.JoyLeftSixAxisSensor, ref newState);
currentEntry.ConnectionState = NpadConnectionState.ControllerStateConnected; WriteNewSixInputEntry(ref currentNpad.JoyRightSixAxisSensor, ref newState);
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;
}
}
ref Array6<NpadSixAxis> sixaxis = ref controllers[i].Sixaxis;
for (int l = 0; l < sixaxis.Length; ++l)
{
ref NpadSixAxis currentLayout = ref sixaxis[l];
int currentIndex = UpdateEntriesHeader(ref currentLayout.Header, out int previousIndex);
ref SixAxisState currentEntry = ref currentLayout.Entries[currentIndex];
SixAxisState previousEntry = currentLayout.Entries[previousIndex];
currentEntry.SampleTimestamp = previousEntry.SampleTimestamp + 1;
currentEntry.SampleTimestamp2 = previousEntry.SampleTimestamp2 + 1;
currentEntry._unknown2 = 1;
}
}
} }
} }
} }

View file

@ -1,3 +1,5 @@
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.TouchScreen;
using System; using System;
namespace Ryujinx.HLE.HOS.Services.Hid namespace Ryujinx.HLE.HOS.Services.Hid
@ -8,39 +10,38 @@ namespace Ryujinx.HLE.HOS.Services.Hid
public void Update(params TouchPoint[] points) public void Update(params TouchPoint[] points)
{ {
ref ShMemTouchScreen touchscreen = ref _device.Hid.SharedMemory.TouchScreen; ref RingLifo<TouchScreenState> lifo = ref _device.Hid.SharedMemory.TouchScreen;
int currentIndex = UpdateEntriesHeader(ref touchscreen.Header, out int previousIndex); ref TouchScreenState previousEntry = ref lifo.GetCurrentEntryRef();
if (!Active) TouchScreenState newState = new TouchScreenState
{ {
return; SamplingNumber = previousEntry.SamplingNumber + 1
} };
ref TouchScreenState currentEntry = ref touchscreen.Entries[currentIndex]; if (Active)
TouchScreenState previousEntry = touchscreen.Entries[previousIndex]; {
newState.TouchesCount = points.Length;
currentEntry.SampleTimestamp = previousEntry.SampleTimestamp + 1; int pointsLength = Math.Min(points.Length, newState.Touches.Length);
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) for (int i = 0; i < pointsLength; ++i)
{ {
TouchPoint pi = points[i]; TouchPoint pi = points[i];
currentEntry.Touches[i] = new TouchScreenStateData newState.Touches[i] = new TouchState
{ {
SampleTimestamp = currentEntry.SampleTimestamp, DeltaTime = newState.SamplingNumber,
X = pi.X, X = pi.X,
Y = pi.Y, Y = pi.Y,
TouchIndex = (uint)i, FingerId = (uint)i,
DiameterX = pi.DiameterX, DiameterX = pi.DiameterX,
DiameterY = pi.DiameterY, DiameterY = pi.DiameterY,
Angle = pi.Angle RotationAngle = pi.Angle
}; };
} }
} }
lifo.Write(ref newState);
}
} }
} }

View file

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

View file

@ -3,6 +3,7 @@ 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.HOS.Services.Hid.Types;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -134,7 +135,7 @@ namespace Ryujinx.HLE.HOS.Services.Hid
// Initialize entries to avoid issues with some games. // Initialize entries to avoid issues with some games.
KeyboardInput emptyInput = new KeyboardInput(); KeyboardInput emptyInput = new KeyboardInput();
emptyInput.Keys = new int[8]; emptyInput.Keys = new ulong[4];
for (int entry = 0; entry < Hid.SharedMemEntryCount; entry++) for (int entry = 0; entry < Hid.SharedMemEntryCount; entry++)
{ {

View file

@ -1,9 +0,0 @@
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

@ -1,4 +1,4 @@
namespace Ryujinx.HLE.HOS.Services.Hid namespace Ryujinx.HLE.HOS.Services.Hid.Types
{ {
struct HidVector struct HidVector
{ {

View file

@ -1,6 +1,6 @@
namespace Ryujinx.HLE.HOS.Services.Hid namespace Ryujinx.HLE.HOS.Services.Hid
{ {
public enum NpadColor : int public enum NpadColor : uint
{ {
BodyGray = 0x828282, BodyGray = 0x828282,
BodyNeonRed = 0xFF3C28, BodyNeonRed = 0xFF3C28,

View file

@ -1,4 +1,4 @@
namespace Ryujinx.HLE.HOS.Services.Hid namespace Ryujinx.HLE.HOS.Services.Hid.Types
{ {
enum NpadJoyHoldType enum NpadJoyHoldType
{ {

View file

@ -1,11 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
struct CommonEntriesHeader
{
public ulong TimestampTicks;
public ulong NumEntries;
public ulong LatestEntry;
public ulong MaxEntryIndex;
}
}

View file

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

View file

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

View file

@ -1,23 +0,0 @@
using Ryujinx.Common.Memory;
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

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

View file

@ -1,10 +0,0 @@
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

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

View file

@ -1,12 +0,0 @@
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

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

View file

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

View file

@ -1,26 +0,0 @@
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

@ -1,23 +0,0 @@
using Ryujinx.Common.Memory;
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,10 +0,0 @@
using System;
namespace Ryujinx.HLE.HOS.Services.Hid
{
[Flags]
enum NpadColorDescription : int
{
ColorDescriptionColorsNonexistent = (1 << 1)
}
}

View file

@ -1,13 +0,0 @@
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

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

View file

@ -1,13 +0,0 @@
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

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

View file

@ -1,14 +0,0 @@
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

@ -1,16 +0,0 @@
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

@ -1,22 +0,0 @@
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

@ -1,14 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
enum SixAxixLayoutsIndex : int
{
ProController = 0,
Handheld = 1,
JoyDualLeft = 2,
JoyDualRight = 3,
JoyLeft = 4,
JoyRight = 5,
Pokeball = 6,
SystemExternal = 7
}
}

View file

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

View file

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

View file

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

View file

@ -1,19 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Hid
{
struct TouchScreenStateData
{
public ulong SampleTimestamp;
#pragma warning disable CS0169
uint _padding;
#pragma warning restore CS0169
public uint TouchIndex;
public uint X;
public uint Y;
public uint DiameterX;
public uint DiameterY;
public uint Angle;
#pragma warning disable CS0169
uint _padding2;
#pragma warning restore CS0169
}
}

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
{
public ulong SamplingNumber;
public T Object;
public ulong ReadSamplingNumberAtomic()
{
return Interlocked.Read(ref SamplingNumber);
}
public void SetObject(ref T obj)
{
ISampledData samplingProvider = obj as ISampledData;
Interlocked.Exchange(ref SamplingNumber, samplingProvider.SamplingNumber);
Thread.MemoryBarrier();
Object = obj;
}
}
}

View file

@ -0,0 +1,7 @@
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common
{
interface ISampledData
{
ulong SamplingNumber { get; }
}
}

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
{
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;
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugPad
{
struct DebugPadState : ISampledData
{
public ulong SamplingNumber;
public DebugPadAttribute Attributes;
public DebugPadButton Buttons;
public AnalogStickState AnalogStickR;
public AnalogStickState AnalogStickL;
ulong ISampledData.SamplingNumber => SamplingNumber;
}
}

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,21 @@
using System;
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard
{
// TODO: This seems entirely wrong
[Flags]
enum KeyboardModifier : uint
{
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;
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard
{
struct KeyboardState : ISampledData
{
public ulong SamplingNumber;
public KeyboardModifier Modifiers;
public KeyboardKey Keys;
ulong ISampledData.SamplingNumber => SamplingNumber;
}
}

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;
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse
{
struct MouseState : ISampledData
{
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;
ulong ISampledData.SamplingNumber => SamplingNumber;
}
}

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;
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
{
struct NpadCommonState : ISampledData
{
public ulong SamplingNumber;
public NpadButton Buttons;
public AnalogStickState AnalogStickL;
public AnalogStickState AnalogStickR;
public NpadAttribute Attributes;
private uint _reserved;
ulong ISampledData.SamplingNumber => SamplingNumber;
}
}

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;
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
{
struct NpadGcTriggerState : ISampledData
{
#pragma warning disable CS0649
public ulong SamplingNumber;
public uint TriggerL;
public uint TriggerR;
#pragma warning restore CS0649
ulong ISampledData.SamplingNumber => SamplingNumber;
}
}

View file

@ -0,0 +1,61 @@
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
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 byte AppletFooterUiType;
private unsafe fixed byte _reserved2[0x7B];
public RingLifo<NpadGcTriggerState> GcTrigger;
public NpadLarkType LarkTypeLeftAndMain;
public NpadLarkType LarkTypeRight;
public NpadLuciaType LuciaType;
public uint Unknown43EC;
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;
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
{
struct SixAxisSensorState : ISampledData
{
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;
ulong ISampledData.SamplingNumber => SamplingNumber;
}
}

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]
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;
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.TouchScreen
{
struct TouchScreenState : ISampledData
{
public ulong SamplingNumber;
public int TouchesCount;
private int _reserved;
public Array16<TouchState> Touches;
ulong ISampledData.SamplingNumber => SamplingNumber;
}
}

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

View file

@ -456,14 +456,14 @@ namespace Ryujinx.Input.HLE
KeyboardInput hidKeyboard = new KeyboardInput KeyboardInput hidKeyboard = new KeyboardInput
{ {
Modifier = 0, Modifier = 0,
Keys = new int[0x8] Keys = new ulong[0x4]
}; };
foreach (HLEKeyboardMappingEntry entry in KeyMapping) foreach (HLEKeyboardMappingEntry entry in KeyMapping)
{ {
int value = keyboardState.IsPressed(entry.TargetKey) ? 1 : 0; ulong value = keyboardState.IsPressed(entry.TargetKey) ? 1UL : 0UL;
hidKeyboard.Keys[entry.Target / 0x20] |= (value << (entry.Target % 0x20)); hidKeyboard.Keys[entry.Target / 0x40] |= (value << (entry.Target % 0x40));
} }
foreach (HLEKeyboardMappingEntry entry in KeyModifierMapping) foreach (HLEKeyboardMappingEntry entry in KeyModifierMapping)