Files
Kenji-NX/src/Ryujinx.HLE/HOS/Services/Nfc/Nfp/NfpManager/INfp.cs
T

1087 lines
39 KiB
C#

using Ryujinx.Common.Memory;
using Ryujinx.Cpu;
using Ryujinx.HLE.Exceptions;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Hid;
using Ryujinx.HLE.HOS.Services.Hid.HidServer;
using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager;
using Ryujinx.Horizon.Common;
using Ryujinx.HLE.Kenjinx;
using System.Security.Cryptography;
using Ryujinx.Common.Logging;
using System;
using System.Buffers.Binary;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
{
class INfp : IpcService
{
#pragma warning disable IDE0052 // Remove unread private member
private ulong _appletResourceUserId;
private ulong _mcuVersionData;
#pragma warning restore IDE0052
private byte[] _mcuData;
private State _state = State.NonInitialized;
private KEvent _availabilityChangeEvent;
private CancellationTokenSource _cancelTokenSource;
private readonly NfpPermissionLevel _permissionLevel;
public INfp(NfpPermissionLevel permissionLevel)
{
_permissionLevel = permissionLevel;
}
[CommandCmif(0)]
// Initialize(u64, u64, pid, buffer<unknown, 5>)
public ResultCode Initialize(ServiceCtx context)
{
_appletResourceUserId = context.RequestData.ReadUInt64();
_mcuVersionData = context.RequestData.ReadUInt64();
ulong inputPosition = context.Request.SendBuff[0].Position;
ulong inputSize = context.Request.SendBuff[0].Size;
_mcuData = new byte[inputSize];
context.Memory.Read(inputPosition, _mcuData);
// TODO: The mcuData buffer seems to contains entries with a size of 0x40 bytes each. Usage of the data needs to be determined.
// TODO: Handle this in a controller class directly.
// Every functions which use the Handle call nn::hid::system::GetXcdHandleForNpadWithNfc().
NfpDevice devicePlayer1 = new()
{
NpadIdType = NpadIdType.Player1,
Handle = HidUtils.GetIndexFromNpadIdType(NpadIdType.Player1),
State = NfpDeviceState.Initialized,
};
context.Device.System.NfpDevices.Add(devicePlayer1);
// TODO: It mounts 0x8000000000000020 save data and stores a random generate value inside. Usage of the data needs to be determined.
_state = State.Initialized;
return ResultCode.Success;
}
[CommandCmif(1)]
// Finalize()
public ResultCode Finalize(ServiceCtx context)
{
if (_state == State.Initialized)
{
_cancelTokenSource?.Cancel();
// NOTE: All events are destroyed here.
context.Device.System.NfpDevices.Clear();
_state = State.NonInitialized;
}
return ResultCode.Success;
}
[CommandCmif(2)]
// ListDevices() -> (u32, buffer<unknown, 0xa>)
public ResultCode ListDevices(ServiceCtx context)
{
if (context.Request.RecvListBuff.Count == 0)
{
return ResultCode.WrongArgument;
}
ulong outputPosition = context.Request.RecvListBuff[0].Position;
ulong outputSize = context.Request.RecvListBuff[0].Size;
if (context.Device.System.NfpDevices.Count == 0)
{
return ResultCode.DeviceNotFound;
}
MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize);
if (CheckNfcIsEnabled() == ResultCode.Success)
{
for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
{
context.Memory.Write(outputPosition + ((uint)i * sizeof(long)), (uint)context.Device.System.NfpDevices[i].Handle);
}
context.ResponseData.Write(context.Device.System.NfpDevices.Count);
}
else
{
context.ResponseData.Write(0);
}
return ResultCode.Success;
}
[CommandCmif(3)]
// StartDetection(bytes<8, 4>)
public ResultCode StartDetection(ServiceCtx context)
{
ResultCode resultCode = CheckNfcIsEnabled();
if (resultCode != ResultCode.Success)
{
return resultCode;
}
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
{
if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle)
{
context.Device.System.NfpDevices[i].State = NfpDeviceState.SearchingForTag;
// ▼ If a tag has already been injected via Bridge, use it immediately.
if (KenjinxAmiiboShim.TryConsume(out byte[] tagBytes))
{
NfpDevice dev = context.Device.System.NfpDevices[i];
if (TryGetFigureIdFrom1Dc(tagBytes, out string fid, out string raw))
{
dev.AmiiboId = fid;
Logger.Info?.PrintMsg(LogClass.ServiceNfp, $"Kenjinx: raw@1DC={raw} → FigureID={fid}");
}
else
{
dev.AmiiboId = MakeAmiiboIdFromBytes(tagBytes); // Fallback (SHA1)
Logger.Info?.PrintMsg(LogClass.ServiceNfp, $"Kenjinx: FigureID={dev.AmiiboId} (fallback, keine 1DC-Bytes)");
}
dev.UseRandomUuid = false; // stabile UUID über die Session
dev.State = NfpDeviceState.TagFound;
}
// ▲
break;
}
}
_cancelTokenSource = new CancellationTokenSource();
Task.Run(() =>
{
while (true)
{
if (_cancelTokenSource.Token.IsCancellationRequested)
{
break;
}
// ▼ Poll during the search for newly injected tags.
for (int d = 0; d < context.Device.System.NfpDevices.Count; d++)
{
NfpDevice dev = context.Device.System.NfpDevices[d];
if (dev.State == NfpDeviceState.SearchingForTag && KenjinxAmiiboShim.TryConsume(out byte[] tagBytesLoop))
{
if (TryGetFigureIdFrom1Dc(tagBytesLoop, out string fid, out string raw))
{
dev.AmiiboId = fid;
Logger.Info?.PrintMsg(LogClass.ServiceNfp, $"Kenjinx: raw@1DC={raw} → FigureID={fid}");
}
else
{
dev.AmiiboId = MakeAmiiboIdFromBytes(tagBytesLoop); // Fallback (SHA1)
Logger.Info?.PrintMsg(LogClass.ServiceNfp, $"Kenjinx: FigureID={dev.AmiiboId} (fallback, keine 1DC-Bytes)");
}
dev.UseRandomUuid = false;
dev.State = NfpDeviceState.TagFound;
}
}
// ▲
for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
{
if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagFound)
{
context.Device.System.NfpDevices[i].SignalActivate();
Thread.Sleep(125); // NOTE: Simulate amiibo scanning delay.
context.Device.System.NfpDevices[i].SignalDeactivate();
break;
}
}
}
}, _cancelTokenSource.Token);
return ResultCode.Success;
}
[CommandCmif(4)]
// StopDetection(bytes<8, 4>)
public ResultCode StopDetection(ServiceCtx context)
{
ResultCode resultCode = CheckNfcIsEnabled();
if (resultCode != ResultCode.Success)
{
return resultCode;
}
_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;
}
}
// ▼ Discard the injected day
KenjinxAmiiboShim.Clear();
// ▲
return ResultCode.Success;
}
// ▼▼▼ FigureID directly from 0x1DC (8 bytes) without 16-bit swapping
private static bool TryGetFigureIdFrom1Dc(byte[] data, out string figureId, out string debugRaw)
{
figureId = string.Empty;
debugRaw = string.Empty;
const int off = 0x1DC;
const int len = 8;
if (data == null || data.Length < off + len)
{
return false;
}
Span<byte> s = data.AsSpan(off, len);
// Debug string "AA-BB-..." in uppercase letters
debugRaw = $"{s[0]:X2}-{s[1]:X2}-{s[2]:X2}-{s[3]:X2}-{s[4]:X2}-{s[5]:X2}-{s[6]:X2}-{s[7]:X2}";
// FigureID as a 16-digit lowercase hexadecimal string in the **same order**
// Layout: [0..1]=CharId(BE), [2]=Variant, [3]=Type, [4..5]=Model(BE), [6]=Series, [7]=unknown
figureId =
$"{s[0]:x2}{s[1]:x2}{s[2]:x2}{s[3]:x2}{s[4]:x2}{s[5]:x2}{s[6]:x2}{s[7]:x2}";
return true;
}
// ▲▲▲
// Fallback: Deterministic 16 hexadecimal characters from SHA1 (first 8 bytes)
private static string MakeAmiiboIdFromBytes(byte[] data)
{
if (data == null || data.Length == 0) return "0000000000000000";
using SHA1 sha = SHA1.Create();
byte[] hash = sha.ComputeHash(data); // 20 Bytes
char[] buf = new char[16];
for (int i = 0, k = 0; i < 8; i++)
{
byte b = hash[i];
buf[k++] = GetHex((b >> 4) & 0xF);
buf[k++] = GetHex(b & 0xF);
}
return new string(buf);
}
private static char GetHex(int v) => (char)(v < 10 ? ('0' + v) : ('a' + (v - 10)));
[CommandCmif(5)]
// Mount(bytes<8, 4>, u32, u32)
public ResultCode Mount(ServiceCtx context)
{
ResultCode resultCode = CheckNfcIsEnabled();
if (resultCode != ResultCode.Success)
{
return resultCode;
}
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
DeviceType deviceType = (DeviceType)context.RequestData.ReadUInt32();
MountTarget mountTarget = (MountTarget)context.RequestData.ReadUInt32();
if (deviceType != 0 || ((uint)mountTarget & 3) == 0)
{
return ResultCode.WrongArgument;
}
// TODO: Found how the MountTarget is handled.
for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
{
if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle)
{
if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved)
{
resultCode = ResultCode.TagNotFound;
}
else
{
if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagFound)
{
// NOTE: This mount the amiibo data, which isn't needed in our case.
context.Device.System.NfpDevices[i].State = NfpDeviceState.TagMounted;
resultCode = ResultCode.Success;
}
else
{
resultCode = ResultCode.WrongDeviceState;
}
}
break;
}
}
return resultCode;
}
[CommandCmif(6)]
// Unmount(bytes<8, 4>)
public ResultCode Unmount(ServiceCtx context)
{
ResultCode resultCode = CheckNfcIsEnabled();
if (resultCode != ResultCode.Success)
{
return resultCode;
}
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
if (context.Device.System.NfpDevices.Count == 0)
{
return ResultCode.DeviceNotFound;
}
for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
{
if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle)
{
if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved)
{
resultCode = ResultCode.TagNotFound;
}
else
{
// NOTE: This mount the amiibo data, which isn't needed in our case.
context.Device.System.NfpDevices[i].State = NfpDeviceState.TagFound;
resultCode = ResultCode.Success;
}
break;
}
}
return resultCode;
}
[CommandCmif(7)]
// OpenApplicationArea(bytes<8, 4>, u32)
public ResultCode OpenApplicationArea(ServiceCtx context)
{
ResultCode resultCode = CheckNfcIsEnabled();
if (resultCode != ResultCode.Success)
{
return resultCode;
}
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
if (context.Device.System.NfpDevices.Count == 0)
{
return ResultCode.DeviceNotFound;
}
uint applicationAreaId = context.RequestData.ReadUInt32();
bool isOpened = false;
for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
{
if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle)
{
if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved)
{
resultCode = ResultCode.TagNotFound;
}
else
{
if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted)
{
isOpened = VirtualAmiibo.OpenApplicationArea(context.Device.System.NfpDevices[i].AmiiboId, applicationAreaId);
resultCode = ResultCode.Success;
}
else
{
resultCode = ResultCode.WrongDeviceState;
}
}
break;
}
}
if (!isOpened)
{
resultCode = ResultCode.ApplicationAreaIsNull;
}
return resultCode;
}
[CommandCmif(8)]
// GetApplicationArea(bytes<8, 4>) -> (u32, buffer<unknown, 6>)
public ResultCode GetApplicationArea(ServiceCtx context)
{
ResultCode resultCode = CheckNfcIsEnabled();
if (resultCode != ResultCode.Success)
{
return resultCode;
}
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
if (context.Device.System.NfpDevices.Count == 0)
{
return ResultCode.DeviceNotFound;
}
ulong outputPosition = context.Request.ReceiveBuff[0].Position;
ulong outputSize = context.Request.ReceiveBuff[0].Size;
MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize);
uint size = 0;
for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
{
if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle)
{
if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved)
{
resultCode = ResultCode.TagNotFound;
}
else
{
if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted)
{
byte[] applicationArea = VirtualAmiibo.GetApplicationArea(context.Device.System.NfpDevices[i].AmiiboId);
context.Memory.Write(outputPosition, applicationArea);
size = (uint)applicationArea.Length;
resultCode = ResultCode.Success;
}
else
{
resultCode = ResultCode.WrongDeviceState;
}
}
}
}
if (resultCode != ResultCode.Success)
{
return resultCode;
}
if (size == 0)
{
return ResultCode.ApplicationAreaIsNull;
}
context.ResponseData.Write(size);
return ResultCode.Success;
}
[CommandCmif(9)]
// SetApplicationArea(bytes<8, 4>, buffer<unknown, 5>)
public ResultCode SetApplicationArea(ServiceCtx context)
{
ResultCode resultCode = CheckNfcIsEnabled();
if (resultCode != ResultCode.Success)
{
return resultCode;
}
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
if (context.Device.System.NfpDevices.Count == 0)
{
return ResultCode.DeviceNotFound;
}
ulong inputPosition = context.Request.SendBuff[0].Position;
ulong inputSize = context.Request.SendBuff[0].Size;
byte[] applicationArea = new byte[inputSize];
context.Memory.Read(inputPosition, applicationArea);
for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
{
if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle)
{
if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved)
{
resultCode = ResultCode.TagNotFound;
}
else
{
if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted)
{
VirtualAmiibo.SetApplicationArea(context.Device.System.NfpDevices[i].AmiiboId, applicationArea);
resultCode = ResultCode.Success;
}
else
{
resultCode = ResultCode.WrongDeviceState;
}
}
break;
}
}
return resultCode;
}
[CommandCmif(10)]
// Flush(bytes<8, 4>)
public ResultCode Flush(ServiceCtx context)
{
#pragma warning disable IDE0059 // Remove unnecessary value assignment
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
#pragma warning restore IDE0059
if (context.Device.System.NfpDevices.Count == 0)
{
return ResultCode.DeviceNotFound;
}
// NOTE: Since we handle amiibo through VirtualAmiibo, we don't have to flush anything in our case.
return ResultCode.Success;
}
[CommandCmif(11)]
// Restore(bytes<8, 4>)
public ResultCode Restore(ServiceCtx context)
{
throw new ServiceNotImplementedException(this, context);
}
[CommandCmif(12)]
// CreateApplicationArea(bytes<8, 4>, u32, buffer<unknown, 5>)
public ResultCode CreateApplicationArea(ServiceCtx context)
{
ResultCode resultCode = CheckNfcIsEnabled();
if (resultCode != ResultCode.Success)
{
return resultCode;
}
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
if (context.Device.System.NfpDevices.Count == 0)
{
return ResultCode.DeviceNotFound;
}
uint applicationAreaId = context.RequestData.ReadUInt32();
ulong inputPosition = context.Request.SendBuff[0].Position;
ulong inputSize = context.Request.SendBuff[0].Size;
byte[] applicationArea = new byte[inputSize];
context.Memory.Read(inputPosition, applicationArea);
bool isCreated = false;
for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
{
if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle)
{
if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved)
{
resultCode = ResultCode.TagNotFound;
}
else
{
if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted)
{
isCreated = VirtualAmiibo.CreateApplicationArea(context.Device.System.NfpDevices[i].AmiiboId, applicationAreaId, applicationArea);
resultCode = ResultCode.Success;
}
else
{
resultCode = ResultCode.WrongDeviceState;
}
}
break;
}
}
if (!isCreated)
{
resultCode = ResultCode.ApplicationAreaIsNull;
}
return resultCode;
}
[CommandCmif(13)]
// GetTagInfo(bytes<8, 4>) -> buffer<unknown<0x58>, 0x1a>
public ResultCode GetTagInfo(ServiceCtx context)
{
ResultCode resultCode = CheckNfcIsEnabled();
if (resultCode != ResultCode.Success)
{
return resultCode;
}
if (context.Request.RecvListBuff.Count == 0)
{
return ResultCode.WrongArgument;
}
ulong outputPosition = context.Request.RecvListBuff[0].Position;
context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize((uint)Marshal.SizeOf<TagInfo>());
MemoryHelper.FillWithZeros(context.Memory, outputPosition, Marshal.SizeOf<TagInfo>());
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
if (context.Device.System.NfpDevices.Count == 0)
{
return ResultCode.DeviceNotFound;
}
for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
{
if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle)
{
if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved)
{
resultCode = ResultCode.TagNotFound;
}
else
{
if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted || context.Device.System.NfpDevices[i].State == NfpDeviceState.TagFound)
{
byte[] uuid = VirtualAmiibo.GenerateUuid(context.Device.System.NfpDevices[i].AmiiboId, context.Device.System.NfpDevices[i].UseRandomUuid);
if (uuid.Length > AmiiboConstants.UuidMaxLength)
{
throw new InvalidOperationException($"{nameof(uuid)} is too long: {uuid.Length}");
}
TagInfo tagInfo = new()
{
UuidLength = (byte)uuid.Length,
Reserved1 = new Array21<byte>(),
Protocol = uint.MaxValue, // All Protocol
TagType = uint.MaxValue, // All Type
Reserved2 = new Array6<byte>(),
};
uuid.CopyTo(tagInfo.Uuid.AsSpan());
context.Memory.Write(outputPosition, tagInfo);
resultCode = ResultCode.Success;
}
else
{
resultCode = ResultCode.WrongDeviceState;
}
}
break;
}
}
return resultCode;
}
[CommandCmif(14)]
// GetRegisterInfo(bytes<8, 4>) -> buffer<unknown<0x100>, 0x1a>
public ResultCode GetRegisterInfo(ServiceCtx context)
{
ResultCode resultCode = CheckNfcIsEnabled();
if (resultCode != ResultCode.Success)
{
return resultCode;
}
if (context.Request.RecvListBuff.Count == 0)
{
return ResultCode.WrongArgument;
}
ulong outputPosition = context.Request.RecvListBuff[0].Position;
context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize((uint)Marshal.SizeOf<RegisterInfo>());
MemoryHelper.FillWithZeros(context.Memory, outputPosition, Marshal.SizeOf<RegisterInfo>());
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
if (context.Device.System.NfpDevices.Count == 0)
{
return ResultCode.DeviceNotFound;
}
for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
{
if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle)
{
if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved)
{
resultCode = ResultCode.TagNotFound;
}
else
{
if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted)
{
RegisterInfo registerInfo = VirtualAmiibo.GetRegisterInfo(
context.Device.System.TickSource,
context.Device.System.NfpDevices[i].AmiiboId,
context.Device.System.AccountManager.LastOpenedUser.Name);
context.Memory.Write(outputPosition, registerInfo);
resultCode = ResultCode.Success;
}
else
{
resultCode = ResultCode.WrongDeviceState;
}
}
break;
}
}
return resultCode;
}
[CommandCmif(15)]
// GetCommonInfo(bytes<8, 4>) -> buffer<unknown<0x40>, 0x1a>
public ResultCode GetCommonInfo(ServiceCtx context)
{
ResultCode resultCode = CheckNfcIsEnabled();
if (resultCode != ResultCode.Success)
{
return resultCode;
}
if (context.Request.RecvListBuff.Count == 0)
{
return ResultCode.WrongArgument;
}
ulong outputPosition = context.Request.RecvListBuff[0].Position;
context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize((uint)Marshal.SizeOf<CommonInfo>());
MemoryHelper.FillWithZeros(context.Memory, outputPosition, Marshal.SizeOf<CommonInfo>());
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
if (context.Device.System.NfpDevices.Count == 0)
{
return ResultCode.DeviceNotFound;
}
for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
{
if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle)
{
if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved)
{
resultCode = ResultCode.TagNotFound;
}
else
{
if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted)
{
CommonInfo commonInfo = VirtualAmiibo.GetCommonInfo(context.Device.System.NfpDevices[i].AmiiboId);
context.Memory.Write(outputPosition, commonInfo);
resultCode = ResultCode.Success;
}
else
{
resultCode = ResultCode.WrongDeviceState;
}
}
break;
}
}
return resultCode;
}
[CommandCmif(16)]
// GetModelInfo(bytes<8, 4>) -> buffer<unknown<0x40>, 0x1a>
public ResultCode GetModelInfo(ServiceCtx context)
{
ResultCode resultCode = CheckNfcIsEnabled();
if (resultCode != ResultCode.Success)
{
return resultCode;
}
if (context.Request.RecvListBuff.Count == 0)
{
return ResultCode.WrongArgument;
}
ulong outputPosition = context.Request.RecvListBuff[0].Position;
context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize((uint)Marshal.SizeOf<ModelInfo>());
MemoryHelper.FillWithZeros(context.Memory, outputPosition, Marshal.SizeOf<ModelInfo>());
uint deviceHandle = (uint)context.RequestData.ReadUInt64();
if (context.Device.System.NfpDevices.Count == 0)
{
return ResultCode.DeviceNotFound;
}
for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
{
if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle)
{
if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved)
{
resultCode = ResultCode.TagNotFound;
}
else
{
if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted)
{
ModelInfo modelInfo = new()
{
Reserved = new Array57<byte>(),
CharacterId = BinaryPrimitives.ReverseEndianness(ushort.Parse(context.Device.System.NfpDevices[i].AmiiboId.AsSpan(0, 4), NumberStyles.HexNumber)),
CharacterVariant = byte.Parse(context.Device.System.NfpDevices[i].AmiiboId.AsSpan(4, 2), NumberStyles.HexNumber),
Series = byte.Parse(context.Device.System.NfpDevices[i].AmiiboId.AsSpan(12, 2), NumberStyles.HexNumber),
ModelNumber = ushort.Parse(context.Device.System.NfpDevices[i].AmiiboId.AsSpan(8, 4), NumberStyles.HexNumber),
Type = byte.Parse(context.Device.System.NfpDevices[i].AmiiboId.AsSpan(6, 2), NumberStyles.HexNumber),
};
context.Memory.Write(outputPosition, modelInfo);
resultCode = ResultCode.Success;
}
else
{
resultCode = ResultCode.WrongDeviceState;
}
}
break;
}
}
return resultCode;
}
[CommandCmif(17)]
// AttachActivateEvent(bytes<8, 4>) -> handle<copy>
public ResultCode AttachActivateEvent(ServiceCtx context)
{
uint deviceHandle = context.RequestData.ReadUInt32();
for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
{
if ((uint)context.Device.System.NfpDevices[i].Handle == deviceHandle)
{
context.Device.System.NfpDevices[i].ActivateEvent = new KEvent(context.Device.System.KernelContext);
if (context.Process.HandleTable.GenerateHandle(context.Device.System.NfpDevices[i].ActivateEvent.ReadableEvent, out int activateEventHandle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(activateEventHandle);
return ResultCode.Success;
}
}
return ResultCode.DeviceNotFound;
}
[CommandCmif(18)]
// AttachDeactivateEvent(bytes<8, 4>) -> handle<copy>
public ResultCode AttachDeactivateEvent(ServiceCtx context)
{
uint deviceHandle = context.RequestData.ReadUInt32();
for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
{
if ((uint)context.Device.System.NfpDevices[i].Handle == deviceHandle)
{
context.Device.System.NfpDevices[i].DeactivateEvent = new KEvent(context.Device.System.KernelContext);
if (context.Process.HandleTable.GenerateHandle(context.Device.System.NfpDevices[i].DeactivateEvent.ReadableEvent, out int deactivateEventHandle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(deactivateEventHandle);
return ResultCode.Success;
}
}
return ResultCode.DeviceNotFound;
}
[CommandCmif(19)]
// GetState() -> u32
public ResultCode GetState(ServiceCtx context)
{
context.ResponseData.Write((int)_state);
return ResultCode.Success;
}
[CommandCmif(20)]
// GetDeviceState(bytes<8, 4>) -> u32
public ResultCode GetDeviceState(ServiceCtx context)
{
uint deviceHandle = context.RequestData.ReadUInt32();
for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
{
if ((uint)context.Device.System.NfpDevices[i].Handle == deviceHandle)
{
if (context.Device.System.NfpDevices[i].State > NfpDeviceState.Finalized)
{
throw new InvalidOperationException($"{nameof(context.Device.System.NfpDevices)} contains an invalid state for device {i}: {context.Device.System.NfpDevices[i].State}");
}
context.ResponseData.Write((uint)context.Device.System.NfpDevices[i].State);
return ResultCode.Success;
}
}
context.ResponseData.Write((uint)NfpDeviceState.Unavailable);
return ResultCode.DeviceNotFound;
}
[CommandCmif(21)]
// GetNpadId(bytes<8, 4>) -> u32
public ResultCode GetNpadId(ServiceCtx context)
{
uint deviceHandle = context.RequestData.ReadUInt32();
for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
{
if ((uint)context.Device.System.NfpDevices[i].Handle == deviceHandle)
{
context.ResponseData.Write((uint)HidUtils.GetNpadIdTypeFromIndex(context.Device.System.NfpDevices[i].Handle));
return ResultCode.Success;
}
}
return ResultCode.DeviceNotFound;
}
[CommandCmif(22)]
// GetApplicationAreaSize() -> u32
public ResultCode GetApplicationAreaSize(ServiceCtx context)
{
context.ResponseData.Write(AmiiboConstants.ApplicationAreaSize);
return ResultCode.Success;
}
[CommandCmif(23)] // 3.0.0+
// AttachAvailabilityChangeEvent() -> handle<copy>
public ResultCode AttachAvailabilityChangeEvent(ServiceCtx context)
{
_availabilityChangeEvent = new KEvent(context.Device.System.KernelContext);
if (context.Process.HandleTable.GenerateHandle(_availabilityChangeEvent.ReadableEvent, out int availabilityChangeEventHandle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(availabilityChangeEventHandle);
return ResultCode.Success;
}
[CommandCmif(24)] // 3.0.0+
// RecreateApplicationArea(bytes<8, 4>, u32, buffer<unknown, 5>)
public ResultCode RecreateApplicationArea(ServiceCtx context)
{
throw new ServiceNotImplementedException(this, context);
}
[CommandCmif(102)]
// GetRegisterInfo2(bytes<8, 4>) -> buffer<unknown<0x100>, 0x1a>
public ResultCode GetRegisterInfo2(ServiceCtx context)
{
// TODO: Find the differencies between IUser and ISystem/IDebug.
if (_permissionLevel is NfpPermissionLevel.Debug or NfpPermissionLevel.System)
{
return GetRegisterInfo(context);
}
return ResultCode.DeviceNotFound;
}
private ResultCode CheckNfcIsEnabled()
{
// TODO: Call nn::settings::detail::GetNfcEnableFlag when it will be implemented.
return true ? ResultCode.Success : ResultCode.NfcDisabled;
}
}
}