nfp: Amiibo scanning support (#2006)
* Initial Impl. * You just want me cause I'm next * Fix some logics * Fix close button
This commit is contained in:
parent
2b92c10105
commit
a56423802c
22 changed files with 1830 additions and 144 deletions
|
@ -22,6 +22,7 @@ using Ryujinx.HLE.HOS.Services.Apm;
|
||||||
using Ryujinx.HLE.HOS.Services.Arp;
|
using Ryujinx.HLE.HOS.Services.Arp;
|
||||||
using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
|
using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
|
||||||
using Ryujinx.HLE.HOS.Services.Mii;
|
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;
|
||||||
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;
|
||||||
|
@ -33,6 +34,7 @@ using Ryujinx.HLE.HOS.SystemState;
|
||||||
using Ryujinx.HLE.Loaders.Executables;
|
using Ryujinx.HLE.Loaders.Executables;
|
||||||
using Ryujinx.HLE.Utilities;
|
using Ryujinx.HLE.Utilities;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
@ -65,6 +67,8 @@ namespace Ryujinx.HLE.HOS
|
||||||
|
|
||||||
internal AppletStateMgr AppletState { get; private set; }
|
internal AppletStateMgr AppletState { get; private set; }
|
||||||
|
|
||||||
|
internal List<NfpDevice> NfpDevices { get; private set; }
|
||||||
|
|
||||||
internal ServerBase BsdServer { get; private set; }
|
internal ServerBase BsdServer { get; private set; }
|
||||||
internal ServerBase AudRenServer { get; private set; }
|
internal ServerBase AudRenServer { get; private set; }
|
||||||
internal ServerBase AudOutServer { get; private set; }
|
internal ServerBase AudOutServer { get; private set; }
|
||||||
|
@ -113,6 +117,8 @@ namespace Ryujinx.HLE.HOS
|
||||||
|
|
||||||
PerformanceState = new PerformanceState();
|
PerformanceState = new PerformanceState();
|
||||||
|
|
||||||
|
NfpDevices = new List<NfpDevice>();
|
||||||
|
|
||||||
// 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.
|
||||||
KMemoryRegionManager region = KernelContext.MemoryRegions[(int)MemoryRegion.NvServices];
|
KMemoryRegionManager region = KernelContext.MemoryRegions[(int)MemoryRegion.NvServices];
|
||||||
|
@ -320,6 +326,33 @@ namespace Ryujinx.HLE.HOS
|
||||||
AppletState.MessageEvent.ReadableEvent.Signal();
|
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()
|
public void SignalDisplayResolutionChange()
|
||||||
{
|
{
|
||||||
DisplayResolutionChangeEvent.ReadableEvent.Signal();
|
DisplayResolutionChangeEvent.ReadableEvent.Signal();
|
||||||
|
|
|
@ -389,7 +389,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types
|
||||||
|
|
||||||
coreData.SetDefault();
|
coreData.SetDefault();
|
||||||
|
|
||||||
if (gender == Types.Gender.All)
|
if (gender == Gender.All)
|
||||||
{
|
{
|
||||||
gender = (Gender)utilImpl.GetRandom((int)gender);
|
gender = (Gender)utilImpl.GetRandom((int)gender);
|
||||||
}
|
}
|
||||||
|
@ -432,7 +432,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types
|
||||||
|
|
||||||
int axisY = 0;
|
int axisY = 0;
|
||||||
|
|
||||||
if (gender == Types.Gender.Female && age == Age.Young)
|
if (gender == Gender.Female && age == Age.Young)
|
||||||
{
|
{
|
||||||
axisY = utilImpl.GetRandom(3);
|
axisY = utilImpl.GetRandom(3);
|
||||||
}
|
}
|
||||||
|
@ -466,8 +466,8 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types
|
||||||
// Eye
|
// Eye
|
||||||
coreData.EyeType = (EyeType)eyeTypeInfo.Values[utilImpl.GetRandom(eyeTypeInfo.ValuesCount)];
|
coreData.EyeType = (EyeType)eyeTypeInfo.Values[utilImpl.GetRandom(eyeTypeInfo.ValuesCount)];
|
||||||
|
|
||||||
int eyeRotateKey1 = gender != Types.Gender.Male ? 4 : 2;
|
int eyeRotateKey1 = gender != Gender.Male ? 4 : 2;
|
||||||
int eyeRotateKey2 = gender != Types.Gender.Male ? 3 : 4;
|
int eyeRotateKey2 = gender != Gender.Male ? 3 : 4;
|
||||||
|
|
||||||
byte eyeRotateOffset = (byte)(32 - EyeRotateTable[eyeRotateKey1] + eyeRotateKey2);
|
byte eyeRotateOffset = (byte)(32 - EyeRotateTable[eyeRotateKey1] + eyeRotateKey2);
|
||||||
byte eyeRotate = (byte)(32 - EyeRotateTable[(int)coreData.EyeType]);
|
byte eyeRotate = (byte)(32 - EyeRotateTable[(int)coreData.EyeType]);
|
||||||
|
@ -496,14 +496,14 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types
|
||||||
coreData.EyebrowY = (byte)(axisY + eyebrowY);
|
coreData.EyebrowY = (byte)(axisY + eyebrowY);
|
||||||
|
|
||||||
// Nose
|
// 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.NoseType = (NoseType)noseTypeInfo.Values[utilImpl.GetRandom(noseTypeInfo.ValuesCount)];
|
||||||
coreData.NoseScale = (byte)noseScale;
|
coreData.NoseScale = (byte)noseScale;
|
||||||
coreData.NoseY = (byte)(axisY + 9);
|
coreData.NoseY = (byte)(axisY + 9);
|
||||||
|
|
||||||
// Mouth
|
// 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.MouthType = (MouthType)mouthTypeInfo.Values[utilImpl.GetRandom(mouthTypeInfo.ValuesCount)];
|
||||||
coreData.MouthColor = (CommonColor)Helper.Ver3MouthColorTable[mouthColor];
|
coreData.MouthColor = (CommonColor)Helper.Ver3MouthColorTable[mouthColor];
|
||||||
|
@ -515,7 +515,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types
|
||||||
coreData.BeardColor = coreData.HairColor;
|
coreData.BeardColor = coreData.HairColor;
|
||||||
coreData.MustacheScale = 4;
|
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);
|
BeardAndMustacheFlag mustacheAndBeardFlag = (BeardAndMustacheFlag)utilImpl.GetRandom(3);
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,11 @@
|
||||||
Success = 0,
|
Success = 0,
|
||||||
|
|
||||||
DeviceNotFound = (64 << ErrorCodeShift) | ModuleId,
|
DeviceNotFound = (64 << ErrorCodeShift) | ModuleId,
|
||||||
DevicesBufferIsNull = (65 << 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
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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.Ipc;
|
||||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
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.Hid.HidServer;
|
||||||
using Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager;
|
using Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager;
|
||||||
using System;
|
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
|
namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
|
||||||
{
|
{
|
||||||
class IUser : IpcService
|
class IUser : IpcService
|
||||||
{
|
{
|
||||||
|
private ulong _appletResourceUserId;
|
||||||
|
private ulong _mcuVersionData;
|
||||||
|
private byte[] _mcuData;
|
||||||
|
|
||||||
private State _state = State.NonInitialized;
|
private State _state = State.NonInitialized;
|
||||||
|
|
||||||
private KEvent _availabilityChangeEvent;
|
private KEvent _availabilityChangeEvent;
|
||||||
private int _availabilityChangeEventHandle = 0;
|
|
||||||
|
|
||||||
private List<Device> _devices = new List<Device>();
|
private CancellationTokenSource _cancelTokenSource;
|
||||||
|
|
||||||
public IUser() { }
|
public IUser() { }
|
||||||
|
|
||||||
|
@ -25,32 +34,30 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
|
||||||
// Initialize(u64, u64, pid, buffer<unknown, 5>)
|
// Initialize(u64, u64, pid, buffer<unknown, 5>)
|
||||||
public ResultCode Initialize(ServiceCtx context)
|
public ResultCode Initialize(ServiceCtx context)
|
||||||
{
|
{
|
||||||
long appletResourceUserId = context.RequestData.ReadInt64();
|
_appletResourceUserId = context.RequestData.ReadUInt64();
|
||||||
long mcuVersionData = context.RequestData.ReadInt64();
|
_mcuVersionData = context.RequestData.ReadUInt64();
|
||||||
|
|
||||||
long inputPosition = context.Request.SendBuff[0].Position;
|
long inputPosition = context.Request.SendBuff[0].Position;
|
||||||
long inputSize = context.Request.SendBuff[0].Size;
|
long inputSize = context.Request.SendBuff[0].Size;
|
||||||
|
|
||||||
byte[] unknownBuffer = new byte[inputSize];
|
_mcuData = new byte[inputSize];
|
||||||
|
|
||||||
context.Memory.Read((ulong)inputPosition, unknownBuffer);
|
context.Memory.Read((ulong)inputPosition, _mcuData);
|
||||||
|
|
||||||
// NOTE: appletResourceUserId, mcuVersionData and the buffer are stored inside an internal struct.
|
// TODO: The mcuData buffer seems to contains entries with a size of 0x40 bytes each. Usage of the data needs to be determined.
|
||||||
// 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.
|
|
||||||
|
|
||||||
// TODO: Add an instance of nn::nfc::server::Manager when it will be implemented.
|
// TODO: Handle this in a controller class directly.
|
||||||
// Add an instance of nn::nfc::server::SaveData when it will be implemented.
|
// Every functions which use the Handle call nn::hid::system::GetXcdHandleForNpadWithNfc().
|
||||||
|
NfpDevice devicePlayer1 = new NfpDevice
|
||||||
// TODO: When we will be able to add multiple controllers add one entry by controller here.
|
|
||||||
Device device1 = new Device
|
|
||||||
{
|
{
|
||||||
NpadIdType = NpadIdType.Player1,
|
NpadIdType = NpadIdType.Player1,
|
||||||
Handle = HidUtils.GetIndexFromNpadIdType(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;
|
_state = State.Initialized;
|
||||||
|
|
||||||
|
@ -61,13 +68,18 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
|
||||||
// Finalize()
|
// Finalize()
|
||||||
public ResultCode Finalize(ServiceCtx context)
|
public ResultCode Finalize(ServiceCtx context)
|
||||||
{
|
{
|
||||||
// TODO: Call StopDetection() and Unmount() when they will be implemented.
|
if (_state == State.Initialized)
|
||||||
// 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 (_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;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
@ -78,23 +90,32 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
|
||||||
{
|
{
|
||||||
if (context.Request.RecvListBuff.Count == 0)
|
if (context.Request.RecvListBuff.Count == 0)
|
||||||
{
|
{
|
||||||
return ResultCode.DevicesBufferIsNull;
|
return ResultCode.WrongArgument;
|
||||||
}
|
}
|
||||||
|
|
||||||
long outputPosition = context.Request.RecvListBuff[0].Position;
|
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;
|
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;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
@ -103,56 +124,376 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
|
||||||
// StartDetection(bytes<8, 4>)
|
// StartDetection(bytes<8, 4>)
|
||||||
public ResultCode StartDetection(ServiceCtx context)
|
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)]
|
[Command(4)]
|
||||||
// StopDetection(bytes<8, 4>)
|
// StopDetection(bytes<8, 4>)
|
||||||
public ResultCode StopDetection(ServiceCtx context)
|
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)]
|
[Command(5)]
|
||||||
// Mount(bytes<8, 4>, u32, u32)
|
// Mount(bytes<8, 4>, u32, u32)
|
||||||
public ResultCode Mount(ServiceCtx context)
|
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)]
|
[Command(6)]
|
||||||
// Unmount(bytes<8, 4>)
|
// Unmount(bytes<8, 4>)
|
||||||
public ResultCode Unmount(ServiceCtx context)
|
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)]
|
[Command(7)]
|
||||||
// OpenApplicationArea(bytes<8, 4>, u32)
|
// OpenApplicationArea(bytes<8, 4>, u32)
|
||||||
public ResultCode OpenApplicationArea(ServiceCtx context)
|
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)]
|
[Command(8)]
|
||||||
// GetApplicationArea(bytes<8, 4>) -> (u32, buffer<unknown, 6>)
|
// GetApplicationArea(bytes<8, 4>) -> (u32, buffer<unknown, 6>)
|
||||||
public ResultCode GetApplicationArea(ServiceCtx context)
|
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)]
|
[Command(9)]
|
||||||
// SetApplicationArea(bytes<8, 4>, buffer<unknown, 5>)
|
// SetApplicationArea(bytes<8, 4>, buffer<unknown, 5>)
|
||||||
public ResultCode SetApplicationArea(ServiceCtx context)
|
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)]
|
[Command(10)]
|
||||||
// Flush(bytes<8, 4>)
|
// Flush(bytes<8, 4>)
|
||||||
public ResultCode Flush(ServiceCtx context)
|
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)]
|
[Command(11)]
|
||||||
|
@ -166,35 +507,328 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
|
||||||
// CreateApplicationArea(bytes<8, 4>, u32, buffer<unknown, 5>)
|
// CreateApplicationArea(bytes<8, 4>, u32, buffer<unknown, 5>)
|
||||||
public ResultCode CreateApplicationArea(ServiceCtx context)
|
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)]
|
[Command(13)]
|
||||||
// GetTagInfo(bytes<8, 4>) -> buffer<unknown<0x58>, 0x1a>
|
// GetTagInfo(bytes<8, 4>) -> buffer<unknown<0x58>, 0x1a>
|
||||||
public ResultCode GetTagInfo(ServiceCtx context)
|
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)]
|
[Command(14)]
|
||||||
// GetRegisterInfo(bytes<8, 4>) -> buffer<unknown<0x100>, 0x1a>
|
// GetRegisterInfo(bytes<8, 4>) -> buffer<unknown<0x100>, 0x1a>
|
||||||
public ResultCode GetRegisterInfo(ServiceCtx context)
|
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)]
|
[Command(15)]
|
||||||
// GetCommonInfo(bytes<8, 4>) -> buffer<unknown<0x40>, 0x1a>
|
// GetCommonInfo(bytes<8, 4>) -> buffer<unknown<0x40>, 0x1a>
|
||||||
public ResultCode GetCommonInfo(ServiceCtx context)
|
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)]
|
[Command(16)]
|
||||||
// GetModelInfo(bytes<8, 4>) -> buffer<unknown<0x40>, 0x1a>
|
// GetModelInfo(bytes<8, 4>) -> buffer<unknown<0x40>, 0x1a>
|
||||||
public ResultCode GetModelInfo(ServiceCtx context)
|
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)]
|
[Command(17)]
|
||||||
|
@ -203,21 +837,18 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
|
||||||
{
|
{
|
||||||
uint deviceHandle = context.RequestData.ReadUInt32();
|
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)
|
context.Device.System.NfpDevices[i].ActivateEvent = new KEvent(context.Device.System.KernelContext);
|
||||||
{
|
|
||||||
_devices[i].ActivateEvent = new KEvent(context.Device.System.KernelContext);
|
|
||||||
|
|
||||||
if (context.Process.HandleTable.GenerateHandle(_devices[i].ActivateEvent.ReadableEvent, out _devices[i].ActivateEventHandle) != KernelResult.Success)
|
if (context.Process.HandleTable.GenerateHandle(context.Device.System.NfpDevices[i].ActivateEvent.ReadableEvent, out int activateEventHandle) != KernelResult.Success)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Out of handles!");
|
throw new InvalidOperationException("Out of handles!");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_devices[i].ActivateEventHandle);
|
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(activateEventHandle);
|
||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
@ -232,21 +863,18 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
|
||||||
{
|
{
|
||||||
uint deviceHandle = context.RequestData.ReadUInt32();
|
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)
|
context.Device.System.NfpDevices[i].DeactivateEvent = new KEvent(context.Device.System.KernelContext);
|
||||||
{
|
|
||||||
_devices[i].DeactivateEvent = new KEvent(context.Device.System.KernelContext);
|
|
||||||
|
|
||||||
if (context.Process.HandleTable.GenerateHandle(_devices[i].DeactivateEvent.ReadableEvent, out _devices[i].DeactivateEventHandle) != KernelResult.Success)
|
if (context.Process.HandleTable.GenerateHandle(context.Device.System.NfpDevices[i].DeactivateEvent.ReadableEvent, out int deactivateEventHandle) != KernelResult.Success)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Out of handles!");
|
throw new InvalidOperationException("Out of handles!");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_devices[i].DeactivateEventHandle);
|
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(deactivateEventHandle);
|
||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
@ -270,17 +898,22 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
|
||||||
{
|
{
|
||||||
uint deviceHandle = context.RequestData.ReadUInt32();
|
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;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context.ResponseData.Write((uint)DeviceState.Unavailable);
|
context.ResponseData.Write((uint)NfpDeviceState.Unavailable);
|
||||||
|
|
||||||
return ResultCode.DeviceNotFound;
|
return ResultCode.DeviceNotFound;
|
||||||
}
|
}
|
||||||
|
@ -291,11 +924,11 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
|
||||||
{
|
{
|
||||||
uint deviceHandle = context.RequestData.ReadUInt32();
|
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;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
@ -305,27 +938,26 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
|
||||||
}
|
}
|
||||||
|
|
||||||
[Command(22)]
|
[Command(22)]
|
||||||
// GetApplicationAreaSize(bytes<8, 4>) -> u32
|
// GetApplicationAreaSize() -> u32
|
||||||
public ResultCode GetApplicationAreaSize(ServiceCtx context)
|
public ResultCode GetApplicationAreaSize(ServiceCtx context)
|
||||||
{
|
{
|
||||||
throw new ServiceNotImplementedException(this, context);
|
context.ResponseData.Write(AmiiboConstants.ApplicationAreaSize);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Command(23)] // 3.0.0+
|
[Command(23)] // 3.0.0+
|
||||||
// AttachAvailabilityChangeEvent() -> handle<copy>
|
// AttachAvailabilityChangeEvent() -> handle<copy>
|
||||||
public ResultCode AttachAvailabilityChangeEvent(ServiceCtx context)
|
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)
|
if (context.Process.HandleTable.GenerateHandle(_availabilityChangeEvent.ReadableEvent, out int availabilityChangeEventHandle) != KernelResult.Success)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Out of handles!");
|
throw new InvalidOperationException("Out of handles!");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_availabilityChangeEventHandle);
|
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(availabilityChangeEventHandle);
|
||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
@ -336,5 +968,11 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
|
||||||
{
|
{
|
||||||
throw new ServiceNotImplementedException(this, context);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager
|
||||||
|
{
|
||||||
|
enum DeviceType : uint
|
||||||
|
{
|
||||||
|
Amiibo
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager
|
||||||
|
{
|
||||||
|
enum MountTarget : uint
|
||||||
|
{
|
||||||
|
Rom = 1,
|
||||||
|
Ram = 2,
|
||||||
|
All = 3
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager
|
namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager
|
||||||
{
|
{
|
||||||
enum DeviceState
|
enum NfpDeviceState
|
||||||
{
|
{
|
||||||
Initialized = 0,
|
Initialized = 0,
|
||||||
SearchingForTag = 1,
|
SearchingForTag = 1,
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
205
Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs
Normal file
205
Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs
Normal file
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -70,6 +70,7 @@
|
||||||
<None Remove="Ui\Resources\Icon_NSO.png" />
|
<None Remove="Ui\Resources\Icon_NSO.png" />
|
||||||
<None Remove="Ui\Resources\Icon_NSP.png" />
|
<None Remove="Ui\Resources\Icon_NSP.png" />
|
||||||
<None Remove="Ui\Resources\Icon_XCI.png" />
|
<None Remove="Ui\Resources\Icon_XCI.png" />
|
||||||
|
<None Remove="Ui\Resources\Logo_Amiibo.png" />
|
||||||
<None Remove="Ui\Resources\Logo_Discord.png" />
|
<None Remove="Ui\Resources\Logo_Discord.png" />
|
||||||
<None Remove="Ui\Resources\Logo_GitHub.png" />
|
<None Remove="Ui\Resources\Logo_GitHub.png" />
|
||||||
<None Remove="Ui\Resources\Logo_Patreon.png" />
|
<None Remove="Ui\Resources\Logo_Patreon.png" />
|
||||||
|
@ -94,6 +95,7 @@
|
||||||
<EmbeddedResource Include="Ui\Resources\Icon_NSO.png" />
|
<EmbeddedResource Include="Ui\Resources\Icon_NSO.png" />
|
||||||
<EmbeddedResource Include="Ui\Resources\Icon_NSP.png" />
|
<EmbeddedResource Include="Ui\Resources\Icon_NSP.png" />
|
||||||
<EmbeddedResource Include="Ui\Resources\Icon_XCI.png" />
|
<EmbeddedResource Include="Ui\Resources\Icon_XCI.png" />
|
||||||
|
<EmbeddedResource Include="Ui\Resources\Logo_Amiibo.png" />
|
||||||
<EmbeddedResource Include="Ui\Resources\Logo_Discord.png" />
|
<EmbeddedResource Include="Ui\Resources\Logo_Discord.png" />
|
||||||
<EmbeddedResource Include="Ui\Resources\Logo_GitHub.png" />
|
<EmbeddedResource Include="Ui\Resources\Logo_GitHub.png" />
|
||||||
<EmbeddedResource Include="Ui\Resources\Logo_Patreon.png" />
|
<EmbeddedResource Include="Ui\Resources\Logo_Patreon.png" />
|
||||||
|
|
|
@ -57,6 +57,9 @@ namespace Ryujinx.Ui
|
||||||
|
|
||||||
private string _currentEmulatedGamePath = null;
|
private string _currentEmulatedGamePath = null;
|
||||||
|
|
||||||
|
private string _lastScannedAmiiboId = "";
|
||||||
|
private bool _lastScannedAmiiboShowAll = false;
|
||||||
|
|
||||||
public GlRenderer GlRendererWidget;
|
public GlRenderer GlRendererWidget;
|
||||||
|
|
||||||
#pragma warning disable CS0169, CS0649, IDE0044
|
#pragma warning disable CS0169, CS0649, IDE0044
|
||||||
|
@ -66,8 +69,11 @@ namespace Ryujinx.Ui
|
||||||
[GUI] MenuBar _menuBar;
|
[GUI] MenuBar _menuBar;
|
||||||
[GUI] Box _footerBox;
|
[GUI] Box _footerBox;
|
||||||
[GUI] Box _statusBar;
|
[GUI] Box _statusBar;
|
||||||
|
[GUI] MenuItem _optionMenu;
|
||||||
|
[GUI] MenuItem _actionMenu;
|
||||||
[GUI] MenuItem _stopEmulation;
|
[GUI] MenuItem _stopEmulation;
|
||||||
[GUI] MenuItem _simulateWakeUpMessage;
|
[GUI] MenuItem _simulateWakeUpMessage;
|
||||||
|
[GUI] MenuItem _scanAmiibo;
|
||||||
[GUI] MenuItem _fullScreen;
|
[GUI] MenuItem _fullScreen;
|
||||||
[GUI] CheckMenuItem _startFullScreen;
|
[GUI] CheckMenuItem _startFullScreen;
|
||||||
[GUI] CheckMenuItem _favToggle;
|
[GUI] CheckMenuItem _favToggle;
|
||||||
|
@ -141,6 +147,8 @@ namespace Ryujinx.Ui
|
||||||
_applicationLibrary.ApplicationAdded += Application_Added;
|
_applicationLibrary.ApplicationAdded += Application_Added;
|
||||||
_applicationLibrary.ApplicationCountUpdated += ApplicationCount_Updated;
|
_applicationLibrary.ApplicationCountUpdated += ApplicationCount_Updated;
|
||||||
|
|
||||||
|
_actionMenu.StateChanged += ActionMenu_StateChanged;
|
||||||
|
|
||||||
_gameTable.ButtonReleaseEvent += Row_Clicked;
|
_gameTable.ButtonReleaseEvent += Row_Clicked;
|
||||||
_fullScreen.Activated += FullScreen_Toggled;
|
_fullScreen.Activated += FullScreen_Toggled;
|
||||||
|
|
||||||
|
@ -151,8 +159,7 @@ namespace Ryujinx.Ui
|
||||||
_startFullScreen.Active = true;
|
_startFullScreen.Active = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_stopEmulation.Sensitive = false;
|
_actionMenu.Sensitive = false;
|
||||||
_simulateWakeUpMessage.Sensitive = false;
|
|
||||||
|
|
||||||
if (ConfigurationState.Instance.Ui.GuiColumns.FavColumn) _favToggle.Active = true;
|
if (ConfigurationState.Instance.Ui.GuiColumns.FavColumn) _favToggle.Active = true;
|
||||||
if (ConfigurationState.Instance.Ui.GuiColumns.IconColumn) _iconToggle.Active = true;
|
if (ConfigurationState.Instance.Ui.GuiColumns.IconColumn) _iconToggle.Active = true;
|
||||||
|
@ -595,8 +602,9 @@ namespace Ryujinx.Ui
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
_gameLoaded = true;
|
_gameLoaded = true;
|
||||||
_stopEmulation.Sensitive = true;
|
_actionMenu.Sensitive = true;
|
||||||
_simulateWakeUpMessage.Sensitive = true;
|
|
||||||
|
_lastScannedAmiiboId = "";
|
||||||
|
|
||||||
_firmwareInstallFile.Sensitive = false;
|
_firmwareInstallFile.Sensitive = false;
|
||||||
_firmwareInstallDirectory.Sensitive = false;
|
_firmwareInstallDirectory.Sensitive = false;
|
||||||
|
@ -692,8 +700,7 @@ namespace Ryujinx.Ui
|
||||||
Task.Run(RefreshFirmwareLabel);
|
Task.Run(RefreshFirmwareLabel);
|
||||||
Task.Run(HandleRelaunch);
|
Task.Run(HandleRelaunch);
|
||||||
|
|
||||||
_stopEmulation.Sensitive = false;
|
_actionMenu.Sensitive = false;
|
||||||
_simulateWakeUpMessage.Sensitive = false;
|
|
||||||
_firmwareInstallFile.Sensitive = true;
|
_firmwareInstallFile.Sensitive = true;
|
||||||
_firmwareInstallDirectory.Sensitive = true;
|
_firmwareInstallDirectory.Sensitive = true;
|
||||||
});
|
});
|
||||||
|
@ -1179,6 +1186,44 @@ namespace Ryujinx.Ui
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ActionMenu_StateChanged(object o, StateChangedArgs args)
|
||||||
|
{
|
||||||
|
_scanAmiibo.Sensitive = _emulationContext != null && _emulationContext.System.SearchingForAmiibo(out int _);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Scan_Amiibo(object sender, EventArgs args)
|
||||||
|
{
|
||||||
|
if (_emulationContext.System.SearchingForAmiibo(out int deviceId))
|
||||||
|
{
|
||||||
|
AmiiboWindow amiiboWindow = new AmiiboWindow
|
||||||
|
{
|
||||||
|
LastScannedAmiiboShowAll = _lastScannedAmiiboShowAll,
|
||||||
|
LastScannedAmiiboId = _lastScannedAmiiboId,
|
||||||
|
DeviceId = deviceId,
|
||||||
|
TitleId = _emulationContext.Application.TitleIdText.ToUpper()
|
||||||
|
};
|
||||||
|
|
||||||
|
amiiboWindow.DeleteEvent += AmiiboWindow_DeleteEvent;
|
||||||
|
|
||||||
|
amiiboWindow.Show();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GtkDialog.CreateInfoDialog($"Amiibo", "The game is currently not ready to receive Amiibo scan data. Ensure that you have an Amiibo-compatible game open and ready to receive Amiibo scan data.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AmiiboWindow_DeleteEvent(object sender, DeleteEventArgs args)
|
||||||
|
{
|
||||||
|
if (((AmiiboWindow)sender).AmiiboId != "" && ((AmiiboWindow)sender).Response == ResponseType.Ok)
|
||||||
|
{
|
||||||
|
_lastScannedAmiiboId = ((AmiiboWindow)sender).AmiiboId;
|
||||||
|
_lastScannedAmiiboShowAll = ((AmiiboWindow)sender).LastScannedAmiiboShowAll;
|
||||||
|
|
||||||
|
_emulationContext.System.ScanAmiibo(((AmiiboWindow)sender).DeviceId, ((AmiiboWindow)sender).AmiiboId, ((AmiiboWindow)sender).UseRandomUuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void Update_Pressed(object sender, EventArgs args)
|
private void Update_Pressed(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
if (Updater.CanUpdate(true))
|
if (Updater.CanUpdate(true))
|
||||||
|
|
|
@ -95,7 +95,7 @@
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkMenuItem" id="OptionsMenu">
|
<object class="GtkMenuItem" id="_optionMenu">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="label" translatable="yes">Options</property>
|
<property name="label" translatable="yes">Options</property>
|
||||||
|
@ -127,32 +127,6 @@
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
|
||||||
<object class="GtkMenuItem" id="_stopEmulation">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
<property name="tooltip_text" translatable="yes">Stop emulation of the current game and return to game selection</property>
|
|
||||||
<property name="label" translatable="yes">Stop Emulation</property>
|
|
||||||
<property name="use_underline">True</property>
|
|
||||||
<signal name="activate" handler="StopEmulation_Pressed" swapped="no"/>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkMenuItem" id="_simulateWakeUpMessage">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
<property name="tooltip_text" translatable="yes">Simulate a Wake-up Message</property>
|
|
||||||
<property name="label" translatable="yes">Simulate Wake-up Message</property>
|
|
||||||
<property name="use_underline">True</property>
|
|
||||||
<signal name="activate" handler="Simulate_WakeUp_Message_Pressed" swapped="no"/>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkSeparatorMenuItem">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkMenuItem" id="GUIColumns">
|
<object class="GtkMenuItem" id="GUIColumns">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
|
@ -278,6 +252,56 @@
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkMenuItem" id="_actionMenu">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="label" translatable="yes">Actions</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<child type="submenu">
|
||||||
|
<object class="GtkMenu">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkMenuItem" id="_stopEmulation">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="tooltip_text" translatable="yes">Stop emulation of the current game and return to game selection</property>
|
||||||
|
<property name="label" translatable="yes">Stop Emulation</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<signal name="activate" handler="StopEmulation_Pressed" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkSeparatorMenuItem">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkMenuItem" id="_simulateWakeUpMessage">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="tooltip_text" translatable="yes">Simulate a Wake-up Message</property>
|
||||||
|
<property name="label" translatable="yes">Simulate Wake-up Message</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<signal name="activate" handler="Simulate_WakeUp_Message_Pressed" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkMenuItem" id="_scanAmiibo">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="tooltip_text" translatable="yes">Scan an Amiibo</property>
|
||||||
|
<property name="label" translatable="yes">Scan an Amiibo</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<signal name="activate" handler="Scan_Amiibo" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkMenuItem" id="_toolsMenu">
|
<object class="GtkMenuItem" id="_toolsMenu">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
|
|
BIN
Ryujinx/Ui/Resources/Logo_Amiibo.png
Normal file
BIN
Ryujinx/Ui/Resources/Logo_Amiibo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
194
Ryujinx/Ui/Windows/AmiiboWindow.Designer.cs
generated
Normal file
194
Ryujinx/Ui/Windows/AmiiboWindow.Designer.cs
generated
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
using Gtk;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ui.Windows
|
||||||
|
{
|
||||||
|
public partial class AmiiboWindow : Window
|
||||||
|
{
|
||||||
|
private Box _mainBox;
|
||||||
|
private ButtonBox _buttonBox;
|
||||||
|
private Button _scanButton;
|
||||||
|
private Button _cancelButton;
|
||||||
|
private CheckButton _randomUuidCheckBox;
|
||||||
|
private Box _amiiboBox;
|
||||||
|
private Box _amiiboHeadBox;
|
||||||
|
private Box _amiiboSeriesBox;
|
||||||
|
private Label _amiiboSeriesLabel;
|
||||||
|
private ComboBoxText _amiiboSeriesComboBox;
|
||||||
|
private Box _amiiboCharsBox;
|
||||||
|
private Label _amiiboCharsLabel;
|
||||||
|
private ComboBoxText _amiiboCharsComboBox;
|
||||||
|
private CheckButton _showAllCheckBox;
|
||||||
|
private Image _amiiboImage;
|
||||||
|
private Label _gameUsageLabel;
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
#pragma warning disable CS0612
|
||||||
|
|
||||||
|
//
|
||||||
|
// AmiiboWindow
|
||||||
|
//
|
||||||
|
CanFocus = false;
|
||||||
|
Resizable = false;
|
||||||
|
Modal = true;
|
||||||
|
WindowPosition = WindowPosition.Center;
|
||||||
|
DefaultWidth = 600;
|
||||||
|
DefaultHeight = 470;
|
||||||
|
TypeHint = Gdk.WindowTypeHint.Dialog;
|
||||||
|
|
||||||
|
//
|
||||||
|
// _mainBox
|
||||||
|
//
|
||||||
|
_mainBox = new Box(Orientation.Vertical, 2);
|
||||||
|
|
||||||
|
//
|
||||||
|
// _buttonBox
|
||||||
|
//
|
||||||
|
_buttonBox = new ButtonBox(Orientation.Horizontal)
|
||||||
|
{
|
||||||
|
Margin = 20,
|
||||||
|
LayoutStyle = ButtonBoxStyle.End
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// _scanButton
|
||||||
|
//
|
||||||
|
_scanButton = new Button()
|
||||||
|
{
|
||||||
|
Label = "Scan It!",
|
||||||
|
CanFocus = true,
|
||||||
|
ReceivesDefault = true,
|
||||||
|
MarginLeft = 10
|
||||||
|
};
|
||||||
|
_scanButton.Clicked += ScanButton_Pressed;
|
||||||
|
|
||||||
|
//
|
||||||
|
// _randomUuidCheckBox
|
||||||
|
//
|
||||||
|
_randomUuidCheckBox = new CheckButton()
|
||||||
|
{
|
||||||
|
Label = "Hack: Use Random Tag Uuid",
|
||||||
|
TooltipText = "This allows multiple scans of a single Amiibo.\n(used in The Legend of Zelda: Breath of the Wild)"
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// _cancelButton
|
||||||
|
//
|
||||||
|
_cancelButton = new Button()
|
||||||
|
{
|
||||||
|
Label = "Cancel",
|
||||||
|
CanFocus = true,
|
||||||
|
ReceivesDefault = true,
|
||||||
|
MarginLeft = 10
|
||||||
|
};
|
||||||
|
_cancelButton.Clicked += CancelButton_Pressed;
|
||||||
|
|
||||||
|
//
|
||||||
|
// _amiiboBox
|
||||||
|
//
|
||||||
|
_amiiboBox = new Box(Orientation.Vertical, 0);
|
||||||
|
|
||||||
|
//
|
||||||
|
// _amiiboHeadBox
|
||||||
|
//
|
||||||
|
_amiiboHeadBox = new Box(Orientation.Horizontal, 0)
|
||||||
|
{
|
||||||
|
Margin = 20,
|
||||||
|
Hexpand = true
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// _amiiboSeriesBox
|
||||||
|
//
|
||||||
|
_amiiboSeriesBox = new Box(Orientation.Horizontal, 0)
|
||||||
|
{
|
||||||
|
Hexpand = true
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// _amiiboSeriesLabel
|
||||||
|
//
|
||||||
|
_amiiboSeriesLabel = new Label("Amiibo Series:");
|
||||||
|
|
||||||
|
//
|
||||||
|
// _amiiboSeriesComboBox
|
||||||
|
//
|
||||||
|
_amiiboSeriesComboBox = new ComboBoxText();
|
||||||
|
|
||||||
|
//
|
||||||
|
// _amiiboCharsBox
|
||||||
|
//
|
||||||
|
_amiiboCharsBox = new Box(Orientation.Horizontal, 0)
|
||||||
|
{
|
||||||
|
Hexpand = true
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// _amiiboCharsLabel
|
||||||
|
//
|
||||||
|
_amiiboCharsLabel = new Label("Character:");
|
||||||
|
|
||||||
|
//
|
||||||
|
// _amiiboCharsComboBox
|
||||||
|
//
|
||||||
|
_amiiboCharsComboBox = new ComboBoxText();
|
||||||
|
|
||||||
|
//
|
||||||
|
// _showAllCheckBox
|
||||||
|
//
|
||||||
|
_showAllCheckBox = new CheckButton()
|
||||||
|
{
|
||||||
|
Label = "Show All Amiibo"
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// _amiiboImage
|
||||||
|
//
|
||||||
|
_amiiboImage = new Image()
|
||||||
|
{
|
||||||
|
HeightRequest = 350,
|
||||||
|
WidthRequest = 350
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
// _gameUsageLabel
|
||||||
|
//
|
||||||
|
_gameUsageLabel = new Label("")
|
||||||
|
{
|
||||||
|
MarginTop = 20
|
||||||
|
};
|
||||||
|
|
||||||
|
#pragma warning restore CS0612
|
||||||
|
|
||||||
|
ShowComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowComponent()
|
||||||
|
{
|
||||||
|
_buttonBox.Add(_showAllCheckBox);
|
||||||
|
_buttonBox.Add(_randomUuidCheckBox);
|
||||||
|
_buttonBox.Add(_scanButton);
|
||||||
|
_buttonBox.Add(_cancelButton);
|
||||||
|
|
||||||
|
_amiiboSeriesBox.Add(_amiiboSeriesLabel);
|
||||||
|
_amiiboSeriesBox.Add(_amiiboSeriesComboBox);
|
||||||
|
|
||||||
|
_amiiboCharsBox.Add(_amiiboCharsLabel);
|
||||||
|
_amiiboCharsBox.Add(_amiiboCharsComboBox);
|
||||||
|
|
||||||
|
_amiiboHeadBox.Add(_amiiboSeriesBox);
|
||||||
|
_amiiboHeadBox.Add(_amiiboCharsBox);
|
||||||
|
|
||||||
|
_amiiboBox.PackStart(_amiiboHeadBox, true, true, 0);
|
||||||
|
_amiiboBox.PackEnd(_gameUsageLabel, false, false, 0);
|
||||||
|
_amiiboBox.PackEnd(_amiiboImage, false, false, 0);
|
||||||
|
|
||||||
|
_mainBox.Add(_amiiboBox);
|
||||||
|
_mainBox.PackEnd(_buttonBox, false, false, 0);
|
||||||
|
|
||||||
|
Add(_mainBox);
|
||||||
|
|
||||||
|
ShowAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
422
Ryujinx/Ui/Windows/AmiiboWindow.cs
Normal file
422
Ryujinx/Ui/Windows/AmiiboWindow.cs
Normal file
|
@ -0,0 +1,422 @@
|
||||||
|
using Gtk;
|
||||||
|
using Ryujinx.Common;
|
||||||
|
using Ryujinx.Common.Configuration;
|
||||||
|
using Ryujinx.Ui.Widgets;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ui.Windows
|
||||||
|
{
|
||||||
|
public partial class AmiiboWindow : Window
|
||||||
|
{
|
||||||
|
private struct AmiiboJson
|
||||||
|
{
|
||||||
|
[JsonPropertyName("amiibo")]
|
||||||
|
public List<AmiiboApi> Amiibo { get; set; }
|
||||||
|
[JsonPropertyName("lastUpdated")]
|
||||||
|
public DateTime LastUpdated { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct AmiiboApi
|
||||||
|
{
|
||||||
|
[JsonPropertyName("name")]
|
||||||
|
public string Name { get; set; }
|
||||||
|
[JsonPropertyName("head")]
|
||||||
|
public string Head { get; set; }
|
||||||
|
[JsonPropertyName("tail")]
|
||||||
|
public string Tail { get; set; }
|
||||||
|
[JsonPropertyName("image")]
|
||||||
|
public string Image { get; set; }
|
||||||
|
[JsonPropertyName("amiiboSeries")]
|
||||||
|
public string AmiiboSeries { get; set; }
|
||||||
|
[JsonPropertyName("character")]
|
||||||
|
public string Character { get; set; }
|
||||||
|
[JsonPropertyName("gameSeries")]
|
||||||
|
public string GameSeries { get; set; }
|
||||||
|
[JsonPropertyName("type")]
|
||||||
|
public string Type { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("release")]
|
||||||
|
public Dictionary<string, string> Release { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("gamesSwitch")]
|
||||||
|
public List<AmiiboApiGamesSwitch> GamesSwitch { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AmiiboApiGamesSwitch
|
||||||
|
{
|
||||||
|
[JsonPropertyName("amiiboUsage")]
|
||||||
|
public List<AmiiboApiUsage> AmiiboUsage { get; set; }
|
||||||
|
[JsonPropertyName("gameID")]
|
||||||
|
public List<string> GameId { get; set; }
|
||||||
|
[JsonPropertyName("gameName")]
|
||||||
|
public string GameName { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AmiiboApiUsage
|
||||||
|
{
|
||||||
|
[JsonPropertyName("Usage")]
|
||||||
|
public string Usage { get; set; }
|
||||||
|
[JsonPropertyName("write")]
|
||||||
|
public bool Write { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private const string DEFAULT_JSON = "{ \"amiibo\": [] }";
|
||||||
|
|
||||||
|
public string AmiiboId { get; private set; }
|
||||||
|
|
||||||
|
public int DeviceId { get; set; }
|
||||||
|
public string TitleId { get; set; }
|
||||||
|
public string LastScannedAmiiboId { get; set; }
|
||||||
|
public bool LastScannedAmiiboShowAll { get; set; }
|
||||||
|
|
||||||
|
public ResponseType Response { get; private set; }
|
||||||
|
|
||||||
|
public bool UseRandomUuid
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _randomUuidCheckBox.Active;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly HttpClient _httpClient;
|
||||||
|
private readonly string _amiiboJsonPath;
|
||||||
|
|
||||||
|
private readonly byte[] _amiiboLogoBytes;
|
||||||
|
|
||||||
|
private List<AmiiboApi> _amiiboList;
|
||||||
|
|
||||||
|
public AmiiboWindow() : base($"Ryujinx {Program.Version} - Amiibo")
|
||||||
|
{
|
||||||
|
Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.Resources.Logo_Ryujinx.png");
|
||||||
|
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
_httpClient = new HttpClient()
|
||||||
|
{
|
||||||
|
Timeout = TimeSpan.FromMilliseconds(5000)
|
||||||
|
};
|
||||||
|
|
||||||
|
Directory.CreateDirectory(System.IO.Path.Join(AppDataManager.BaseDirPath, "system", "amiibo"));
|
||||||
|
|
||||||
|
_amiiboJsonPath = System.IO.Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", "Amiibo.json");
|
||||||
|
_amiiboList = new List<AmiiboApi>();
|
||||||
|
|
||||||
|
_amiiboLogoBytes = EmbeddedResources.Read("Ryujinx/Ui/Resources/Logo_Amiibo.png");
|
||||||
|
_amiiboImage.Pixbuf = new Gdk.Pixbuf(_amiiboLogoBytes);
|
||||||
|
|
||||||
|
_scanButton.Sensitive = false;
|
||||||
|
_randomUuidCheckBox.Sensitive = false;
|
||||||
|
|
||||||
|
_ = LoadContentAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadContentAsync()
|
||||||
|
{
|
||||||
|
string amiiboJsonString = DEFAULT_JSON;
|
||||||
|
|
||||||
|
if (File.Exists(_amiiboJsonPath))
|
||||||
|
{
|
||||||
|
amiiboJsonString = File.ReadAllText(_amiiboJsonPath);
|
||||||
|
|
||||||
|
if (await NeedsUpdate(JsonSerializer.Deserialize<AmiiboJson>(amiiboJsonString).LastUpdated))
|
||||||
|
{
|
||||||
|
amiiboJsonString = await DownloadAmiiboJson();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
amiiboJsonString = await DownloadAmiiboJson();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
ShowInfoDialog();
|
||||||
|
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_amiiboList = JsonSerializer.Deserialize<AmiiboJson>(amiiboJsonString).Amiibo;
|
||||||
|
_amiiboList = _amiiboList.OrderBy(amiibo => amiibo.AmiiboSeries).ToList();
|
||||||
|
|
||||||
|
if (LastScannedAmiiboShowAll)
|
||||||
|
{
|
||||||
|
_showAllCheckBox.Click();
|
||||||
|
}
|
||||||
|
|
||||||
|
ParseAmiiboData();
|
||||||
|
|
||||||
|
_showAllCheckBox.Clicked += ShowAllCheckBox_Clicked;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ParseAmiiboData()
|
||||||
|
{
|
||||||
|
List<string> comboxItemList = new List<string>();
|
||||||
|
|
||||||
|
for (int i = 0; i < _amiiboList.Count; i++)
|
||||||
|
{
|
||||||
|
if (!comboxItemList.Contains(_amiiboList[i].AmiiboSeries))
|
||||||
|
{
|
||||||
|
if (!_showAllCheckBox.Active)
|
||||||
|
{
|
||||||
|
foreach (var game in _amiiboList[i].GamesSwitch)
|
||||||
|
{
|
||||||
|
if (game != null)
|
||||||
|
{
|
||||||
|
if (game.GameId.Contains(TitleId))
|
||||||
|
{
|
||||||
|
comboxItemList.Add(_amiiboList[i].AmiiboSeries);
|
||||||
|
_amiiboSeriesComboBox.Append(_amiiboList[i].AmiiboSeries, _amiiboList[i].AmiiboSeries);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
comboxItemList.Add(_amiiboList[i].AmiiboSeries);
|
||||||
|
_amiiboSeriesComboBox.Append(_amiiboList[i].AmiiboSeries, _amiiboList[i].AmiiboSeries);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_amiiboSeriesComboBox.Changed += SeriesComboBox_Changed;
|
||||||
|
_amiiboCharsComboBox.Changed += CharacterComboBox_Changed;
|
||||||
|
|
||||||
|
if (LastScannedAmiiboId != "")
|
||||||
|
{
|
||||||
|
SelectLastScannedAmiibo();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_amiiboSeriesComboBox.Active = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SelectLastScannedAmiibo()
|
||||||
|
{
|
||||||
|
bool isSet = _amiiboSeriesComboBox.SetActiveId(_amiiboList.FirstOrDefault(amiibo => amiibo.Head + amiibo.Tail == LastScannedAmiiboId).AmiiboSeries);
|
||||||
|
isSet = _amiiboCharsComboBox.SetActiveId(LastScannedAmiiboId);
|
||||||
|
|
||||||
|
if (isSet == false)
|
||||||
|
{
|
||||||
|
_amiiboSeriesComboBox.Active = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> NeedsUpdate(DateTime oldLastModified)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
HttpResponseMessage response = await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, "https://amiibo.ryujinx.org/"));
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
return response.Content.Headers.LastModified != oldLastModified;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
ShowInfoDialog();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string> DownloadAmiiboJson()
|
||||||
|
{
|
||||||
|
HttpResponseMessage response = await _httpClient.GetAsync("https://amiibo.ryujinx.org/");
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
string amiiboJsonString = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
using (FileStream dlcJsonStream = File.Create(_amiiboJsonPath, 4096, FileOptions.WriteThrough))
|
||||||
|
{
|
||||||
|
dlcJsonStream.Write(Encoding.UTF8.GetBytes(amiiboJsonString));
|
||||||
|
}
|
||||||
|
|
||||||
|
return amiiboJsonString;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GtkDialog.CreateInfoDialog($"Amiibo API", "An error occured while fetching informations from the API.");
|
||||||
|
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return DEFAULT_JSON;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task UpdateAmiiboPreview(string imageUrl)
|
||||||
|
{
|
||||||
|
HttpResponseMessage response = await _httpClient.GetAsync(imageUrl);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
byte[] amiiboPreviewBytes = await response.Content.ReadAsByteArrayAsync();
|
||||||
|
Gdk.Pixbuf amiiboPreview = new Gdk.Pixbuf(amiiboPreviewBytes);
|
||||||
|
|
||||||
|
float ratio = Math.Min((float)_amiiboImage.AllocatedWidth / amiiboPreview.Width,
|
||||||
|
(float)_amiiboImage.AllocatedHeight / amiiboPreview.Height);
|
||||||
|
|
||||||
|
int resizeHeight = (int)(amiiboPreview.Height * ratio);
|
||||||
|
int resizeWidth = (int)(amiiboPreview.Width * ratio);
|
||||||
|
|
||||||
|
_amiiboImage.Pixbuf = amiiboPreview.ScaleSimple(resizeWidth, resizeHeight, Gdk.InterpType.Bilinear);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowInfoDialog()
|
||||||
|
{
|
||||||
|
GtkDialog.CreateInfoDialog($"Amiibo API", "Unable to connect to Amiibo API server. The service may be down or you may need to verify your internet connection is online.");
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Events
|
||||||
|
//
|
||||||
|
private void SeriesComboBox_Changed(object sender, EventArgs args)
|
||||||
|
{
|
||||||
|
_amiiboCharsComboBox.Changed -= CharacterComboBox_Changed;
|
||||||
|
|
||||||
|
_amiiboCharsComboBox.RemoveAll();
|
||||||
|
|
||||||
|
List<AmiiboApi> amiiboSortedList = _amiiboList.Where(amiibo => amiibo.AmiiboSeries == _amiiboSeriesComboBox.ActiveId).OrderBy(amiibo => amiibo.Name).ToList();
|
||||||
|
|
||||||
|
List<string> comboxItemList = new List<string>();
|
||||||
|
|
||||||
|
for (int i = 0; i < amiiboSortedList.Count; i++)
|
||||||
|
{
|
||||||
|
if (!comboxItemList.Contains(amiiboSortedList[i].Head + amiiboSortedList[i].Tail))
|
||||||
|
{
|
||||||
|
if (!_showAllCheckBox.Active)
|
||||||
|
{
|
||||||
|
foreach (var game in amiiboSortedList[i].GamesSwitch)
|
||||||
|
{
|
||||||
|
if (game != null)
|
||||||
|
{
|
||||||
|
if (game.GameId.Contains(TitleId))
|
||||||
|
{
|
||||||
|
comboxItemList.Add(amiiboSortedList[i].Head + amiiboSortedList[i].Tail);
|
||||||
|
_amiiboCharsComboBox.Append(amiiboSortedList[i].Head + amiiboSortedList[i].Tail, amiiboSortedList[i].Name);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
comboxItemList.Add(amiiboSortedList[i].Head + amiiboSortedList[i].Tail);
|
||||||
|
_amiiboCharsComboBox.Append(amiiboSortedList[i].Head + amiiboSortedList[i].Tail, amiiboSortedList[i].Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_amiiboCharsComboBox.Changed += CharacterComboBox_Changed;
|
||||||
|
|
||||||
|
_amiiboCharsComboBox.Active = 0;
|
||||||
|
|
||||||
|
_scanButton.Sensitive = true;
|
||||||
|
_randomUuidCheckBox.Sensitive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CharacterComboBox_Changed(object sender, EventArgs args)
|
||||||
|
{
|
||||||
|
AmiiboId = _amiiboCharsComboBox.ActiveId;
|
||||||
|
|
||||||
|
_amiiboImage.Pixbuf = new Gdk.Pixbuf(_amiiboLogoBytes);
|
||||||
|
|
||||||
|
string imageUrl = _amiiboList.FirstOrDefault(amiibo => amiibo.Head + amiibo.Tail == _amiiboCharsComboBox.ActiveId).Image;
|
||||||
|
|
||||||
|
string usageString = "";
|
||||||
|
|
||||||
|
for (int i = 0; i < _amiiboList.Count; i++)
|
||||||
|
{
|
||||||
|
if (_amiiboList[i].Head + _amiiboList[i].Tail == _amiiboCharsComboBox.ActiveId)
|
||||||
|
{
|
||||||
|
bool writable = false;
|
||||||
|
|
||||||
|
foreach (var item in _amiiboList[i].GamesSwitch)
|
||||||
|
{
|
||||||
|
if (item.GameId.Contains(TitleId))
|
||||||
|
{
|
||||||
|
foreach (AmiiboApiUsage usageItem in item.AmiiboUsage)
|
||||||
|
{
|
||||||
|
usageString += Environment.NewLine + $"- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}";
|
||||||
|
|
||||||
|
writable = usageItem.Write;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usageString.Length == 0)
|
||||||
|
{
|
||||||
|
usageString = "Unknown.";
|
||||||
|
}
|
||||||
|
|
||||||
|
_gameUsageLabel.Text = $"Usage{(writable ? " (Writable)" : "")} : {usageString}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = UpdateAmiiboPreview(imageUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowAllCheckBox_Clicked(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
_amiiboImage.Pixbuf = new Gdk.Pixbuf(_amiiboLogoBytes);
|
||||||
|
|
||||||
|
_amiiboSeriesComboBox.Changed -= SeriesComboBox_Changed;
|
||||||
|
_amiiboCharsComboBox.Changed -= CharacterComboBox_Changed;
|
||||||
|
|
||||||
|
_amiiboSeriesComboBox.RemoveAll();
|
||||||
|
_amiiboCharsComboBox.RemoveAll();
|
||||||
|
|
||||||
|
_scanButton.Sensitive = false;
|
||||||
|
_randomUuidCheckBox.Sensitive = false;
|
||||||
|
|
||||||
|
new Task(() => ParseAmiiboData()).Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ScanButton_Pressed(object sender, EventArgs args)
|
||||||
|
{
|
||||||
|
LastScannedAmiiboShowAll = _showAllCheckBox.Active;
|
||||||
|
|
||||||
|
Response = ResponseType.Ok;
|
||||||
|
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CancelButton_Pressed(object sender, EventArgs args)
|
||||||
|
{
|
||||||
|
AmiiboId = "";
|
||||||
|
LastScannedAmiiboId = "";
|
||||||
|
LastScannedAmiiboShowAll = false;
|
||||||
|
|
||||||
|
Response = ResponseType.Cancel;
|
||||||
|
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
_httpClient.Dispose();
|
||||||
|
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue