diff options
author | Ac_K <Acoustik666@gmail.com> | 2021-03-18 21:40:20 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-03-18 21:40:20 +0100 |
commit | a56423802cd6e74809c1cf5d93b51fdf11f07bef (patch) | |
tree | 124cd814b327ea1eb446f6c867a34491ffe16b0d /Ryujinx.HLE | |
parent | 2b92c10105c8c7fcae3ab39d473f640c846a24ce (diff) |
nfp: Amiibo scanning support (#2006)
* Initial Impl.
* You just want me cause I'm next
* Fix some logics
* Fix close button
Diffstat (limited to 'Ryujinx.HLE')
16 files changed, 1108 insertions, 109 deletions
diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index 16b4c376..4da147bf 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -22,6 +22,7 @@ using Ryujinx.HLE.HOS.Services.Apm; using Ryujinx.HLE.HOS.Services.Arp; using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer; using Ryujinx.HLE.HOS.Services.Mii; +using Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager; using Ryujinx.HLE.HOS.Services.Nv; using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl; using Ryujinx.HLE.HOS.Services.Pcv.Bpc; @@ -33,6 +34,7 @@ using Ryujinx.HLE.HOS.SystemState; using Ryujinx.HLE.Loaders.Executables; using Ryujinx.HLE.Utilities; using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; @@ -65,6 +67,8 @@ namespace Ryujinx.HLE.HOS internal AppletStateMgr AppletState { get; private set; } + internal List<NfpDevice> NfpDevices { get; private set; } + internal ServerBase BsdServer { get; private set; } internal ServerBase AudRenServer { get; private set; } internal ServerBase AudOutServer { get; private set; } @@ -113,6 +117,8 @@ namespace Ryujinx.HLE.HOS PerformanceState = new PerformanceState(); + NfpDevices = new List<NfpDevice>(); + // Note: This is not really correct, but with HLE of services, the only memory // region used that is used is Application, so we can use the other ones for anything. KMemoryRegionManager region = KernelContext.MemoryRegions[(int)MemoryRegion.NvServices]; @@ -320,6 +326,33 @@ namespace Ryujinx.HLE.HOS AppletState.MessageEvent.ReadableEvent.Signal(); } + public void ScanAmiibo(int nfpDeviceId, string amiiboId, bool useRandomUuid) + { + if (NfpDevices[nfpDeviceId].State == NfpDeviceState.SearchingForTag) + { + NfpDevices[nfpDeviceId].State = NfpDeviceState.TagFound; + NfpDevices[nfpDeviceId].AmiiboId = amiiboId; + NfpDevices[nfpDeviceId].UseRandomUuid = useRandomUuid; + } + } + + public bool SearchingForAmiibo(out int nfpDeviceId) + { + nfpDeviceId = default; + + for (int i = 0; i < NfpDevices.Count; i++) + { + if (NfpDevices[i].State == NfpDeviceState.SearchingForTag) + { + nfpDeviceId = i; + + return true; + } + } + + return false; + } + public void SignalDisplayResolutionChange() { DisplayResolutionChangeEvent.ReadableEvent.Signal(); diff --git a/Ryujinx.HLE/HOS/Services/Mii/Types/CoreData.cs b/Ryujinx.HLE/HOS/Services/Mii/Types/CoreData.cs index 1b11f99d..39a3945b 100644 --- a/Ryujinx.HLE/HOS/Services/Mii/Types/CoreData.cs +++ b/Ryujinx.HLE/HOS/Services/Mii/Types/CoreData.cs @@ -389,7 +389,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types coreData.SetDefault(); - if (gender == Types.Gender.All) + if (gender == Gender.All) { gender = (Gender)utilImpl.GetRandom((int)gender); } @@ -432,7 +432,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types int axisY = 0; - if (gender == Types.Gender.Female && age == Age.Young) + if (gender == Gender.Female && age == Age.Young) { axisY = utilImpl.GetRandom(3); } @@ -466,8 +466,8 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types // Eye coreData.EyeType = (EyeType)eyeTypeInfo.Values[utilImpl.GetRandom(eyeTypeInfo.ValuesCount)]; - int eyeRotateKey1 = gender != Types.Gender.Male ? 4 : 2; - int eyeRotateKey2 = gender != Types.Gender.Male ? 3 : 4; + int eyeRotateKey1 = gender != Gender.Male ? 4 : 2; + int eyeRotateKey2 = gender != Gender.Male ? 3 : 4; byte eyeRotateOffset = (byte)(32 - EyeRotateTable[eyeRotateKey1] + eyeRotateKey2); byte eyeRotate = (byte)(32 - EyeRotateTable[(int)coreData.EyeType]); @@ -496,14 +496,14 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types coreData.EyebrowY = (byte)(axisY + eyebrowY); // Nose - int noseScale = gender == Types.Gender.Female ? 3 : 4; + int noseScale = gender == Gender.Female ? 3 : 4; coreData.NoseType = (NoseType)noseTypeInfo.Values[utilImpl.GetRandom(noseTypeInfo.ValuesCount)]; coreData.NoseScale = (byte)noseScale; coreData.NoseY = (byte)(axisY + 9); // Mouth - int mouthColor = gender == Types.Gender.Female ? utilImpl.GetRandom(0, 4) : 0; + int mouthColor = gender == Gender.Female ? utilImpl.GetRandom(0, 4) : 0; coreData.MouthType = (MouthType)mouthTypeInfo.Values[utilImpl.GetRandom(mouthTypeInfo.ValuesCount)]; coreData.MouthColor = (CommonColor)Helper.Ver3MouthColorTable[mouthColor]; @@ -515,7 +515,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types coreData.BeardColor = coreData.HairColor; coreData.MustacheScale = 4; - if (gender == Types.Gender.Male && age != Age.Young && utilImpl.GetRandom(10) < 2) + if (gender == Gender.Male && age != Age.Young && utilImpl.GetRandom(10) < 2) { BeardAndMustacheFlag mustacheAndBeardFlag = (BeardAndMustacheFlag)utilImpl.GetRandom(3); diff --git a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ResultCode.cs index b42a28a9..e0ccbc6d 100644 --- a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ResultCode.cs +++ b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ResultCode.cs @@ -7,7 +7,12 @@ Success = 0, - DeviceNotFound = (64 << ErrorCodeShift) | ModuleId, - DevicesBufferIsNull = (65 << ErrorCodeShift) | ModuleId + 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/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/IUser.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/IUser.cs index 2cd35b9e..90881565 100644 --- a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/IUser.cs +++ b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/IUser.cs @@ -1,4 +1,6 @@ -using Ryujinx.HLE.Exceptions; +using Ryujinx.Common.Memory; +using Ryujinx.Cpu; +using Ryujinx.HLE.Exceptions; using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Threading; @@ -6,18 +8,25 @@ using Ryujinx.HLE.HOS.Services.Hid; using Ryujinx.HLE.HOS.Services.Hid.HidServer; using Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager; using System; -using System.Collections.Generic; +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 IUser : IpcService { + private ulong _appletResourceUserId; + private ulong _mcuVersionData; + private byte[] _mcuData; + private State _state = State.NonInitialized; private KEvent _availabilityChangeEvent; - private int _availabilityChangeEventHandle = 0; - private List<Device> _devices = new List<Device>(); + private CancellationTokenSource _cancelTokenSource; public IUser() { } @@ -25,32 +34,30 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp // Initialize(u64, u64, pid, buffer<unknown, 5>) public ResultCode Initialize(ServiceCtx context) { - long appletResourceUserId = context.RequestData.ReadInt64(); - long mcuVersionData = context.RequestData.ReadInt64(); + _appletResourceUserId = context.RequestData.ReadUInt64(); + _mcuVersionData = context.RequestData.ReadUInt64(); long inputPosition = context.Request.SendBuff[0].Position; long inputSize = context.Request.SendBuff[0].Size; - byte[] unknownBuffer = new byte[inputSize]; - - context.Memory.Read((ulong)inputPosition, unknownBuffer); + _mcuData = new byte[inputSize]; - // NOTE: appletResourceUserId, mcuVersionData and the buffer are stored inside an internal struct. - // The buffer seems to contains entries with a size of 0x40 bytes each. - // Sadly, this internal struct doesn't seems to be used in retail. + context.Memory.Read((ulong)inputPosition, _mcuData); - // TODO: Add an instance of nn::nfc::server::Manager when it will be implemented. - // Add an instance of nn::nfc::server::SaveData when it will be implemented. + // TODO: The mcuData buffer seems to contains entries with a size of 0x40 bytes each. Usage of the data needs to be determined. - // TODO: When we will be able to add multiple controllers add one entry by controller here. - Device device1 = new Device + // 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 = DeviceState.Initialized + State = NfpDeviceState.Initialized }; - _devices.Add(device1); + 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; @@ -61,13 +68,18 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp // Finalize() public ResultCode Finalize(ServiceCtx context) { - // TODO: Call StopDetection() and Unmount() when they will be implemented. - // Remove the instance of nn::nfc::server::Manager when it will be implemented. - // Remove the instance of nn::nfc::server::SaveData when it will be implemented. + if (_state == State.Initialized) + { + if (_cancelTokenSource != null) + { + _cancelTokenSource.Cancel(); + } - _devices.Clear(); + // NOTE: All events are destroyed here. + context.Device.System.NfpDevices.Clear(); - _state = State.NonInitialized; + _state = State.NonInitialized; + } return ResultCode.Success; } @@ -78,23 +90,32 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp { if (context.Request.RecvListBuff.Count == 0) { - return ResultCode.DevicesBufferIsNull; + return ResultCode.WrongArgument; } long outputPosition = context.Request.RecvListBuff[0].Position; - long outputSize = context.Request.RecvListBuff[0].Size; + long outputSize = context.Request.RecvListBuff[0].Size; - if (_devices.Count == 0) + if (context.Device.System.NfpDevices.Count == 0) { return ResultCode.DeviceNotFound; } - for (int i = 0; i < _devices.Count; i++) + MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize); + + if (CheckNfcIsEnabled() == ResultCode.Success) { - context.Memory.Write((ulong)(outputPosition + (i * sizeof(long))), (uint)_devices[i].Handle); - } + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) + { + context.Memory.Write((ulong)(outputPosition + (i * sizeof(long))), (uint)context.Device.System.NfpDevices[i].Handle); + } - context.ResponseData.Write(_devices.Count); + context.ResponseData.Write(context.Device.System.NfpDevices.Count); + } + else + { + context.ResponseData.Write(0); + } return ResultCode.Success; } @@ -103,56 +124,376 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp // StartDetection(bytes<8, 4>) public ResultCode StartDetection(ServiceCtx context) { - throw new ServiceNotImplementedException(this, 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(50); // NOTE: Simulate amiibo scanning delay. + context.Device.System.NfpDevices[i].SignalDeactivate(); + + break; + } + } + } + }, _cancelTokenSource.Token); + + return ResultCode.Success; } [Command(4)] // StopDetection(bytes<8, 4>) public ResultCode StopDetection(ServiceCtx context) { - throw new ServiceNotImplementedException(this, 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; } [Command(5)] // Mount(bytes<8, 4>, u32, u32) public ResultCode Mount(ServiceCtx context) { - throw new ServiceNotImplementedException(this, context); + ResultCode resultCode = CheckNfcIsEnabled(); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + uint deviceHandle = (uint)context.RequestData.ReadUInt64(); + UserManager.DeviceType deviceType = (UserManager.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; } [Command(6)] // Unmount(bytes<8, 4>) public ResultCode Unmount(ServiceCtx context) { - throw new ServiceNotImplementedException(this, 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; } [Command(7)] // OpenApplicationArea(bytes<8, 4>, u32) public ResultCode OpenApplicationArea(ServiceCtx context) { - throw new ServiceNotImplementedException(this, 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; } [Command(8)] // GetApplicationArea(bytes<8, 4>) -> (u32, buffer<unknown, 6>) public ResultCode GetApplicationArea(ServiceCtx context) { - throw new ServiceNotImplementedException(this, 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; + } + + long outputPosition = context.Request.ReceiveBuff[0].Position; + long 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((ulong)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; } [Command(9)] // SetApplicationArea(bytes<8, 4>, buffer<unknown, 5>) public ResultCode SetApplicationArea(ServiceCtx context) { - throw new ServiceNotImplementedException(this, 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; + } + + long inputPosition = context.Request.SendBuff[0].Position; + long inputSize = context.Request.SendBuff[0].Size; + + byte[] applicationArea = new byte[inputSize]; + + context.Memory.Read((ulong)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; } [Command(10)] // Flush(bytes<8, 4>) public ResultCode Flush(ServiceCtx context) { - throw new ServiceNotImplementedException(this, 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; } [Command(11)] @@ -166,35 +507,328 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp // CreateApplicationArea(bytes<8, 4>, u32, buffer<unknown, 5>) public ResultCode CreateApplicationArea(ServiceCtx context) { - throw new ServiceNotImplementedException(this, 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(); + + long inputPosition = context.Request.SendBuff[0].Position; + long inputSize = context.Request.SendBuff[0].Size; + + byte[] applicationArea = new byte[inputSize]; + + context.Memory.Read((ulong)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; } [Command(13)] // GetTagInfo(bytes<8, 4>) -> buffer<unknown<0x58>, 0x1a> public ResultCode GetTagInfo(ServiceCtx context) { - throw new ServiceNotImplementedException(this, context); + ResultCode resultCode = CheckNfcIsEnabled(); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + if (context.Request.RecvListBuff.Count == 0) + { + return ResultCode.WrongArgument; + } + + long outputPosition = context.Request.RecvListBuff[0].Position; + + context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize(Marshal.SizeOf(typeof(TagInfo))); + + MemoryHelper.FillWithZeros(context.Memory, outputPosition, Marshal.SizeOf(typeof(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.ToSpan()); + + context.Memory.Write((ulong)outputPosition, tagInfo); + + resultCode = ResultCode.Success; + } + else + { + resultCode = ResultCode.WrongDeviceState; + } + } + + break; + } + } + + return resultCode; } [Command(14)] // GetRegisterInfo(bytes<8, 4>) -> buffer<unknown<0x100>, 0x1a> public ResultCode GetRegisterInfo(ServiceCtx context) { - throw new ServiceNotImplementedException(this, context); + ResultCode resultCode = CheckNfcIsEnabled(); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + if (context.Request.RecvListBuff.Count == 0) + { + return ResultCode.WrongArgument; + } + + long outputPosition = context.Request.RecvListBuff[0].Position; + + context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize(Marshal.SizeOf(typeof(RegisterInfo))); + + MemoryHelper.FillWithZeros(context.Memory, outputPosition, Marshal.SizeOf(typeof(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.NfpDevices[i].AmiiboId); + + context.Memory.Write((ulong)outputPosition, registerInfo); + + resultCode = ResultCode.Success; + } + else + { + resultCode = ResultCode.WrongDeviceState; + } + } + + break; + } + } + + return resultCode; } [Command(15)] // GetCommonInfo(bytes<8, 4>) -> buffer<unknown<0x40>, 0x1a> public ResultCode GetCommonInfo(ServiceCtx context) { - throw new ServiceNotImplementedException(this, context); + ResultCode resultCode = CheckNfcIsEnabled(); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + if (context.Request.RecvListBuff.Count == 0) + { + return ResultCode.WrongArgument; + } + + long outputPosition = context.Request.RecvListBuff[0].Position; + + context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize(Marshal.SizeOf(typeof(CommonInfo))); + + MemoryHelper.FillWithZeros(context.Memory, outputPosition, Marshal.SizeOf(typeof(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((ulong)outputPosition, commonInfo); + + resultCode = ResultCode.Success; + } + else + { + resultCode = ResultCode.WrongDeviceState; + } + } + + break; + } + } + + return resultCode; } [Command(16)] // GetModelInfo(bytes<8, 4>) -> buffer<unknown<0x40>, 0x1a> public ResultCode GetModelInfo(ServiceCtx context) { - throw new ServiceNotImplementedException(this, context); + ResultCode resultCode = CheckNfcIsEnabled(); + + if (resultCode != ResultCode.Success) + { + return resultCode; + } + + if (context.Request.RecvListBuff.Count == 0) + { + return ResultCode.WrongArgument; + } + + long outputPosition = context.Request.RecvListBuff[0].Position; + + context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize(Marshal.SizeOf(typeof(ModelInfo))); + + MemoryHelper.FillWithZeros(context.Memory, outputPosition, Marshal.SizeOf(typeof(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.Substring(0, 4), NumberStyles.HexNumber)); + modelInfo.CharacterVariant = byte.Parse(context.Device.System.NfpDevices[i].AmiiboId.Substring(4, 2), NumberStyles.HexNumber); + modelInfo.Series = byte.Parse(context.Device.System.NfpDevices[i].AmiiboId.Substring(12, 2), NumberStyles.HexNumber); + modelInfo.ModelNumber = ushort.Parse(context.Device.System.NfpDevices[i].AmiiboId.Substring(8, 4), NumberStyles.HexNumber); + modelInfo.Type = byte.Parse(context.Device.System.NfpDevices[i].AmiiboId.Substring(6, 2), NumberStyles.HexNumber); + + context.Memory.Write((ulong)outputPosition, modelInfo); + + resultCode = ResultCode.Success; + } + else + { + resultCode = ResultCode.WrongDeviceState; + } + } + + break; + } + } + + return resultCode; } [Command(17)] @@ -203,21 +837,18 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp { uint deviceHandle = context.RequestData.ReadUInt32(); - for (int i = 0; i < _devices.Count; i++) + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) { - if ((uint)_devices[i].Handle == deviceHandle) + if ((uint)context.Device.System.NfpDevices[i].Handle == deviceHandle) { - if (_devices[i].ActivateEventHandle == 0) - { - _devices[i].ActivateEvent = new KEvent(context.Device.System.KernelContext); + context.Device.System.NfpDevices[i].ActivateEvent = new KEvent(context.Device.System.KernelContext); - if (context.Process.HandleTable.GenerateHandle(_devices[i].ActivateEvent.ReadableEvent, out _devices[i].ActivateEventHandle) != KernelResult.Success) - { - throw new InvalidOperationException("Out of handles!"); - } + if (context.Process.HandleTable.GenerateHandle(context.Device.System.NfpDevices[i].ActivateEvent.ReadableEvent, out int activateEventHandle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); } - context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_devices[i].ActivateEventHandle); + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(activateEventHandle); return ResultCode.Success; } @@ -232,21 +863,18 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp { uint deviceHandle = context.RequestData.ReadUInt32(); - for (int i = 0; i < _devices.Count; i++) + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) { - if ((uint)_devices[i].Handle == deviceHandle) + if ((uint)context.Device.System.NfpDevices[i].Handle == deviceHandle) { - if (_devices[i].DeactivateEventHandle == 0) - { - _devices[i].DeactivateEvent = new KEvent(context.Device.System.KernelContext); + context.Device.System.NfpDevices[i].DeactivateEvent = new KEvent(context.Device.System.KernelContext); - if (context.Process.HandleTable.GenerateHandle(_devices[i].DeactivateEvent.ReadableEvent, out _devices[i].DeactivateEventHandle) != KernelResult.Success) - { - throw new InvalidOperationException("Out of handles!"); - } + if (context.Process.HandleTable.GenerateHandle(context.Device.System.NfpDevices[i].DeactivateEvent.ReadableEvent, out int deactivateEventHandle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); } - context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_devices[i].DeactivateEventHandle); + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(deactivateEventHandle); return ResultCode.Success; } @@ -270,17 +898,22 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp { uint deviceHandle = context.RequestData.ReadUInt32(); - for (int i = 0; i < _devices.Count; i++) + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) { - if ((uint)_devices[i].Handle == deviceHandle) + if ((uint)context.Device.System.NfpDevices[i].Handle == deviceHandle) { - context.ResponseData.Write((uint)_devices[i].State); + 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)DeviceState.Unavailable); + context.ResponseData.Write((uint)NfpDeviceState.Unavailable); return ResultCode.DeviceNotFound; } @@ -291,11 +924,11 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp { uint deviceHandle = context.RequestData.ReadUInt32(); - for (int i = 0; i < _devices.Count; i++) + for (int i = 0; i < context.Device.System.NfpDevices.Count; i++) { - if ((uint)_devices[i].Handle == deviceHandle) + if ((uint)context.Device.System.NfpDevices[i].Handle == deviceHandle) { - context.ResponseData.Write((uint)HidUtils.GetNpadIdTypeFromIndex(_devices[i].Handle)); + context.ResponseData.Write((uint)HidUtils.GetNpadIdTypeFromIndex(context.Device.System.NfpDevices[i].Handle)); return ResultCode.Success; } @@ -305,27 +938,26 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp } [Command(22)] - // GetApplicationAreaSize(bytes<8, 4>) -> u32 + // GetApplicationAreaSize() -> u32 public ResultCode GetApplicationAreaSize(ServiceCtx context) { - throw new ServiceNotImplementedException(this, context); + context.ResponseData.Write(AmiiboConstants.ApplicationAreaSize); + + return ResultCode.Success; } [Command(23)] // 3.0.0+ // AttachAvailabilityChangeEvent() -> handle<copy> public ResultCode AttachAvailabilityChangeEvent(ServiceCtx context) { - if (_availabilityChangeEventHandle == 0) - { - _availabilityChangeEvent = new KEvent(context.Device.System.KernelContext); + _availabilityChangeEvent = new KEvent(context.Device.System.KernelContext); - if (context.Process.HandleTable.GenerateHandle(_availabilityChangeEvent.ReadableEvent, out _availabilityChangeEventHandle) != KernelResult.Success) - { - throw new InvalidOperationException("Out of handles!"); - } + if (context.Process.HandleTable.GenerateHandle(_availabilityChangeEvent.ReadableEvent, out int availabilityChangeEventHandle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); } - context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_availabilityChangeEventHandle); + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(availabilityChangeEventHandle); return ResultCode.Success; } @@ -336,5 +968,11 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp { throw new ServiceNotImplementedException(this, context); } + + private ResultCode CheckNfcIsEnabled() + { + // TODO: Call nn::settings::detail::GetNfcEnableFlag when it will be implemented. + return true ? ResultCode.Success : ResultCode.NfcDisabled; + } } }
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/AmiiboConstants.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/AmiiboConstants.cs new file mode 100644 index 00000000..47f6f0fa --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/AmiiboConstants.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager +{ + static class AmiiboConstants + { + public const int UuidMaxLength = 10; + public const int ApplicationAreaSize = 0xD8; + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/CommonInfo.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/CommonInfo.cs new file mode 100644 index 00000000..da055dc3 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/CommonInfo.cs @@ -0,0 +1,17 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager +{ + [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/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/Device.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/Device.cs deleted file mode 100644 index 3ff3489b..00000000 --- a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/Device.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Ryujinx.HLE.HOS.Kernel.Threading; -using Ryujinx.HLE.HOS.Services.Hid; - -namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager -{ - class Device - { - public KEvent ActivateEvent; - public int ActivateEventHandle; - - public KEvent DeactivateEvent; - public int DeactivateEventHandle; - - public DeviceState State = DeviceState.Unavailable; - - public PlayerIndex Handle; - public NpadIdType NpadIdType; - } -}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/DeviceType.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/DeviceType.cs new file mode 100644 index 00000000..753b91a9 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/DeviceType.cs @@ -0,0 +1,7 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager +{ + enum DeviceType : uint + { + Amiibo + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/ModelInfo.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/ModelInfo.cs new file mode 100644 index 00000000..1b6a3d32 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/ModelInfo.cs @@ -0,0 +1,16 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager +{ + [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/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/MountTarget.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/MountTarget.cs new file mode 100644 index 00000000..11520bc6 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/MountTarget.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager +{ + enum MountTarget : uint + { + Rom = 1, + Ram = 2, + All = 3 + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/NfpDevice.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/NfpDevice.cs new file mode 100644 index 00000000..b0d9c806 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/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.UserManager +{ + 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/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/DeviceState.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/NfpDeviceState.cs index 7e373494..0e753250 100644 --- a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/DeviceState.cs +++ b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/NfpDeviceState.cs @@ -1,6 +1,6 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager { - enum DeviceState + enum NfpDeviceState { Initialized = 0, SearchingForTag = 1, diff --git a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/RegisterInfo.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/RegisterInfo.cs new file mode 100644 index 00000000..3c72a971 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/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.UserManager +{ + [StructLayout(LayoutKind.Sequential, Size = 0x100)] + struct RegisterInfo + { + public CharInfo MiiCharInfo; + public ushort FirstWriteYear; + public byte FirstWriteMonth; + public byte FirstWriteDay; + public Array11<byte> Nickname; + public byte FontRegion; + public Array64<byte> Reserved1; + public Array58<byte> Reserved2; + } +}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/TagInfo.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/TagInfo.cs new file mode 100644 index 00000000..950f8c10 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/TagInfo.cs @@ -0,0 +1,16 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager +{ + [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/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/VirtualAmiiboFile.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/VirtualAmiiboFile.cs new file mode 100644 index 00000000..5265c038 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/VirtualAmiiboFile.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager +{ + 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/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs new file mode 100644 index 00000000..bd810d96 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs @@ -0,0 +1,205 @@ +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS.Services.Mii; +using Ryujinx.HLE.HOS.Services.Mii.Types; +using Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.Json; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp +{ + static class VirtualAmiibo + { + private static uint _openedApplicationAreaId; + + 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]; + + new Random().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(string amiiboId) + { + VirtualAmiiboFile amiiboFile = LoadAmiiboFile(amiiboId); + + UtilityImpl utilityImpl = new UtilityImpl(); + CharInfo charInfo = new CharInfo(); + + charInfo.SetFromStoreData(StoreData.BuildDefault(utilityImpl, 0)); + + // TODO: Maybe change the "no name" by the player name when user profile will be implemented. + // 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>() + }; + + Encoding.ASCII.GetBytes("Ryujinx").CopyTo(registerInfo.Nickname.ToSpan()); + + 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 = JsonSerializer.Deserialize<VirtualAmiiboFile>(File.ReadAllText(filePath)); + } + 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"); + + File.WriteAllText(filePath, JsonSerializer.Serialize(virtualAmiiboFile)); + } + } +}
\ No newline at end of file |