aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.HLE/HOS/Services/Nfc
diff options
context:
space:
mode:
authorTSR Berry <20988865+TSRBerry@users.noreply.github.com>2023-04-08 01:22:00 +0200
committerMary <thog@protonmail.com>2023-04-27 23:51:14 +0200
commitcee712105850ac3385cd0091a923438167433f9f (patch)
tree4a5274b21d8b7f938c0d0ce18736d3f2993b11b1 /src/Ryujinx.HLE/HOS/Services/Nfc
parentcd124bda587ef09668a971fa1cac1c3f0cfc9f21 (diff)
Move solution and projects to src
Diffstat (limited to 'src/Ryujinx.HLE/HOS/Services/Nfc')
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/IAmManager.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/ISystemManager.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/IUserManager.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/IUserManager.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/INfc.cs63
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/Types/NfcPermissionLevel.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/Types/State.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/AmiiboJsonSerializerContext.cs10
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/IDebugManager.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ISystemManager.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/IUserManager.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/INfp.cs1000
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/AmiiboConstants.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/CommonInfo.cs17
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/DeviceType.cs7
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/ModelInfo.cs16
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/MountTarget.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpDevice.cs23
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpDeviceState.cs13
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpPermissionLevel.cs9
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/RegisterInfo.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/State.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/TagInfo.cs16
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/VirtualAmiiboFile.cs22
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ResultCode.cs18
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs204
26 files changed, 1589 insertions, 0 deletions
diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/IAmManager.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/IAmManager.cs
new file mode 100644
index 00000000..33932568
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Nfc/IAmManager.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Nfc
+{
+ [Service("nfc:am")]
+ class IAmManager : IpcService
+ {
+ public IAmManager(ServiceCtx context) { }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/ISystemManager.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/ISystemManager.cs
new file mode 100644
index 00000000..ef90b6ad
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Nfc/ISystemManager.cs
@@ -0,0 +1,19 @@
+using Ryujinx.HLE.HOS.Services.Nfc.NfcManager;
+
+namespace Ryujinx.HLE.HOS.Services.Nfc
+{
+ [Service("nfc:sys")]
+ class ISystemManager : IpcService
+ {
+ public ISystemManager(ServiceCtx context) { }
+
+ [CommandCmif(0)]
+ // CreateSystemInterface() -> object<nn::nfc::detail::ISystem>
+ public ResultCode CreateSystemInterface(ServiceCtx context)
+ {
+ MakeObject(context, new INfc(NfcPermissionLevel.System));
+
+ return ResultCode.Success;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/IUserManager.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/IUserManager.cs
new file mode 100644
index 00000000..97959a62
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Nfc/IUserManager.cs
@@ -0,0 +1,19 @@
+using Ryujinx.HLE.HOS.Services.Nfc.NfcManager;
+
+namespace Ryujinx.HLE.HOS.Services.Nfc
+{
+ [Service("nfc:user")]
+ class IUserManager : IpcService
+ {
+ public IUserManager(ServiceCtx context) { }
+
+ [CommandCmif(0)]
+ // CreateUserInterface() -> object<nn::nfc::detail::IUser>
+ public ResultCode CreateUserInterface(ServiceCtx context)
+ {
+ MakeObject(context, new INfc(NfcPermissionLevel.User));
+
+ return ResultCode.Success;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/IUserManager.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/IUserManager.cs
new file mode 100644
index 00000000..cc3cd3aa
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/IUserManager.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare
+{
+ [Service("nfc:mf:u")]
+ class IUserManager : IpcService
+ {
+ public IUserManager(ServiceCtx context) { }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/INfc.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/INfc.cs
new file mode 100644
index 00000000..b091aabf
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/INfc.cs
@@ -0,0 +1,63 @@
+using Ryujinx.Common.Logging;
+
+namespace Ryujinx.HLE.HOS.Services.Nfc.NfcManager
+{
+ class INfc : IpcService
+ {
+ private NfcPermissionLevel _permissionLevel;
+ private State _state;
+
+ public INfc(NfcPermissionLevel permissionLevel)
+ {
+ _permissionLevel = permissionLevel;
+ _state = State.NonInitialized;
+ }
+
+ [CommandCmif(0)]
+ [CommandCmif(400)] // 4.0.0+
+ // Initialize(u64, u64, pid, buffer<unknown, 5>)
+ public ResultCode Initialize(ServiceCtx context)
+ {
+ _state = State.Initialized;
+
+ Logger.Stub?.PrintStub(LogClass.ServiceNfc, new { _permissionLevel });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(1)]
+ [CommandCmif(401)] // 4.0.0+
+ // Finalize()
+ public ResultCode Finalize(ServiceCtx context)
+ {
+ _state = State.NonInitialized;
+
+ Logger.Stub?.PrintStub(LogClass.ServiceNfc, new { _permissionLevel });
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(2)]
+ [CommandCmif(402)] // 4.0.0+
+ // GetState() -> u32
+ public ResultCode GetState(ServiceCtx context)
+ {
+ context.ResponseData.Write((int)_state);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(3)]
+ [CommandCmif(403)] // 4.0.0+
+ // IsNfcEnabled() -> b8
+ public ResultCode IsNfcEnabled(ServiceCtx context)
+ {
+ // NOTE: Write false value here could make nfp service not called.
+ context.ResponseData.Write(true);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceNfc, new { _permissionLevel });
+
+ return ResultCode.Success;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/Types/NfcPermissionLevel.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/Types/NfcPermissionLevel.cs
new file mode 100644
index 00000000..39babc73
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/Types/NfcPermissionLevel.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Nfc.NfcManager
+{
+ enum NfcPermissionLevel
+ {
+ User,
+ System
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/Types/State.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/Types/State.cs
new file mode 100644
index 00000000..85f99950
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Nfc/NfcManager/Types/State.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Nfc.NfcManager
+{
+ enum State
+ {
+ NonInitialized,
+ Initialized
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/AmiiboJsonSerializerContext.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/AmiiboJsonSerializerContext.cs
new file mode 100644
index 00000000..e75f6200
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/AmiiboJsonSerializerContext.cs
@@ -0,0 +1,10 @@
+using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager;
+using System.Text.Json.Serialization;
+
+namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
+{
+ [JsonSerializable(typeof(VirtualAmiiboFile))]
+ internal partial class AmiiboJsonSerializerContext : JsonSerializerContext
+ {
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/IDebugManager.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/IDebugManager.cs
new file mode 100644
index 00000000..fc454473
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/IDebugManager.cs
@@ -0,0 +1,19 @@
+using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager;
+
+namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
+{
+ [Service("nfp:dbg")]
+ class IAmManager : IpcService
+ {
+ public IAmManager(ServiceCtx context) { }
+
+ [CommandCmif(0)]
+ // CreateDebugInterface() -> object<nn::nfp::detail::IDebug>
+ public ResultCode CreateDebugInterface(ServiceCtx context)
+ {
+ MakeObject(context, new INfp(NfpPermissionLevel.Debug));
+
+ return ResultCode.Success;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ISystemManager.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ISystemManager.cs
new file mode 100644
index 00000000..3fcf7a87
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ISystemManager.cs
@@ -0,0 +1,19 @@
+using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager;
+
+namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
+{
+ [Service("nfp:sys")]
+ class ISystemManager : IpcService
+ {
+ public ISystemManager(ServiceCtx context) { }
+
+ [CommandCmif(0)]
+ // CreateSystemInterface() -> object<nn::nfp::detail::ISystem>
+ public ResultCode CreateSystemInterface(ServiceCtx context)
+ {
+ MakeObject(context, new INfp(NfpPermissionLevel.System));
+
+ return ResultCode.Success;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/IUserManager.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/IUserManager.cs
new file mode 100644
index 00000000..93da8419
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/IUserManager.cs
@@ -0,0 +1,19 @@
+using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager;
+
+namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
+{
+ [Service("nfp:user")]
+ class IUserManager : IpcService
+ {
+ public IUserManager(ServiceCtx context) { }
+
+ [CommandCmif(0)]
+ // CreateUserInterface() -> object<nn::nfp::detail::IUser>
+ public ResultCode CreateUserInterface(ServiceCtx context)
+ {
+ MakeObject(context, new INfp(NfpPermissionLevel.User));
+
+ return ResultCode.Success;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/INfp.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/INfp.cs
new file mode 100644
index 00000000..e25a2972
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/INfp.cs
@@ -0,0 +1,1000 @@
+using Ryujinx.Common.Memory;
+using Ryujinx.Cpu;
+using Ryujinx.HLE.Exceptions;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.HLE.HOS.Services.Hid;
+using Ryujinx.HLE.HOS.Services.Hid.HidServer;
+using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager;
+using Ryujinx.Horizon.Common;
+using System;
+using System.Buffers.Binary;
+using System.Globalization;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
+{
+ class INfp : IpcService
+ {
+ private ulong _appletResourceUserId;
+ private ulong _mcuVersionData;
+ private byte[] _mcuData;
+
+ private State _state = State.NonInitialized;
+
+ private KEvent _availabilityChangeEvent;
+
+ private CancellationTokenSource _cancelTokenSource;
+
+ private NfpPermissionLevel _permissionLevel;
+
+ public INfp(NfpPermissionLevel permissionLevel)
+ {
+ _permissionLevel = permissionLevel;
+ }
+
+ [CommandCmif(0)]
+ // Initialize(u64, u64, pid, buffer<unknown, 5>)
+ public ResultCode Initialize(ServiceCtx context)
+ {
+ _appletResourceUserId = context.RequestData.ReadUInt64();
+ _mcuVersionData = context.RequestData.ReadUInt64();
+
+ ulong inputPosition = context.Request.SendBuff[0].Position;
+ ulong inputSize = context.Request.SendBuff[0].Size;
+
+ _mcuData = new byte[inputSize];
+
+ context.Memory.Read(inputPosition, _mcuData);
+
+ // TODO: The mcuData buffer seems to contains entries with a size of 0x40 bytes each. Usage of the data needs to be determined.
+
+ // TODO: Handle this in a controller class directly.
+ // Every functions which use the Handle call nn::hid::system::GetXcdHandleForNpadWithNfc().
+ NfpDevice devicePlayer1 = new NfpDevice
+ {
+ NpadIdType = NpadIdType.Player1,
+ Handle = HidUtils.GetIndexFromNpadIdType(NpadIdType.Player1),
+ State = NfpDeviceState.Initialized
+ };
+
+ context.Device.System.NfpDevices.Add(devicePlayer1);
+
+ // TODO: It mounts 0x8000000000000020 save data and stores a random generate value inside. Usage of the data needs to be determined.
+
+ _state = State.Initialized;
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(1)]
+ // Finalize()
+ public ResultCode Finalize(ServiceCtx context)
+ {
+ if (_state == State.Initialized)
+ {
+ if (_cancelTokenSource != null)
+ {
+ _cancelTokenSource.Cancel();
+ }
+
+ // NOTE: All events are destroyed here.
+ context.Device.System.NfpDevices.Clear();
+
+ _state = State.NonInitialized;
+ }
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(2)]
+ // ListDevices() -> (u32, buffer<unknown, 0xa>)
+ public ResultCode ListDevices(ServiceCtx context)
+ {
+ if (context.Request.RecvListBuff.Count == 0)
+ {
+ return ResultCode.WrongArgument;
+ }
+
+ ulong outputPosition = context.Request.RecvListBuff[0].Position;
+ ulong outputSize = context.Request.RecvListBuff[0].Size;
+
+ if (context.Device.System.NfpDevices.Count == 0)
+ {
+ return ResultCode.DeviceNotFound;
+ }
+
+ MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize);
+
+ if (CheckNfcIsEnabled() == ResultCode.Success)
+ {
+ for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
+ {
+ context.Memory.Write(outputPosition + ((uint)i * sizeof(long)), (uint)context.Device.System.NfpDevices[i].Handle);
+ }
+
+ context.ResponseData.Write(context.Device.System.NfpDevices.Count);
+ }
+ else
+ {
+ context.ResponseData.Write(0);
+ }
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(3)]
+ // StartDetection(bytes<8, 4>)
+ public ResultCode StartDetection(ServiceCtx context)
+ {
+ ResultCode resultCode = CheckNfcIsEnabled();
+
+ if (resultCode != ResultCode.Success)
+ {
+ return resultCode;
+ }
+
+ uint deviceHandle = (uint)context.RequestData.ReadUInt64();
+
+ for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
+ {
+ if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle)
+ {
+ context.Device.System.NfpDevices[i].State = NfpDeviceState.SearchingForTag;
+
+ break;
+ }
+ }
+
+ _cancelTokenSource = new CancellationTokenSource();
+
+ Task.Run(() =>
+ {
+ while (true)
+ {
+ if (_cancelTokenSource.Token.IsCancellationRequested)
+ {
+ break;
+ }
+
+ for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
+ {
+ if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagFound)
+ {
+ context.Device.System.NfpDevices[i].SignalActivate();
+ Thread.Sleep(125); // NOTE: Simulate amiibo scanning delay.
+ context.Device.System.NfpDevices[i].SignalDeactivate();
+
+ break;
+ }
+ }
+ }
+ }, _cancelTokenSource.Token);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(4)]
+ // StopDetection(bytes<8, 4>)
+ public ResultCode StopDetection(ServiceCtx context)
+ {
+ ResultCode resultCode = CheckNfcIsEnabled();
+
+ if (resultCode != ResultCode.Success)
+ {
+ return resultCode;
+ }
+
+ if (_cancelTokenSource != null)
+ {
+ _cancelTokenSource.Cancel();
+ }
+
+ uint deviceHandle = (uint)context.RequestData.ReadUInt64();
+
+ for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
+ {
+ if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle)
+ {
+ context.Device.System.NfpDevices[i].State = NfpDeviceState.Initialized;
+
+ break;
+ }
+ }
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(5)]
+ // Mount(bytes<8, 4>, u32, u32)
+ public ResultCode Mount(ServiceCtx context)
+ {
+ ResultCode resultCode = CheckNfcIsEnabled();
+
+ if (resultCode != ResultCode.Success)
+ {
+ return resultCode;
+ }
+
+ uint deviceHandle = (uint)context.RequestData.ReadUInt64();
+ DeviceType deviceType = (DeviceType)context.RequestData.ReadUInt32();
+ MountTarget mountTarget = (MountTarget)context.RequestData.ReadUInt32();
+
+ if (deviceType != 0)
+ {
+ return ResultCode.WrongArgument;
+ }
+
+ if (((uint)mountTarget & 3) == 0)
+ {
+ return ResultCode.WrongArgument;
+ }
+
+ // TODO: Found how the MountTarget is handled.
+
+ for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
+ {
+ if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle)
+ {
+ if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved)
+ {
+ resultCode = ResultCode.TagNotFound;
+ }
+ else
+ {
+ if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagFound)
+ {
+ // NOTE: This mount the amiibo data, which isn't needed in our case.
+
+ context.Device.System.NfpDevices[i].State = NfpDeviceState.TagMounted;
+
+ resultCode = ResultCode.Success;
+ }
+ else
+ {
+ resultCode = ResultCode.WrongDeviceState;
+ }
+ }
+
+ break;
+ }
+ }
+
+ return resultCode;
+ }
+
+ [CommandCmif(6)]
+ // Unmount(bytes<8, 4>)
+ public ResultCode Unmount(ServiceCtx context)
+ {
+ ResultCode resultCode = CheckNfcIsEnabled();
+
+ if (resultCode != ResultCode.Success)
+ {
+ return resultCode;
+ }
+
+ uint deviceHandle = (uint)context.RequestData.ReadUInt64();
+
+ if (context.Device.System.NfpDevices.Count == 0)
+ {
+ return ResultCode.DeviceNotFound;
+ }
+
+ for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
+ {
+ if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle)
+ {
+ if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved)
+ {
+ resultCode = ResultCode.TagNotFound;
+ }
+ else
+ {
+ // NOTE: This mount the amiibo data, which isn't needed in our case.
+
+ context.Device.System.NfpDevices[i].State = NfpDeviceState.TagFound;
+
+ resultCode = ResultCode.Success;
+ }
+
+ break;
+ }
+ }
+
+ return resultCode;
+ }
+
+ [CommandCmif(7)]
+ // OpenApplicationArea(bytes<8, 4>, u32)
+ public ResultCode OpenApplicationArea(ServiceCtx context)
+ {
+ ResultCode resultCode = CheckNfcIsEnabled();
+
+ if (resultCode != ResultCode.Success)
+ {
+ return resultCode;
+ }
+
+ uint deviceHandle = (uint)context.RequestData.ReadUInt64();
+
+ if (context.Device.System.NfpDevices.Count == 0)
+ {
+ return ResultCode.DeviceNotFound;
+ }
+
+ uint applicationAreaId = context.RequestData.ReadUInt32();
+
+ bool isOpened = false;
+
+ for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
+ {
+ if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle)
+ {
+ if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved)
+ {
+ resultCode = ResultCode.TagNotFound;
+ }
+ else
+ {
+ if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted)
+ {
+ isOpened = VirtualAmiibo.OpenApplicationArea(context.Device.System.NfpDevices[i].AmiiboId, applicationAreaId);
+
+ resultCode = ResultCode.Success;
+ }
+ else
+ {
+ resultCode = ResultCode.WrongDeviceState;
+ }
+ }
+
+ break;
+ }
+ }
+
+ if (!isOpened)
+ {
+ resultCode = ResultCode.ApplicationAreaIsNull;
+ }
+
+ return resultCode;
+ }
+
+ [CommandCmif(8)]
+ // GetApplicationArea(bytes<8, 4>) -> (u32, buffer<unknown, 6>)
+ public ResultCode GetApplicationArea(ServiceCtx context)
+ {
+ ResultCode resultCode = CheckNfcIsEnabled();
+
+ if (resultCode != ResultCode.Success)
+ {
+ return resultCode;
+ }
+
+ uint deviceHandle = (uint)context.RequestData.ReadUInt64();
+
+ if (context.Device.System.NfpDevices.Count == 0)
+ {
+ return ResultCode.DeviceNotFound;
+ }
+
+ ulong outputPosition = context.Request.ReceiveBuff[0].Position;
+ ulong outputSize = context.Request.ReceiveBuff[0].Size;
+
+ MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize);
+
+ uint size = 0;
+
+ for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
+ {
+ if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle)
+ {
+ if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved)
+ {
+ resultCode = ResultCode.TagNotFound;
+ }
+ else
+ {
+ if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted)
+ {
+ byte[] applicationArea = VirtualAmiibo.GetApplicationArea(context.Device.System.NfpDevices[i].AmiiboId);
+
+ context.Memory.Write(outputPosition, applicationArea);
+
+ size = (uint)applicationArea.Length;
+
+ resultCode = ResultCode.Success;
+ }
+ else
+ {
+ resultCode = ResultCode.WrongDeviceState;
+ }
+ }
+ }
+ }
+
+ if (resultCode != ResultCode.Success)
+ {
+ return resultCode;
+ }
+
+ if (size == 0)
+ {
+ return ResultCode.ApplicationAreaIsNull;
+ }
+
+ context.ResponseData.Write(size);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(9)]
+ // SetApplicationArea(bytes<8, 4>, buffer<unknown, 5>)
+ public ResultCode SetApplicationArea(ServiceCtx context)
+ {
+ ResultCode resultCode = CheckNfcIsEnabled();
+
+ if (resultCode != ResultCode.Success)
+ {
+ return resultCode;
+ }
+
+ uint deviceHandle = (uint)context.RequestData.ReadUInt64();
+
+ if (context.Device.System.NfpDevices.Count == 0)
+ {
+ return ResultCode.DeviceNotFound;
+ }
+
+ ulong inputPosition = context.Request.SendBuff[0].Position;
+ ulong inputSize = context.Request.SendBuff[0].Size;
+
+ byte[] applicationArea = new byte[inputSize];
+
+ context.Memory.Read(inputPosition, applicationArea);
+
+ for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
+ {
+ if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle)
+ {
+ if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved)
+ {
+ resultCode = ResultCode.TagNotFound;
+ }
+ else
+ {
+ if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted)
+ {
+ VirtualAmiibo.SetApplicationArea(context.Device.System.NfpDevices[i].AmiiboId, applicationArea);
+
+ resultCode = ResultCode.Success;
+ }
+ else
+ {
+ resultCode = ResultCode.WrongDeviceState;
+ }
+ }
+
+ break;
+ }
+ }
+
+ return resultCode;
+ }
+
+ [CommandCmif(10)]
+ // Flush(bytes<8, 4>)
+ public ResultCode Flush(ServiceCtx context)
+ {
+ uint deviceHandle = (uint)context.RequestData.ReadUInt64();
+
+ if (context.Device.System.NfpDevices.Count == 0)
+ {
+ return ResultCode.DeviceNotFound;
+ }
+
+ // NOTE: Since we handle amiibo through VirtualAmiibo, we don't have to flush anything in our case.
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(11)]
+ // Restore(bytes<8, 4>)
+ public ResultCode Restore(ServiceCtx context)
+ {
+ throw new ServiceNotImplementedException(this, context);
+ }
+
+ [CommandCmif(12)]
+ // CreateApplicationArea(bytes<8, 4>, u32, buffer<unknown, 5>)
+ public ResultCode CreateApplicationArea(ServiceCtx context)
+ {
+ ResultCode resultCode = CheckNfcIsEnabled();
+
+ if (resultCode != ResultCode.Success)
+ {
+ return resultCode;
+ }
+
+ uint deviceHandle = (uint)context.RequestData.ReadUInt64();
+
+ if (context.Device.System.NfpDevices.Count == 0)
+ {
+ return ResultCode.DeviceNotFound;
+ }
+
+ uint applicationAreaId = context.RequestData.ReadUInt32();
+
+ ulong inputPosition = context.Request.SendBuff[0].Position;
+ ulong inputSize = context.Request.SendBuff[0].Size;
+
+ byte[] applicationArea = new byte[inputSize];
+
+ context.Memory.Read(inputPosition, applicationArea);
+
+ bool isCreated = false;
+
+ for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
+ {
+ if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle)
+ {
+ if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved)
+ {
+ resultCode = ResultCode.TagNotFound;
+ }
+ else
+ {
+ if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted)
+ {
+ isCreated = VirtualAmiibo.CreateApplicationArea(context.Device.System.NfpDevices[i].AmiiboId, applicationAreaId, applicationArea);
+
+ resultCode = ResultCode.Success;
+ }
+ else
+ {
+ resultCode = ResultCode.WrongDeviceState;
+ }
+ }
+
+ break;
+ }
+ }
+
+ if (!isCreated)
+ {
+ resultCode = ResultCode.ApplicationAreaIsNull;
+ }
+
+ return resultCode;
+ }
+
+ [CommandCmif(13)]
+ // GetTagInfo(bytes<8, 4>) -> buffer<unknown<0x58>, 0x1a>
+ public ResultCode GetTagInfo(ServiceCtx context)
+ {
+ ResultCode resultCode = CheckNfcIsEnabled();
+
+ if (resultCode != ResultCode.Success)
+ {
+ return resultCode;
+ }
+
+ if (context.Request.RecvListBuff.Count == 0)
+ {
+ return ResultCode.WrongArgument;
+ }
+
+ ulong outputPosition = context.Request.RecvListBuff[0].Position;
+
+ context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize((uint)Marshal.SizeOf<TagInfo>());
+
+ MemoryHelper.FillWithZeros(context.Memory, outputPosition, Marshal.SizeOf<TagInfo>());
+
+ uint deviceHandle = (uint)context.RequestData.ReadUInt64();
+
+ if (context.Device.System.NfpDevices.Count == 0)
+ {
+ return ResultCode.DeviceNotFound;
+ }
+
+ for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
+ {
+ if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle)
+ {
+ if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved)
+ {
+ resultCode = ResultCode.TagNotFound;
+ }
+ else
+ {
+ if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted || context.Device.System.NfpDevices[i].State == NfpDeviceState.TagFound)
+ {
+ byte[] Uuid = VirtualAmiibo.GenerateUuid(context.Device.System.NfpDevices[i].AmiiboId, context.Device.System.NfpDevices[i].UseRandomUuid);
+
+ if (Uuid.Length > AmiiboConstants.UuidMaxLength)
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+
+ TagInfo tagInfo = new TagInfo
+ {
+ UuidLength = (byte)Uuid.Length,
+ Reserved1 = new Array21<byte>(),
+ Protocol = uint.MaxValue, // All Protocol
+ TagType = uint.MaxValue, // All Type
+ Reserved2 = new Array6<byte>()
+ };
+
+ Uuid.CopyTo(tagInfo.Uuid.AsSpan());
+
+ context.Memory.Write(outputPosition, tagInfo);
+
+ resultCode = ResultCode.Success;
+ }
+ else
+ {
+ resultCode = ResultCode.WrongDeviceState;
+ }
+ }
+
+ break;
+ }
+ }
+
+ return resultCode;
+ }
+
+ [CommandCmif(14)]
+ // GetRegisterInfo(bytes<8, 4>) -> buffer<unknown<0x100>, 0x1a>
+ public ResultCode GetRegisterInfo(ServiceCtx context)
+ {
+ ResultCode resultCode = CheckNfcIsEnabled();
+
+ if (resultCode != ResultCode.Success)
+ {
+ return resultCode;
+ }
+
+ if (context.Request.RecvListBuff.Count == 0)
+ {
+ return ResultCode.WrongArgument;
+ }
+
+ ulong outputPosition = context.Request.RecvListBuff[0].Position;
+
+ context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize((uint)Marshal.SizeOf<RegisterInfo>());
+
+ MemoryHelper.FillWithZeros(context.Memory, outputPosition, Marshal.SizeOf<RegisterInfo>());
+
+ uint deviceHandle = (uint)context.RequestData.ReadUInt64();
+
+ if (context.Device.System.NfpDevices.Count == 0)
+ {
+ return ResultCode.DeviceNotFound;
+ }
+
+ for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
+ {
+ if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle)
+ {
+ if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved)
+ {
+ resultCode = ResultCode.TagNotFound;
+ }
+ else
+ {
+ if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted)
+ {
+ RegisterInfo registerInfo = VirtualAmiibo.GetRegisterInfo(
+ context.Device.System.TickSource,
+ context.Device.System.NfpDevices[i].AmiiboId,
+ context.Device.System.AccountManager.LastOpenedUser.Name);
+
+ context.Memory.Write(outputPosition, registerInfo);
+
+ resultCode = ResultCode.Success;
+ }
+ else
+ {
+ resultCode = ResultCode.WrongDeviceState;
+ }
+ }
+
+ break;
+ }
+ }
+
+ return resultCode;
+ }
+
+ [CommandCmif(15)]
+ // GetCommonInfo(bytes<8, 4>) -> buffer<unknown<0x40>, 0x1a>
+ public ResultCode GetCommonInfo(ServiceCtx context)
+ {
+ ResultCode resultCode = CheckNfcIsEnabled();
+
+ if (resultCode != ResultCode.Success)
+ {
+ return resultCode;
+ }
+
+ if (context.Request.RecvListBuff.Count == 0)
+ {
+ return ResultCode.WrongArgument;
+ }
+
+ ulong outputPosition = context.Request.RecvListBuff[0].Position;
+
+ context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize((uint)Marshal.SizeOf<CommonInfo>());
+
+ MemoryHelper.FillWithZeros(context.Memory, outputPosition, Marshal.SizeOf<CommonInfo>());
+
+ uint deviceHandle = (uint)context.RequestData.ReadUInt64();
+
+ if (context.Device.System.NfpDevices.Count == 0)
+ {
+ return ResultCode.DeviceNotFound;
+ }
+
+ for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
+ {
+ if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle)
+ {
+ if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved)
+ {
+ resultCode = ResultCode.TagNotFound;
+ }
+ else
+ {
+ if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted)
+ {
+ CommonInfo commonInfo = VirtualAmiibo.GetCommonInfo(context.Device.System.NfpDevices[i].AmiiboId);
+
+ context.Memory.Write(outputPosition, commonInfo);
+
+ resultCode = ResultCode.Success;
+ }
+ else
+ {
+ resultCode = ResultCode.WrongDeviceState;
+ }
+ }
+
+ break;
+ }
+ }
+
+ return resultCode;
+ }
+
+ [CommandCmif(16)]
+ // GetModelInfo(bytes<8, 4>) -> buffer<unknown<0x40>, 0x1a>
+ public ResultCode GetModelInfo(ServiceCtx context)
+ {
+ ResultCode resultCode = CheckNfcIsEnabled();
+
+ if (resultCode != ResultCode.Success)
+ {
+ return resultCode;
+ }
+
+ if (context.Request.RecvListBuff.Count == 0)
+ {
+ return ResultCode.WrongArgument;
+ }
+
+ ulong outputPosition = context.Request.RecvListBuff[0].Position;
+
+ context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize((uint)Marshal.SizeOf<ModelInfo>());
+
+ MemoryHelper.FillWithZeros(context.Memory, outputPosition, Marshal.SizeOf<ModelInfo>());
+
+ uint deviceHandle = (uint)context.RequestData.ReadUInt64();
+
+ if (context.Device.System.NfpDevices.Count == 0)
+ {
+ return ResultCode.DeviceNotFound;
+ }
+
+ for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
+ {
+ if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle)
+ {
+ if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved)
+ {
+ resultCode = ResultCode.TagNotFound;
+ }
+ else
+ {
+ if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted)
+ {
+ ModelInfo modelInfo = new ModelInfo
+ {
+ Reserved = new Array57<byte>()
+ };
+
+ modelInfo.CharacterId = BinaryPrimitives.ReverseEndianness(ushort.Parse(context.Device.System.NfpDevices[i].AmiiboId.AsSpan(0, 4), NumberStyles.HexNumber));
+ modelInfo.CharacterVariant = byte.Parse(context.Device.System.NfpDevices[i].AmiiboId.AsSpan(4, 2), NumberStyles.HexNumber);
+ modelInfo.Series = byte.Parse(context.Device.System.NfpDevices[i].AmiiboId.AsSpan(12, 2), NumberStyles.HexNumber);
+ modelInfo.ModelNumber = ushort.Parse(context.Device.System.NfpDevices[i].AmiiboId.AsSpan(8, 4), NumberStyles.HexNumber);
+ modelInfo.Type = byte.Parse(context.Device.System.NfpDevices[i].AmiiboId.AsSpan(6, 2), NumberStyles.HexNumber);
+
+ context.Memory.Write(outputPosition, modelInfo);
+
+ resultCode = ResultCode.Success;
+ }
+ else
+ {
+ resultCode = ResultCode.WrongDeviceState;
+ }
+ }
+
+ break;
+ }
+ }
+
+ return resultCode;
+ }
+
+ [CommandCmif(17)]
+ // AttachActivateEvent(bytes<8, 4>) -> handle<copy>
+ public ResultCode AttachActivateEvent(ServiceCtx context)
+ {
+ uint deviceHandle = context.RequestData.ReadUInt32();
+
+ for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
+ {
+ if ((uint)context.Device.System.NfpDevices[i].Handle == deviceHandle)
+ {
+ context.Device.System.NfpDevices[i].ActivateEvent = new KEvent(context.Device.System.KernelContext);
+
+ if (context.Process.HandleTable.GenerateHandle(context.Device.System.NfpDevices[i].ActivateEvent.ReadableEvent, out int activateEventHandle) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(activateEventHandle);
+
+ return ResultCode.Success;
+ }
+ }
+
+ return ResultCode.DeviceNotFound;
+ }
+
+ [CommandCmif(18)]
+ // AttachDeactivateEvent(bytes<8, 4>) -> handle<copy>
+ public ResultCode AttachDeactivateEvent(ServiceCtx context)
+ {
+ uint deviceHandle = context.RequestData.ReadUInt32();
+
+ for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
+ {
+ if ((uint)context.Device.System.NfpDevices[i].Handle == deviceHandle)
+ {
+ context.Device.System.NfpDevices[i].DeactivateEvent = new KEvent(context.Device.System.KernelContext);
+
+ if (context.Process.HandleTable.GenerateHandle(context.Device.System.NfpDevices[i].DeactivateEvent.ReadableEvent, out int deactivateEventHandle) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(deactivateEventHandle);
+
+ return ResultCode.Success;
+ }
+ }
+
+ return ResultCode.DeviceNotFound;
+ }
+
+ [CommandCmif(19)]
+ // GetState() -> u32
+ public ResultCode GetState(ServiceCtx context)
+ {
+ context.ResponseData.Write((int)_state);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(20)]
+ // GetDeviceState(bytes<8, 4>) -> u32
+ public ResultCode GetDeviceState(ServiceCtx context)
+ {
+ uint deviceHandle = context.RequestData.ReadUInt32();
+
+ for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
+ {
+ if ((uint)context.Device.System.NfpDevices[i].Handle == deviceHandle)
+ {
+ if (context.Device.System.NfpDevices[i].State > NfpDeviceState.Finalized)
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+
+ context.ResponseData.Write((uint)context.Device.System.NfpDevices[i].State);
+
+ return ResultCode.Success;
+ }
+ }
+
+ context.ResponseData.Write((uint)NfpDeviceState.Unavailable);
+
+ return ResultCode.DeviceNotFound;
+ }
+
+ [CommandCmif(21)]
+ // GetNpadId(bytes<8, 4>) -> u32
+ public ResultCode GetNpadId(ServiceCtx context)
+ {
+ uint deviceHandle = context.RequestData.ReadUInt32();
+
+ for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
+ {
+ if ((uint)context.Device.System.NfpDevices[i].Handle == deviceHandle)
+ {
+ context.ResponseData.Write((uint)HidUtils.GetNpadIdTypeFromIndex(context.Device.System.NfpDevices[i].Handle));
+
+ return ResultCode.Success;
+ }
+ }
+
+ return ResultCode.DeviceNotFound;
+ }
+
+ [CommandCmif(22)]
+ // GetApplicationAreaSize() -> u32
+ public ResultCode GetApplicationAreaSize(ServiceCtx context)
+ {
+ context.ResponseData.Write(AmiiboConstants.ApplicationAreaSize);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(23)] // 3.0.0+
+ // AttachAvailabilityChangeEvent() -> handle<copy>
+ public ResultCode AttachAvailabilityChangeEvent(ServiceCtx context)
+ {
+ _availabilityChangeEvent = new KEvent(context.Device.System.KernelContext);
+
+ if (context.Process.HandleTable.GenerateHandle(_availabilityChangeEvent.ReadableEvent, out int availabilityChangeEventHandle) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(availabilityChangeEventHandle);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(24)] // 3.0.0+
+ // RecreateApplicationArea(bytes<8, 4>, u32, buffer<unknown, 5>)
+ public ResultCode RecreateApplicationArea(ServiceCtx context)
+ {
+ throw new ServiceNotImplementedException(this, context);
+ }
+
+ [CommandCmif(102)]
+ // GetRegisterInfo2(bytes<8, 4>) -> buffer<unknown<0x100>, 0x1a>
+ public ResultCode GetRegisterInfo2(ServiceCtx context)
+ {
+ // TODO: Find the differencies between IUser and ISystem/IDebug.
+
+ if (_permissionLevel == NfpPermissionLevel.Debug || _permissionLevel == NfpPermissionLevel.System)
+ {
+ return GetRegisterInfo(context);
+ }
+
+ return ResultCode.DeviceNotFound;
+ }
+
+ private ResultCode CheckNfcIsEnabled()
+ {
+ // TODO: Call nn::settings::detail::GetNfcEnableFlag when it will be implemented.
+ return true ? ResultCode.Success : ResultCode.NfcDisabled;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/AmiiboConstants.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/AmiiboConstants.cs
new file mode 100644
index 00000000..b06492e6
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/AmiiboConstants.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager
+{
+ static class AmiiboConstants
+ {
+ public const int UuidMaxLength = 10;
+ public const int ApplicationAreaSize = 0xD8;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/CommonInfo.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/CommonInfo.cs
new file mode 100644
index 00000000..a7976de9
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/CommonInfo.cs
@@ -0,0 +1,17 @@
+using Ryujinx.Common.Memory;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x40)]
+ struct CommonInfo
+ {
+ public ushort LastWriteYear;
+ public byte LastWriteMonth;
+ public byte LastWriteDay;
+ public ushort WriteCounter;
+ public ushort Version;
+ public uint ApplicationAreaSize;
+ public Array52<byte> Reserved;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/DeviceType.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/DeviceType.cs
new file mode 100644
index 00000000..096522a0
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/DeviceType.cs
@@ -0,0 +1,7 @@
+namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager
+{
+ enum DeviceType : uint
+ {
+ Amiibo
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/ModelInfo.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/ModelInfo.cs
new file mode 100644
index 00000000..c66636ae
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/ModelInfo.cs
@@ -0,0 +1,16 @@
+using Ryujinx.Common.Memory;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x40)]
+ struct ModelInfo
+ {
+ public ushort CharacterId;
+ public byte CharacterVariant;
+ public byte Series;
+ public ushort ModelNumber;
+ public byte Type;
+ public Array57<byte> Reserved;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/MountTarget.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/MountTarget.cs
new file mode 100644
index 00000000..4a145773
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/MountTarget.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager
+{
+ enum MountTarget : uint
+ {
+ Rom = 1,
+ Ram = 2,
+ All = 3
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpDevice.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpDevice.cs
new file mode 100644
index 00000000..f56d33a9
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpDevice.cs
@@ -0,0 +1,23 @@
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.HLE.HOS.Services.Hid;
+
+namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager
+{
+ class NfpDevice
+ {
+ public KEvent ActivateEvent;
+ public KEvent DeactivateEvent;
+
+ public void SignalActivate() => ActivateEvent.ReadableEvent.Signal();
+ public void SignalDeactivate() => DeactivateEvent.ReadableEvent.Signal();
+
+ public NfpDeviceState State = NfpDeviceState.Unavailable;
+
+ public PlayerIndex Handle;
+ public NpadIdType NpadIdType;
+
+ public string AmiiboId;
+
+ public bool UseRandomUuid;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpDeviceState.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpDeviceState.cs
new file mode 100644
index 00000000..51e1d060
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpDeviceState.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager
+{
+ enum NfpDeviceState
+ {
+ Initialized = 0,
+ SearchingForTag = 1,
+ TagFound = 2,
+ TagRemoved = 3,
+ TagMounted = 4,
+ Unavailable = 5,
+ Finalized = 6
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpPermissionLevel.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpPermissionLevel.cs
new file mode 100644
index 00000000..8b84dcfe
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/NfpPermissionLevel.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager
+{
+ enum NfpPermissionLevel
+ {
+ Debug,
+ User,
+ System
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/RegisterInfo.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/RegisterInfo.cs
new file mode 100644
index 00000000..6b30eb8e
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/RegisterInfo.cs
@@ -0,0 +1,19 @@
+using Ryujinx.Common.Memory;
+using Ryujinx.HLE.HOS.Services.Mii.Types;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x100)]
+ struct RegisterInfo
+ {
+ public CharInfo MiiCharInfo;
+ public ushort FirstWriteYear;
+ public byte FirstWriteMonth;
+ public byte FirstWriteDay;
+ public Array41<byte> Nickname;
+ public byte FontRegion;
+ public Array64<byte> Reserved1;
+ public Array58<byte> Reserved2;
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/State.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/State.cs
new file mode 100644
index 00000000..b38cf9e2
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/State.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager
+{
+ enum State
+ {
+ NonInitialized = 0,
+ Initialized = 1
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/TagInfo.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/TagInfo.cs
new file mode 100644
index 00000000..d2076b2a
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/TagInfo.cs
@@ -0,0 +1,16 @@
+using Ryujinx.Common.Memory;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x58)]
+ struct TagInfo
+ {
+ public Array10<byte> Uuid;
+ public byte UuidLength;
+ public Array21<byte> Reserved1;
+ public uint Protocol;
+ public uint TagType;
+ public Array6<byte> Reserved2;
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/VirtualAmiiboFile.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/VirtualAmiiboFile.cs
new file mode 100644
index 00000000..be1877e5
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/Types/VirtualAmiiboFile.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager
+{
+ struct VirtualAmiiboFile
+ {
+ public uint FileVersion { get; set; }
+ public byte[] TagUuid { get; set; }
+ public string AmiiboId { get; set; }
+ public DateTime FirstWriteDate { get; set; }
+ public DateTime LastWriteDate { get; set; }
+ public ushort WriteCounter { get; set; }
+ public List<VirtualAmiiboApplicationArea> ApplicationAreas { get; set; }
+ }
+
+ struct VirtualAmiiboApplicationArea
+ {
+ public uint ApplicationAreaId { get; set; }
+ public byte[] ApplicationArea { get; set; }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ResultCode.cs
new file mode 100644
index 00000000..e0ccbc6d
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ResultCode.cs
@@ -0,0 +1,18 @@
+namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
+{
+ public enum ResultCode
+ {
+ ModuleId = 115,
+ ErrorCodeShift = 9,
+
+ Success = 0,
+
+ DeviceNotFound = (64 << ErrorCodeShift) | ModuleId,
+ WrongArgument = (65 << ErrorCodeShift) | ModuleId,
+ WrongDeviceState = (73 << ErrorCodeShift) | ModuleId,
+ NfcDisabled = (80 << ErrorCodeShift) | ModuleId,
+ TagNotFound = (97 << ErrorCodeShift) | ModuleId,
+ ApplicationAreaIsNull = (128 << ErrorCodeShift) | ModuleId,
+ ApplicationAreaAlreadyCreated = (168 << ErrorCodeShift) | ModuleId
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs
new file mode 100644
index 00000000..9166e87f
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs
@@ -0,0 +1,204 @@
+using Ryujinx.Common.Configuration;
+using Ryujinx.Common.Memory;
+using Ryujinx.Common.Utilities;
+using Ryujinx.Cpu;
+using Ryujinx.HLE.HOS.Services.Mii;
+using Ryujinx.HLE.HOS.Services.Mii.Types;
+using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
+{
+ static class VirtualAmiibo
+ {
+ private static uint _openedApplicationAreaId;
+
+ private static readonly AmiiboJsonSerializerContext SerializerContext = AmiiboJsonSerializerContext.Default;
+
+ public static byte[] GenerateUuid(string amiiboId, bool useRandomUuid)
+ {
+ if (useRandomUuid)
+ {
+ return GenerateRandomUuid();
+ }
+
+ VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId);
+
+ if (virtualAmiiboFile.TagUuid.Length == 0)
+ {
+ virtualAmiiboFile.TagUuid = GenerateRandomUuid();
+
+ SaveAmiiboFile(virtualAmiiboFile);
+ }
+
+ return virtualAmiiboFile.TagUuid;
+ }
+
+ private static byte[] GenerateRandomUuid()
+ {
+ byte[] uuid = new byte[9];
+
+ Random.Shared.NextBytes(uuid);
+
+ uuid[3] = (byte)(0x88 ^ uuid[0] ^ uuid[1] ^ uuid[2]);
+ uuid[8] = (byte)(uuid[3] ^ uuid[4] ^ uuid[5] ^ uuid[6]);
+
+ return uuid;
+ }
+
+ public static CommonInfo GetCommonInfo(string amiiboId)
+ {
+ VirtualAmiiboFile amiiboFile = LoadAmiiboFile(amiiboId);
+
+ return new CommonInfo()
+ {
+ LastWriteYear = (ushort)amiiboFile.LastWriteDate.Year,
+ LastWriteMonth = (byte)amiiboFile.LastWriteDate.Month,
+ LastWriteDay = (byte)amiiboFile.LastWriteDate.Day,
+ WriteCounter = amiiboFile.WriteCounter,
+ Version = 1,
+ ApplicationAreaSize = AmiiboConstants.ApplicationAreaSize,
+ Reserved = new Array52<byte>()
+ };
+ }
+
+ public static RegisterInfo GetRegisterInfo(ITickSource tickSource, string amiiboId, string nickname)
+ {
+ VirtualAmiiboFile amiiboFile = LoadAmiiboFile(amiiboId);
+
+ UtilityImpl utilityImpl = new UtilityImpl(tickSource);
+ CharInfo charInfo = new CharInfo();
+
+ charInfo.SetFromStoreData(StoreData.BuildDefault(utilityImpl, 0));
+
+ charInfo.Nickname = Nickname.FromString(nickname);
+
+ RegisterInfo registerInfo = new RegisterInfo()
+ {
+ MiiCharInfo = charInfo,
+ FirstWriteYear = (ushort)amiiboFile.FirstWriteDate.Year,
+ FirstWriteMonth = (byte)amiiboFile.FirstWriteDate.Month,
+ FirstWriteDay = (byte)amiiboFile.FirstWriteDate.Day,
+ FontRegion = 0,
+ Reserved1 = new Array64<byte>(),
+ Reserved2 = new Array58<byte>()
+ };
+ "Ryujinx"u8.CopyTo(registerInfo.Nickname.AsSpan());
+
+ return registerInfo;
+ }
+
+ public static bool OpenApplicationArea(string amiiboId, uint applicationAreaId)
+ {
+ VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId);
+
+ if (virtualAmiiboFile.ApplicationAreas.Any(item => item.ApplicationAreaId == applicationAreaId))
+ {
+ _openedApplicationAreaId = applicationAreaId;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ public static byte[] GetApplicationArea(string amiiboId)
+ {
+ VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId);
+
+ foreach (VirtualAmiiboApplicationArea applicationArea in virtualAmiiboFile.ApplicationAreas)
+ {
+ if (applicationArea.ApplicationAreaId == _openedApplicationAreaId)
+ {
+ return applicationArea.ApplicationArea;
+ }
+ }
+
+ return Array.Empty<byte>();
+ }
+
+ public static bool CreateApplicationArea(string amiiboId, uint applicationAreaId, byte[] applicationAreaData)
+ {
+ VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId);
+
+ if (virtualAmiiboFile.ApplicationAreas.Any(item => item.ApplicationAreaId == applicationAreaId))
+ {
+ return false;
+ }
+
+ virtualAmiiboFile.ApplicationAreas.Add(new VirtualAmiiboApplicationArea()
+ {
+ ApplicationAreaId = applicationAreaId,
+ ApplicationArea = applicationAreaData
+ });
+
+ SaveAmiiboFile(virtualAmiiboFile);
+
+ return true;
+ }
+
+ public static void SetApplicationArea(string amiiboId, byte[] applicationAreaData)
+ {
+ VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId);
+
+ if (virtualAmiiboFile.ApplicationAreas.Any(item => item.ApplicationAreaId == _openedApplicationAreaId))
+ {
+ for (int i = 0; i < virtualAmiiboFile.ApplicationAreas.Count; i++)
+ {
+ if (virtualAmiiboFile.ApplicationAreas[i].ApplicationAreaId == _openedApplicationAreaId)
+ {
+ virtualAmiiboFile.ApplicationAreas[i] = new VirtualAmiiboApplicationArea()
+ {
+ ApplicationAreaId = _openedApplicationAreaId,
+ ApplicationArea = applicationAreaData
+ };
+
+ break;
+ }
+ }
+
+ SaveAmiiboFile(virtualAmiiboFile);
+ }
+ }
+
+ private static VirtualAmiiboFile LoadAmiiboFile(string amiiboId)
+ {
+ Directory.CreateDirectory(Path.Join(AppDataManager.BaseDirPath, "system", "amiibo"));
+
+ string filePath = Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", $"{amiiboId}.json");
+
+ VirtualAmiiboFile virtualAmiiboFile;
+
+ if (File.Exists(filePath))
+ {
+ virtualAmiiboFile = JsonHelper.DeserializeFromFile(filePath, SerializerContext.VirtualAmiiboFile);
+ }
+ else
+ {
+ virtualAmiiboFile = new VirtualAmiiboFile()
+ {
+ FileVersion = 0,
+ TagUuid = Array.Empty<byte>(),
+ AmiiboId = amiiboId,
+ FirstWriteDate = DateTime.Now,
+ LastWriteDate = DateTime.Now,
+ WriteCounter = 0,
+ ApplicationAreas = new List<VirtualAmiiboApplicationArea>()
+ };
+
+ SaveAmiiboFile(virtualAmiiboFile);
+ }
+
+ return virtualAmiiboFile;
+ }
+
+ private static void SaveAmiiboFile(VirtualAmiiboFile virtualAmiiboFile)
+ {
+ string filePath = Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", $"{virtualAmiiboFile.AmiiboId}.json");
+ JsonHelper.SerializeToFile(filePath, virtualAmiiboFile, SerializerContext.VirtualAmiiboFile);
+ }
+ }
+}