mirror of
https://git.naxdy.org/Mirror/Ryujinx.git
synced 2026-03-11 20:25:30 +00:00
NFC Mifare Manager (ryubing/ryujinx!270)
See merge request ryubing/ryujinx!270
This commit is contained in:
parent
012d1d6886
commit
b70e2e44cb
19 changed files with 872 additions and 0 deletions
|
|
@ -700,6 +700,56 @@
|
||||||
"zh_TW": "掃描 Amiibo"
|
"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",
|
"ID": "MenuBarActionsScanAmiiboBin",
|
||||||
"Translations": {
|
"Translations": {
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ using Ryujinx.HLE.HOS.Services.Mii;
|
||||||
using Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption;
|
using Ryujinx.HLE.HOS.Services.Nfc.AmiiboDecryption;
|
||||||
using Ryujinx.HLE.HOS.Services.Nfc.Nfp;
|
using Ryujinx.HLE.HOS.Services.Nfc.Nfp;
|
||||||
using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager;
|
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;
|
||||||
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl;
|
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl;
|
||||||
using Ryujinx.HLE.HOS.Services.Pcv.Bpc;
|
using Ryujinx.HLE.HOS.Services.Pcv.Bpc;
|
||||||
|
|
@ -66,6 +67,8 @@ namespace Ryujinx.HLE.HOS
|
||||||
|
|
||||||
internal List<NfpDevice> NfpDevices { get; private set; }
|
internal List<NfpDevice> NfpDevices { get; private set; }
|
||||||
|
|
||||||
|
internal List<NfcDevice> NfcDevices { get; private set; }
|
||||||
|
|
||||||
internal SmRegistry SmRegistry { get; private set; }
|
internal SmRegistry SmRegistry { get; private set; }
|
||||||
|
|
||||||
internal ServerBase SmServer { get; private set; }
|
internal ServerBase SmServer { get; private set; }
|
||||||
|
|
@ -132,6 +135,7 @@ namespace Ryujinx.HLE.HOS
|
||||||
PerformanceState = new PerformanceState();
|
PerformanceState = new PerformanceState();
|
||||||
|
|
||||||
NfpDevices = [];
|
NfpDevices = [];
|
||||||
|
NfcDevices = [];
|
||||||
|
|
||||||
// Note: This is not really correct, but with HLE of services, the only memory
|
// 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.
|
// 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)
|
public bool SearchingForAmiibo(out int nfpDeviceId)
|
||||||
{
|
{
|
||||||
nfpDeviceId = default;
|
nfpDeviceId = default;
|
||||||
|
|
@ -389,6 +402,53 @@ namespace Ryujinx.HLE.HOS
|
||||||
return false;
|
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()
|
public void SignalDisplayResolutionChange()
|
||||||
{
|
{
|
||||||
DisplayResolutionChangeEvent.ReadableEvent.Signal();
|
DisplayResolutionChangeEvent.ReadableEvent.Signal();
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,19 @@
|
||||||
|
using Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare
|
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare
|
||||||
{
|
{
|
||||||
[Service("nfc:mf:u")]
|
[Service("nfc:mf:u")]
|
||||||
class IUserManager : IpcService
|
class IUserManager : IpcService
|
||||||
{
|
{
|
||||||
public IUserManager(ServiceCtx context) { }
|
public IUserManager(ServiceCtx context) { }
|
||||||
|
|
||||||
|
[CommandCmif(0)]
|
||||||
|
// CreateUserInterface() -> object<nn::nfc::mf::IUser>
|
||||||
|
public ResultCode CreateUserInterface(ServiceCtx context)
|
||||||
|
{
|
||||||
|
MakeObject(context, new IMifare());
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
477
src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/IMifare.cs
Normal file
477
src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/MifareManager/IMifare.cs
Normal file
|
|
@ -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<byte, NfcMifareReadBlockParameter>(readBlockParameter);
|
||||||
|
var list = new List<NfcMifareReadBlockParameter>(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>(),
|
||||||
|
};
|
||||||
|
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<NfcMifareReadBlockData>())), 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<byte, NfcMifareWriteBlockParameter>(writeBlockParameter);
|
||||||
|
var list = new List<NfcMifareWriteBlockParameter>(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<TagInfo>());
|
||||||
|
|
||||||
|
MemoryHelper.FillWithZeros(context.Memory, outputPosition, Marshal.SizeOf<TagInfo>());
|
||||||
|
|
||||||
|
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<byte>(),
|
||||||
|
Protocol = (uint)NfcProtocol.NfcProtocol_TypeA, // Type A Protocol
|
||||||
|
TagType = (uint)NfcTagType.NfcTagType_Mifare, // Mifare Type
|
||||||
|
Reserved2 = new Array6<byte>(),
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<byte> Data;
|
||||||
|
public byte SectorNumber;
|
||||||
|
public Array7<byte> Reserved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<byte> Reserved;
|
||||||
|
public NfcSectorKey SectorKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<byte> Data;
|
||||||
|
public byte SectorNumber;
|
||||||
|
public Array7<byte> Reserved;
|
||||||
|
public NfcSectorKey SectorKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<byte> Reserved1;
|
||||||
|
public Array6<byte> SectorKey;
|
||||||
|
public Array2<byte> Reserved2;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace Ryujinx.HLE.HOS.Services.Nfc.Mifare.MifareManager
|
||||||
|
{
|
||||||
|
enum State
|
||||||
|
{
|
||||||
|
NonInitialized,
|
||||||
|
Initialized,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<byte> Uuid;
|
||||||
|
public byte UuidLength;
|
||||||
|
public Array21<byte> Reserved1;
|
||||||
|
public uint Protocol;
|
||||||
|
public uint TagType;
|
||||||
|
public Array6<byte> Reserved2;
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/ResultCode.cs
Normal file
17
src/Ryujinx.HLE/HOS/Services/Nfc/Mifare/ResultCode.cs
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -370,6 +370,39 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
|
|
||||||
public bool CanScanAmiiboBinaries => AmiiboBinReader.HasAmiiboKeyFile;
|
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
|
public bool ShowLoadProgress
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
|
|
@ -1864,6 +1897,46 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public async Task OpenSkylanderWindow()
|
||||||
|
{
|
||||||
|
if (AppHost.Device.System.SearchingForSkylander(out int deviceId))
|
||||||
|
{
|
||||||
|
Optional<IStorageFile> result = await StorageProvider.OpenSingleFilePickerAsync(
|
||||||
|
new FilePickerOpenOptions
|
||||||
|
{
|
||||||
|
Title = LocaleManager.Instance[LocaleKeys.OpenFileDialogTitle],
|
||||||
|
FileTypeFilter = new List<FilePickerFileType>
|
||||||
|
{
|
||||||
|
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()
|
public void ReloadRenderDocApi()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -184,6 +184,22 @@
|
||||||
IsVisible="{Binding CanScanAmiiboBinaries}"
|
IsVisible="{Binding CanScanAmiiboBinaries}"
|
||||||
InputGesture="Ctrl + B"
|
InputGesture="Ctrl + B"
|
||||||
IsEnabled="{Binding IsAmiiboBinRequested}" />
|
IsEnabled="{Binding IsAmiiboBinRequested}" />
|
||||||
|
<MenuItem
|
||||||
|
Command="{Binding OpenSkylanderWindow}"
|
||||||
|
AttachedToVisualTree="ScanSkylanderMenuItem_AttachedToVisualTree"
|
||||||
|
Header="{ext:Locale MenuBarActionsScanSkylander}"
|
||||||
|
Icon="{ext:Icon fa-solid fa-cube}"
|
||||||
|
IsVisible="{Binding ShowSkylanderActions}"
|
||||||
|
InputGesture="Ctrl + S"
|
||||||
|
IsEnabled="{Binding IsSkylanderRequested}" />
|
||||||
|
<MenuItem
|
||||||
|
Command="{Binding RemoveSkylander}"
|
||||||
|
AttachedToVisualTree="RemoveSkylanderMenuItem_AttachedToVisualTree"
|
||||||
|
Header="{ext:Locale MenuBarActionsRemoveSkylander}"
|
||||||
|
Icon="{ext:Icon fa-solid fa-cube}"
|
||||||
|
IsVisible="{Binding ShowSkylanderActions}"
|
||||||
|
InputGesture="Ctrl + D"
|
||||||
|
IsEnabled="{Binding HasSkylander}" />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
Command="{Binding TakeScreenshot}"
|
Command="{Binding TakeScreenshot}"
|
||||||
Header="{ext:Locale MenuBarFileToolsTakeScreenshot}"
|
Header="{ext:Locale MenuBarFileToolsTakeScreenshot}"
|
||||||
|
|
|
||||||
|
|
@ -193,6 +193,20 @@ namespace Ryujinx.Ava.UI.Views.Main
|
||||||
ViewModel.IsAmiiboBinRequested = ViewModel.IsAmiiboRequested && AmiiboBinReader.HasAmiiboKeyFile;
|
ViewModel.IsAmiiboBinRequested = ViewModel.IsAmiiboRequested && AmiiboBinReader.HasAmiiboKeyFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ScanSkylanderMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is MenuItem)
|
||||||
|
ViewModel.IsSkylanderRequested = ViewModel.AppHost.Device.System.SearchingForSkylander(out _);
|
||||||
|
ViewModel.ShowSkylanderActions = string.Equals(ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText.ToUpper(), "0100CCC0002E6000");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveSkylanderMenuItem_AttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is MenuItem)
|
||||||
|
ViewModel.HasSkylander = ViewModel.AppHost.Device.System.HasSkylander(out _);
|
||||||
|
ViewModel.ShowSkylanderActions = string.Equals(ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText.ToUpper(), "0100CCC0002E6000");
|
||||||
|
}
|
||||||
|
|
||||||
private async Task InstallFileTypes()
|
private async Task InstallFileTypes()
|
||||||
{
|
{
|
||||||
ViewModel.AreMimeTypesRegistered = FileAssociationHelper.Install();
|
ViewModel.AreMimeTypesRegistered = FileAssociationHelper.Install();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue