diff --git a/assets/Locales/Root.json b/assets/Locales/Root.json index 9ecf9826c..3d23d4286 100644 --- a/assets/Locales/Root.json +++ b/assets/Locales/Root.json @@ -700,6 +700,56 @@ "zh_TW": "掃描 Amiibo" } }, + { + "ID": "MenuBarActionsScanSkylander", + "Translations": { + "ar_SA": "‫فحص Skylander", + "de_DE": "Skylander scannen", + "el_GR": "Σάρωση Skylander", + "en_US": "Scan A Skylander", + "es_ES": "Escanear Skylander", + "fr_FR": "Scanner un Skylander", + "he_IL": "סרוק אמיבו", + "it_IT": "Scansiona un Skylander", + "ja_JP": "Skylander をスキャン", + "ko_KR": "Skylander 스캔", + "no_NO": "Skann en Skylander", + "pl_PL": "Skanuj Skylander", + "pt_BR": "Escanear um Skylander", + "ru_RU": "Сканировать Skylander", + "sv_SE": "Skanna en Skylander", + "th_TH": "สแกนหา Skylander", + "tr_TR": "Bir Skylander Tara", + "uk_UA": "Сканувати Skylander", + "zh_CN": "扫描 Skylander", + "zh_TW": "掃描 Skylander" + } + }, + { + "ID": "MenuBarActionsRemoveSkylander", + "Translations": { + "ar_SA": "إزالة Skylander", + "de_DE": "Skylander entfernen", + "el_GR": "Αφαίρεση Skylander", + "en_US": "Remove Skylander", + "es_ES": "Eliminar Skylander", + "fr_FR": "Supprimer un Skylander", + "he_IL": "הסר Skylander", + "it_IT": "Rimuovi Skylander", + "ja_JP": "Skylander を削除", + "ko_KR": "Skylander 제거", + "no_NO": "Fjern Skylander", + "pl_PL": "Usuń Skylander", + "pt_BR": "Remover um Skylander", + "ru_RU": "Удалить Skylander", + "sv_SE": "Ta bort Skylander", + "th_TH": "ลบ Skylander", + "tr_TR": "Skylander'ı Kaldır", + "uk_UA": "Видалити Skylander", + "zh_CN": "移除 Skylander", + "zh_TW": "移除 Skylander" + } + }, { "ID": "MenuBarActionsScanAmiiboBin", "Translations": { diff --git a/src/Ryujinx.HLE/HOS/Horizon.cs b/src/Ryujinx.HLE/HOS/Horizon.cs index 83aaa1f4d..38d504bbf 100644 --- a/src/Ryujinx.HLE/HOS/Horizon.cs +++ b/src/Ryujinx.HLE/HOS/Horizon.cs @@ -20,6 +20,7 @@ using Ryujinx.HLE.HOS.Services.Mii; using Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption; using Ryujinx.HLE.HOS.Services.Nfc.Nfp; using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager; +using Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager; using Ryujinx.HLE.HOS.Services.Nv; using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl; using Ryujinx.HLE.HOS.Services.Pcv.Bpc; @@ -66,6 +67,8 @@ namespace Ryujinx.HLE.HOS internal List NfpDevices { get; private set; } + internal List NfcDevices { get; private set; } + internal SmRegistry SmRegistry { get; private set; } internal ServerBase SmServer { get; private set; } @@ -132,6 +135,7 @@ namespace Ryujinx.HLE.HOS PerformanceState = new PerformanceState(); NfpDevices = []; + NfcDevices = []; // 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. @@ -372,6 +376,15 @@ namespace Ryujinx.HLE.HOS } } + public void ScanSkylander(int nfcDeviceId, byte[] data) + { + if (NfcDevices[nfcDeviceId].State == NfcDeviceState.SearchingForTag) + { + NfcDevices[nfcDeviceId].State = NfcDeviceState.TagFound; + NfcDevices[nfcDeviceId].Data = data; + } + } + public bool SearchingForAmiibo(out int nfpDeviceId) { nfpDeviceId = default; @@ -389,6 +402,53 @@ namespace Ryujinx.HLE.HOS return false; } + public bool SearchingForSkylander(out int nfcDeviceId) + { + nfcDeviceId = default; + + for (int i = 0; i < NfcDevices.Count; i++) + { + if (NfcDevices[i].State == NfcDeviceState.SearchingForTag) + { + nfcDeviceId = i; + + return true; + } + } + + return false; + } + + public bool HasSkylander(out int nfcDeviceId) + { + nfcDeviceId = default; + + for (int i = 0; i < NfcDevices.Count; i++) + { + if (NfcDevices[i].State == NfcDeviceState.TagFound) + { + nfcDeviceId = i; + + return true; + } + } + + return false; + } + + public void RemoveSkylander() + { + for (int i = 0; i < NfcDevices.Count; i++) + { + if (NfcDevices[i].State == NfcDeviceState.TagFound) + { + NfcDevices[i].State = NfcDeviceState.Initialized; + NfcDevices[i].SignalDeactivate(); + Thread.Sleep(100); // NOTE: Simulate skylander scanning delay. + } + } + } + public void SignalDisplayResolutionChange() { DisplayResolutionChangeEvent.ReadableEvent.Signal(); diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/IUserManager.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/IUserManager.cs index 295c7e71a..53b6549c5 100644 --- a/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/IUserManager.cs +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/IUserManager.cs @@ -1,8 +1,19 @@ +using Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager; + namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare { [Service("nfc:mf:u")] class IUserManager : IpcService { public IUserManager(ServiceCtx context) { } + + [CommandCmif(0)] + // CreateUserInterface() -> object + public ResultCode CreateUserInterface(ServiceCtx context) + { + MakeObject(context, new IMifare()); + + return ResultCode.Success; + } } } diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/IMifare.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/IMifare.cs new file mode 100644 index 000000000..43e28b5cf --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/IMifare.cs @@ -0,0 +1,477 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Cpu; +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.Horizon.Common; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager +{ + class IMifare : IpcService + { + private State _state; + + private KEvent _availabilityChangeEvent; + + private CancellationTokenSource _cancelTokenSource; + + public IMifare() + { + _state = State.NonInitialized; + } + + [CommandCmif(0)] + public ResultCode Initialize(ServiceCtx context) + { + _state = State.Initialized; + + NfcDevice devicePlayer1 = new() + { + NpadIdType = NpadIdType.Player1, + Handle = HidUtils.GetIndexFromNpadIdType(NpadIdType.Player1), + State = NfcDeviceState.Initialized, + }; + + context.Device.System.NfcDevices.Add(devicePlayer1); + + return ResultCode.Success; + } + + [CommandCmif(1)] + public ResultCode Finalize(ServiceCtx context) + { + if (_state == State.Initialized) + { + _cancelTokenSource?.Cancel(); + + // NOTE: All events are destroyed here. + context.Device.System.NfcDevices.Clear(); + + _state = State.NonInitialized; + } + + return ResultCode.Success; + } + + [CommandCmif(2)] + public ResultCode GetListDevices(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.NfcDevices.Count == 0) + { + return ResultCode.DeviceNotFound; + } + + MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize); + + for (int i = 0; i < context.Device.System.NfcDevices.Count; i++) + { + context.Memory.Write(outputPosition + ((uint)i * sizeof(long)), (uint)context.Device.System.NfcDevices[i].Handle); + } + + context.ResponseData.Write(context.Device.System.NfcDevices.Count); + + return ResultCode.Success; + } + + [CommandCmif(3)] + public ResultCode StartDetection(ServiceCtx context) + { + uint deviceHandle = (uint)context.RequestData.ReadUInt64(); + + for (int i = 0; i < context.Device.System.NfcDevices.Count; i++) + { + if (context.Device.System.NfcDevices[i].Handle == (PlayerIndex)deviceHandle) + { + context.Device.System.NfcDevices[i].State = NfcDeviceState.SearchingForTag; + + break; + } + } + + _cancelTokenSource = new CancellationTokenSource(); + + Task.Run(() => + { + while (true) + { + if (_cancelTokenSource.Token.IsCancellationRequested) + { + break; + } + + for (int i = 0; i < context.Device.System.NfcDevices.Count; i++) + { + if (context.Device.System.NfcDevices[i].State == NfcDeviceState.TagFound) + { + context.Device.System.NfcDevices[i].SignalActivate(); + Thread.Sleep(125); // NOTE: Simulate skylander scanning delay. + + break; + } + } + } + }, _cancelTokenSource.Token); + + return ResultCode.Success; + } + + [CommandCmif(4)] + public ResultCode StopDetection(ServiceCtx context) + { + _cancelTokenSource?.Cancel(); + + uint deviceHandle = (uint)context.RequestData.ReadUInt64(); + + for (int i = 0; i < context.Device.System.NfcDevices.Count; i++) + { + if (context.Device.System.NfcDevices[i].Handle == (PlayerIndex)deviceHandle) + { + context.Device.System.NfcDevices[i].State = NfcDeviceState.Initialized; + Array.Clear(context.Device.System.NfcDevices[i].Data); + context.Device.System.NfcDevices[i].SignalDeactivate(); + + break; + } + } + + return ResultCode.Success; + } + + [CommandCmif(5)] + public ResultCode ReadMifare(ServiceCtx context) + { + if (context.Request.ReceiveBuff.Count == 0 || context.Request.SendBuff.Count == 0) + { + return ResultCode.WrongArgument; + } + + uint deviceHandle = (uint)context.RequestData.ReadUInt64(); + + if (context.Device.System.NfcDevices.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); + + ulong inputPosition = context.Request.SendBuff[0].Position; + ulong inputSize = context.Request.SendBuff[0].Size; + + byte[] readBlockParameter = new byte[inputSize]; + + context.Memory.Read(inputPosition, readBlockParameter); + + var span = MemoryMarshal.Cast(readBlockParameter); + var list = new List(span.Length); + + foreach (var item in span) + list.Add(item); + + Thread.Sleep(125 * list.Count); // NOTE: Simulate skylander scanning delay. + + for (int i = 0; i < context.Device.System.NfcDevices.Count; i++) + { + if (context.Device.System.NfcDevices[i].Handle == (PlayerIndex)deviceHandle) + { + if (context.Device.System.NfcDevices[i].State == NfcDeviceState.TagRemoved) + { + return ResultCode.TagNotFound; + } + else + { + for (int p = 0; p < list.Count; p++) + { + NfcMifareReadBlockData blockData = new() + { + SectorNumber = list[p].SectorNumber, + Reserved = new Array7(), + }; + byte[] data = new byte[16]; + + switch (list[p].SectorKey.MifareCommand) + { + case NfcMifareCommand.NfcMifareCommand_Read: + case NfcMifareCommand.NfcMifareCommand_AuthA: + if (IsCurrentBlockKeyBlock(list[p].SectorNumber)) + { + Array.Copy(context.Device.System.NfcDevices[i].Data, (16 * list[p].SectorNumber) + 6, data, 6, 4); + } + else + { + Array.Copy(context.Device.System.NfcDevices[i].Data, 16 * list[p].SectorNumber, data, 0, 16); + } + data.CopyTo(blockData.Data.AsSpan()); + context.Memory.Write(outputPosition + ((uint)(p * Unsafe.SizeOf())), blockData); + break; + } + } + } + } + } + + return ResultCode.Success; + } + + [CommandCmif(6)] + public ResultCode WriteMifare(ServiceCtx context) + { + if (context.Request.SendBuff.Count == 0) + { + return ResultCode.WrongArgument; + } + + uint deviceHandle = (uint)context.RequestData.ReadUInt64(); + + if (context.Device.System.NfcDevices.Count == 0) + { + return ResultCode.DeviceNotFound; + } + + ulong inputPosition = context.Request.SendBuff[0].Position; + ulong inputSize = context.Request.SendBuff[0].Size; + + byte[] writeBlockParameter = new byte[inputSize]; + + context.Memory.Read(inputPosition, writeBlockParameter); + + var span = MemoryMarshal.Cast(writeBlockParameter); + var list = new List(span.Length); + + foreach (var item in span) + list.Add(item); + + Thread.Sleep(125 * list.Count); // NOTE: Simulate skylander scanning delay. + + for (int i = 0; i < context.Device.System.NfcDevices.Count; i++) + { + if (context.Device.System.NfcDevices[i].Handle == (PlayerIndex)deviceHandle) + { + if (context.Device.System.NfcDevices[i].State == NfcDeviceState.TagRemoved) + { + return ResultCode.TagNotFound; + } + else + { + for (int p = 0; p < list.Count; p++) + { + switch (list[p].SectorKey.MifareCommand) + { + case NfcMifareCommand.NfcMifareCommand_Write: + case NfcMifareCommand.NfcMifareCommand_AuthA: + list[p].Data.AsSpan().CopyTo(context.Device.System.NfcDevices[i].Data.AsSpan(list[p].SectorNumber * 16, 16)); + break; + } + } + } + } + } + + return ResultCode.Success; + } + + [CommandCmif(7)] + public ResultCode GetTagInfo(ServiceCtx context) + { + ResultCode resultCode = ResultCode.Success; + + 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()); + + MemoryHelper.FillWithZeros(context.Memory, outputPosition, Marshal.SizeOf()); + + uint deviceHandle = (uint)context.RequestData.ReadUInt64(); + + if (context.Device.System.NfcDevices.Count == 0) + { + return ResultCode.DeviceNotFound; + } + + for (int i = 0; i < context.Device.System.NfcDevices.Count; i++) + { + if (context.Device.System.NfcDevices[i].Handle == (PlayerIndex)deviceHandle) + { + if (context.Device.System.NfcDevices[i].State == NfcDeviceState.TagRemoved) + { + resultCode = ResultCode.TagNotFound; + } + else + { + if (context.Device.System.NfcDevices[i].State == NfcDeviceState.TagMounted || context.Device.System.NfcDevices[i].State == NfcDeviceState.TagFound) + { + TagInfo tagInfo = new() + { + UuidLength = 4, + Reserved1 = new Array21(), + Protocol = (uint)NfcProtocol.NfcProtocol_TypeA, // Type A Protocol + TagType = (uint)NfcTagType.NfcTagType_Mifare, // Mifare Type + Reserved2 = new Array6(), + }; + + byte[] uuid = new byte[4]; + + Array.Copy(context.Device.System.NfcDevices[i].Data, 0, uuid, 0, 4); + + uuid.CopyTo(tagInfo.Uuid.AsSpan()); + + context.Memory.Write(outputPosition, tagInfo); + + resultCode = ResultCode.Success; + } + else + { + resultCode = ResultCode.WrongDeviceState; + } + } + + break; + } + } + + return resultCode; + } + + [CommandCmif(8)] + public ResultCode AttachActivateEvent(ServiceCtx context) + { + uint deviceHandle = (uint)context.RequestData.ReadUInt64(); + + for (int i = 0; i < context.Device.System.NfcDevices.Count; i++) + { + if ((uint)context.Device.System.NfcDevices[i].Handle == deviceHandle) + { + context.Device.System.NfcDevices[i].ActivateEvent = new KEvent(context.Device.System.KernelContext); + + if (context.Process.HandleTable.GenerateHandle(context.Device.System.NfcDevices[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(9)] + public ResultCode AttachDeactivateEvent(ServiceCtx context) + { + uint deviceHandle = (uint)context.RequestData.ReadUInt64(); + + for (int i = 0; i < context.Device.System.NfcDevices.Count; i++) + { + if ((uint)context.Device.System.NfcDevices[i].Handle == deviceHandle) + { + context.Device.System.NfcDevices[i].DeactivateEvent = new KEvent(context.Device.System.KernelContext); + + if (context.Process.HandleTable.GenerateHandle(context.Device.System.NfcDevices[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(10)] + public ResultCode GetState(ServiceCtx context) + { + context.ResponseData.Write((int)_state); + + return ResultCode.Success; + } + + [CommandCmif(11)] + public ResultCode GetDeviceState(ServiceCtx context) + { + uint deviceHandle = (uint)context.RequestData.ReadUInt64(); + + for (int i = 0; i < context.Device.System.NfcDevices.Count; i++) + { + if ((uint)context.Device.System.NfcDevices[i].Handle == deviceHandle) + { + if (context.Device.System.NfcDevices[i].State > NfcDeviceState.Finalized) + { + throw new InvalidOperationException($"{nameof(context.Device.System.NfcDevices)} contains an invalid state for device {i}: {context.Device.System.NfcDevices[i].State}"); + } + context.ResponseData.Write((uint)context.Device.System.NfcDevices[i].State); + + return ResultCode.Success; + } + } + + context.ResponseData.Write((uint)NfcDeviceState.Unavailable); + + return ResultCode.DeviceNotFound; + } + + [CommandCmif(12)] + public ResultCode GetNpadId(ServiceCtx context) + { + uint deviceHandle = (uint)context.RequestData.ReadUInt64(); + + for (int i = 0; i < context.Device.System.NfcDevices.Count; i++) + { + if ((uint)context.Device.System.NfcDevices[i].Handle == deviceHandle) + { + context.ResponseData.Write((uint)HidUtils.GetNpadIdTypeFromIndex(context.Device.System.NfcDevices[i].Handle)); + + return ResultCode.Success; + } + } + + return ResultCode.DeviceNotFound; + } + + [CommandCmif(13)] + 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; + } + + private bool IsCurrentBlockKeyBlock(byte block) + { + return ((block + 1) % 4) == 0; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/Types/NfcDevice.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/Types/NfcDevice.cs new file mode 100644 index 000000000..5d2b97fe9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/Types/NfcDevice.cs @@ -0,0 +1,21 @@ +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Hid; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager +{ + class NfcDevice + { + public KEvent ActivateEvent; + public KEvent DeactivateEvent; + + public void SignalActivate() => ActivateEvent.ReadableEvent.Signal(); + public void SignalDeactivate() => DeactivateEvent.ReadableEvent.Signal(); + + public NfcDeviceState State = NfcDeviceState.Unavailable; + + public PlayerIndex Handle; + public NpadIdType NpadIdType; + + public byte[] Data; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/Types/NfcMifareCommand.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/Types/NfcMifareCommand.cs new file mode 100644 index 000000000..0ab18c183 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/Types/NfcMifareCommand.cs @@ -0,0 +1,14 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager +{ + enum NfcMifareCommand : byte + { + NfcMifareCommand_Read = 0x30, + NfcMifareCommand_AuthA = 0x60, + NfcMifareCommand_AuthB = 0x61, + NfcMifareCommand_Write = 0xA0, + NfcMifareCommand_Transfer = 0xB0, + NfcMifareCommand_Decrement = 0xC0, + NfcMifareCommand_Increment = 0xC1, + NfcMifareCommand_Store = 0xC2, + } +} \ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/Types/NfcMifareReadBlockData.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/Types/NfcMifareReadBlockData.cs new file mode 100644 index 000000000..bc5f1dcf3 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/Types/NfcMifareReadBlockData.cs @@ -0,0 +1,13 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager +{ + [StructLayout(LayoutKind.Sequential, Size = 0x18)] + struct NfcMifareReadBlockData + { + public Array16 Data; + public byte SectorNumber; + public Array7 Reserved; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/Types/NfcMifareReadBlockParameter.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/Types/NfcMifareReadBlockParameter.cs new file mode 100644 index 000000000..df4ed6fce --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/Types/NfcMifareReadBlockParameter.cs @@ -0,0 +1,13 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager +{ + [StructLayout(LayoutKind.Sequential, Size = 0x18)] + struct NfcMifareReadBlockParameter + { + public byte SectorNumber; + public Array7 Reserved; + public NfcSectorKey SectorKey; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/Types/NfcMifareWriteBlockParameter.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/Types/NfcMifareWriteBlockParameter.cs new file mode 100644 index 000000000..fcdbfab2d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/Types/NfcMifareWriteBlockParameter.cs @@ -0,0 +1,14 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager +{ + [StructLayout(LayoutKind.Sequential, Size = 0x28)] + struct NfcMifareWriteBlockParameter + { + public Array16 Data; + public byte SectorNumber; + public Array7 Reserved; + public NfcSectorKey SectorKey; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/Types/NfcProtocol.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/Types/NfcProtocol.cs new file mode 100644 index 000000000..d486ddade --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/Types/NfcProtocol.cs @@ -0,0 +1,11 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager +{ + enum NfcProtocol : byte + { + NfcProtocol_None = 0b_0000_0000, + NfcProtocol_TypeA = 0b_0000_0001, ///< ISO14443A + NfcProtocol_TypeB = 0b_0000_0010, ///< ISO14443B + NfcProtocol_TypeF = 0b_0000_0100, ///< Sony FeliCa + NfcProtocol_All = 0xFF, + } +} \ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/Types/NfcSectorKey.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/Types/NfcSectorKey.cs new file mode 100644 index 000000000..bd7d51813 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/Types/NfcSectorKey.cs @@ -0,0 +1,16 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10)] + struct NfcSectorKey + { + public NfcMifareCommand MifareCommand; + public byte Unknown; + public Array6 Reserved1; + public Array6 SectorKey; + public Array2 Reserved2; + + } +} \ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/Types/NfcTagType.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/Types/NfcTagType.cs new file mode 100644 index 000000000..b21c4c9d0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/Types/NfcTagType.cs @@ -0,0 +1,15 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager +{ + enum NfcTagType : byte + { + NfcTagType_None = 0b_0000_0000, + NfcTagType_Type1 = 0b_0000_0001, ///< ISO14443A RW. Topaz + NfcTagType_Type2 = 0b_0000_0010, ///< ISO14443A RW. Ultralight, NTAGX, ST25TN + NfcTagType_Type3 = 0b_0000_0100, ///< ISO14443A RW/RO. Sony FeliCa + NfcTagType_Type4A = 0b_0000_1000, ///< ISO14443A RW/RO. DESFire + NfcTagType_Type4B = 0b_0001_0000, ///< ISO14443B RW/RO. DESFire + NfcTagType_Type5 = 0b_0010_0000, ///< ISO15693 RW/RO. SLI, SLIX, ST25TV + NfcTagType_Mifare = 0b_0100_0000, ///< Mifare clasic. Skylanders + NfcTagType_All = 0xFF, + } +} \ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/Types/NfpDeviceState.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/Types/NfpDeviceState.cs new file mode 100644 index 000000000..9e51b8d4d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/Types/NfpDeviceState.cs @@ -0,0 +1,13 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager +{ + enum NfcDeviceState : byte + { + Initialized = 0, + SearchingForTag = 1, + TagFound = 2, + TagRemoved = 3, + TagMounted = 4, + Unavailable = 5, + Finalized = 6, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/Types/State.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/Types/State.cs new file mode 100644 index 000000000..a9ee720e9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/Types/State.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager +{ + enum State + { + NonInitialized, + Initialized, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/Types/TagInfo.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/Types/TagInfo.cs new file mode 100644 index 000000000..5db4612c9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/Types/TagInfo.cs @@ -0,0 +1,16 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager +{ + [StructLayout(LayoutKind.Sequential, Size = 0x58)] + struct TagInfo + { + public Array10 Uuid; + public byte UuidLength; + public Array21 Reserved1; + public uint Protocol; + public uint TagType; + public Array6 Reserved2; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/ResultCode.cs new file mode 100644 index 000000000..3148e02e4 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/ResultCode.cs @@ -0,0 +1,17 @@ +namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare +{ + public enum ResultCode + { + ModuleId = 161, + ErrorCodeShift = 9, + + Success = 0, + + DeviceNotFound = (64 << ErrorCodeShift) | ModuleId, // 0x80A1 + WrongArgument = (65 << ErrorCodeShift) | ModuleId, // 0x82A1 + WrongDeviceState = (73 << ErrorCodeShift) | ModuleId, // 0x92A1 + NfcDisabled = (80 << ErrorCodeShift) | ModuleId, // 0xA0A1 + TagNotFound = (97 << ErrorCodeShift) | ModuleId, // 0xC2A1 + MifareAccessError = (288 << ErrorCodeShift) | ModuleId, // 0x240a1 + } +} diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs index 96159a1ea..48e18a12e 100644 --- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs @@ -370,6 +370,39 @@ namespace Ryujinx.Ava.UI.ViewModels public bool CanScanAmiiboBinaries => AmiiboBinReader.HasAmiiboKeyFile; + public bool IsSkylanderRequested + { + get => field && _isGameRunning; + set + { + field = value; + + OnPropertyChanged(); + } + } + + public bool HasSkylander + { + get => field && _isGameRunning; + set + { + field = value; + + OnPropertyChanged(); + } + } + + public bool ShowSkylanderActions + { + get => field && _isGameRunning; + set + { + field = value; + + OnPropertyChanged(); + } + } + public bool ShowLoadProgress { get; @@ -1864,6 +1897,46 @@ namespace Ryujinx.Ava.UI.ViewModels } } } + public async Task OpenSkylanderWindow() + { + if (AppHost.Device.System.SearchingForSkylander(out int deviceId)) + { + Optional result = await StorageProvider.OpenSingleFilePickerAsync( + new FilePickerOpenOptions + { + Title = LocaleManager.Instance[LocaleKeys.OpenFileDialogTitle], + FileTypeFilter = new List + { + new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats]) + { + Patterns = ["*.sky", "*.bin", "*.dmp", "*.dump"], + }, + }, + }); + if (result.HasValue) + { + // Open reading stream from the first file. + await using var stream = await result.Value.OpenReadAsync(); + using var streamReader = new BinaryReader(stream); + // Reads all the content of file as a text. + byte[] data = new byte[1024]; + var count = streamReader.Read(data, 0, 1024); + if (count < 1024) + { + return; + } + else + { + AppHost.Device.System.ScanSkylander(deviceId, data); + } + } + } + } + + public async Task RemoveSkylander() + { + AppHost.Device.System.RemoveSkylander(); + } public void ReloadRenderDocApi() { diff --git a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml index 13a5d4a40..d5a59c181 100755 --- a/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml +++ b/src/Ryujinx/UI/Views/Main/MainMenuBarView.axaml @@ -184,6 +184,22 @@ IsVisible="{Binding CanScanAmiiboBinaries}" InputGesture="Ctrl + B" IsEnabled="{Binding IsAmiiboBinRequested}" /> + +