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