From a56423802cd6e74809c1cf5d93b51fdf11f07bef Mon Sep 17 00:00:00 2001
From: Ac_K <Acoustik666@gmail.com>
Date: Thu, 18 Mar 2021 21:40:20 +0100
Subject: [PATCH] nfp: Amiibo scanning support (#2006)

* Initial Impl.

* You just want me cause I'm next

* Fix some logics

* Fix close button
---
 Ryujinx.HLE/HOS/Horizon.cs                    |  33 +
 .../HOS/Services/Mii/Types/CoreData.cs        |  14 +-
 .../HOS/Services/Nfc/Nfp/ResultCode.cs        |   9 +-
 .../HOS/Services/Nfc/Nfp/UserManager/IUser.cs | 800 ++++++++++++++++--
 .../Nfp/UserManager/Types/AmiiboConstants.cs  |   8 +
 .../Nfc/Nfp/UserManager/Types/CommonInfo.cs   |  17 +
 .../Nfc/Nfp/UserManager/Types/Device.cs       |  19 -
 .../Nfc/Nfp/UserManager/Types/DeviceType.cs   |   7 +
 .../Nfc/Nfp/UserManager/Types/ModelInfo.cs    |  16 +
 .../Nfc/Nfp/UserManager/Types/MountTarget.cs  |   9 +
 .../Nfc/Nfp/UserManager/Types/NfpDevice.cs    |  23 +
 .../{DeviceState.cs => NfpDeviceState.cs}     |   2 +-
 .../Nfc/Nfp/UserManager/Types/RegisterInfo.cs |  19 +
 .../Nfc/Nfp/UserManager/Types/TagInfo.cs      |  16 +
 .../UserManager/Types/VirtualAmiiboFile.cs    |  22 +
 .../HOS/Services/Nfc/Nfp/VirtualAmiibo.cs     | 205 +++++
 Ryujinx/Ryujinx.csproj                        |   2 +
 Ryujinx/Ui/MainWindow.cs                      |  59 +-
 Ryujinx/Ui/MainWindow.glade                   |  78 +-
 Ryujinx/Ui/Resources/Logo_Amiibo.png          | Bin 0 -> 11676 bytes
 Ryujinx/Ui/Windows/AmiiboWindow.Designer.cs   | 194 +++++
 Ryujinx/Ui/Windows/AmiiboWindow.cs            | 422 +++++++++
 22 files changed, 1830 insertions(+), 144 deletions(-)
 create mode 100644 Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/AmiiboConstants.cs
 create mode 100644 Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/CommonInfo.cs
 delete mode 100644 Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/Device.cs
 create mode 100644 Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/DeviceType.cs
 create mode 100644 Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/ModelInfo.cs
 create mode 100644 Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/MountTarget.cs
 create mode 100644 Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/NfpDevice.cs
 rename Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/{DeviceState.cs => NfpDeviceState.cs} (91%)
 create mode 100644 Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/RegisterInfo.cs
 create mode 100644 Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/TagInfo.cs
 create mode 100644 Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/VirtualAmiiboFile.cs
 create mode 100644 Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs
 create mode 100644 Ryujinx/Ui/Resources/Logo_Amiibo.png
 create mode 100644 Ryujinx/Ui/Windows/AmiiboWindow.Designer.cs
 create mode 100644 Ryujinx/Ui/Windows/AmiiboWindow.cs

diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs
index 16b4c3760f..4da147bf58 100644
--- a/Ryujinx.HLE/HOS/Horizon.cs
+++ b/Ryujinx.HLE/HOS/Horizon.cs
@@ -22,6 +22,7 @@ using Ryujinx.HLE.HOS.Services.Apm;
 using Ryujinx.HLE.HOS.Services.Arp;
 using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
 using Ryujinx.HLE.HOS.Services.Mii;
+using Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager;
 using Ryujinx.HLE.HOS.Services.Nv;
 using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrl;
 using Ryujinx.HLE.HOS.Services.Pcv.Bpc;
@@ -33,6 +34,7 @@ using Ryujinx.HLE.HOS.SystemState;
 using Ryujinx.HLE.Loaders.Executables;
 using Ryujinx.HLE.Utilities;
 using System;
+using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 using System.Threading;
@@ -65,6 +67,8 @@ namespace Ryujinx.HLE.HOS
 
         internal AppletStateMgr AppletState { get; private set; }
 
+        internal List<NfpDevice> NfpDevices { get; private set; }
+
         internal ServerBase BsdServer { get; private set; }
         internal ServerBase AudRenServer { get; private set; }
         internal ServerBase AudOutServer { get; private set; }
@@ -113,6 +117,8 @@ namespace Ryujinx.HLE.HOS
 
             PerformanceState = new PerformanceState();
 
+            NfpDevices = new List<NfpDevice>();
+
             // Note: This is not really correct, but with HLE of services, the only memory
             // region used that is used is Application, so we can use the other ones for anything.
             KMemoryRegionManager region = KernelContext.MemoryRegions[(int)MemoryRegion.NvServices];
@@ -320,6 +326,33 @@ namespace Ryujinx.HLE.HOS
             AppletState.MessageEvent.ReadableEvent.Signal();
         }
 
+        public void ScanAmiibo(int nfpDeviceId, string amiiboId, bool useRandomUuid)
+        {
+            if (NfpDevices[nfpDeviceId].State == NfpDeviceState.SearchingForTag)
+            {
+                NfpDevices[nfpDeviceId].State         = NfpDeviceState.TagFound;
+                NfpDevices[nfpDeviceId].AmiiboId      = amiiboId;
+                NfpDevices[nfpDeviceId].UseRandomUuid = useRandomUuid;
+            }
+        }
+
+        public bool SearchingForAmiibo(out int nfpDeviceId)
+        {
+            nfpDeviceId = default;
+
+            for (int i = 0; i < NfpDevices.Count; i++)
+            {
+                if (NfpDevices[i].State == NfpDeviceState.SearchingForTag)
+                {
+                    nfpDeviceId = i;
+
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
         public void SignalDisplayResolutionChange()
         {
             DisplayResolutionChangeEvent.ReadableEvent.Signal();
diff --git a/Ryujinx.HLE/HOS/Services/Mii/Types/CoreData.cs b/Ryujinx.HLE/HOS/Services/Mii/Types/CoreData.cs
index 1b11f99deb..39a3945b0e 100644
--- a/Ryujinx.HLE/HOS/Services/Mii/Types/CoreData.cs
+++ b/Ryujinx.HLE/HOS/Services/Mii/Types/CoreData.cs
@@ -389,7 +389,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types
 
             coreData.SetDefault();
 
-            if (gender == Types.Gender.All)
+            if (gender == Gender.All)
             {
                 gender = (Gender)utilImpl.GetRandom((int)gender);
             }
@@ -432,7 +432,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types
 
             int axisY = 0;
 
-            if (gender == Types.Gender.Female && age == Age.Young)
+            if (gender == Gender.Female && age == Age.Young)
             {
                 axisY = utilImpl.GetRandom(3);
             }
@@ -466,8 +466,8 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types
             // Eye
             coreData.EyeType = (EyeType)eyeTypeInfo.Values[utilImpl.GetRandom(eyeTypeInfo.ValuesCount)];
 
-            int eyeRotateKey1 = gender != Types.Gender.Male ? 4 : 2;
-            int eyeRotateKey2 = gender != Types.Gender.Male ? 3 : 4;
+            int eyeRotateKey1 = gender != Gender.Male ? 4 : 2;
+            int eyeRotateKey2 = gender != Gender.Male ? 3 : 4;
 
             byte eyeRotateOffset = (byte)(32 - EyeRotateTable[eyeRotateKey1] + eyeRotateKey2);
             byte eyeRotate       = (byte)(32 - EyeRotateTable[(int)coreData.EyeType]);
@@ -496,14 +496,14 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types
             coreData.EyebrowY      = (byte)(axisY + eyebrowY);
 
             // Nose
-            int noseScale = gender == Types.Gender.Female ? 3 : 4;
+            int noseScale = gender == Gender.Female ? 3 : 4;
 
             coreData.NoseType  = (NoseType)noseTypeInfo.Values[utilImpl.GetRandom(noseTypeInfo.ValuesCount)];
             coreData.NoseScale = (byte)noseScale;
             coreData.NoseY     = (byte)(axisY + 9);
 
             // Mouth
-            int mouthColor = gender == Types.Gender.Female ? utilImpl.GetRandom(0, 4) : 0;
+            int mouthColor = gender == Gender.Female ? utilImpl.GetRandom(0, 4) : 0;
 
             coreData.MouthType   = (MouthType)mouthTypeInfo.Values[utilImpl.GetRandom(mouthTypeInfo.ValuesCount)];
             coreData.MouthColor  = (CommonColor)Helper.Ver3MouthColorTable[mouthColor];
@@ -515,7 +515,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii.Types
             coreData.BeardColor    = coreData.HairColor;
             coreData.MustacheScale = 4;
 
-            if (gender == Types.Gender.Male && age != Age.Young && utilImpl.GetRandom(10) < 2)
+            if (gender == Gender.Male && age != Age.Young && utilImpl.GetRandom(10) < 2)
             {
                 BeardAndMustacheFlag mustacheAndBeardFlag = (BeardAndMustacheFlag)utilImpl.GetRandom(3);
 
diff --git a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ResultCode.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ResultCode.cs
index b42a28a91b..e0ccbc6d6b 100644
--- a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ResultCode.cs
+++ b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/ResultCode.cs
@@ -7,7 +7,12 @@
 
         Success = 0,
 
-        DeviceNotFound      = (64 << ErrorCodeShift) | ModuleId,
-        DevicesBufferIsNull = (65 << ErrorCodeShift) | ModuleId
+        DeviceNotFound                = (64  << ErrorCodeShift) | ModuleId,
+        WrongArgument                 = (65  << ErrorCodeShift) | ModuleId,
+        WrongDeviceState              = (73  << ErrorCodeShift) | ModuleId,
+        NfcDisabled                   = (80  << ErrorCodeShift) | ModuleId,
+        TagNotFound                   = (97  << ErrorCodeShift) | ModuleId,
+        ApplicationAreaIsNull         = (128 << ErrorCodeShift) | ModuleId,
+        ApplicationAreaAlreadyCreated = (168 << ErrorCodeShift) | ModuleId
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/IUser.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/IUser.cs
index 2cd35b9ed2..908815656a 100644
--- a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/IUser.cs
+++ b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/IUser.cs
@@ -1,4 +1,6 @@
-using Ryujinx.HLE.Exceptions;
+using Ryujinx.Common.Memory;
+using Ryujinx.Cpu;
+using Ryujinx.HLE.Exceptions;
 using Ryujinx.HLE.HOS.Ipc;
 using Ryujinx.HLE.HOS.Kernel.Common;
 using Ryujinx.HLE.HOS.Kernel.Threading;
@@ -6,18 +8,25 @@ using Ryujinx.HLE.HOS.Services.Hid;
 using Ryujinx.HLE.HOS.Services.Hid.HidServer;
 using Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager;
 using System;
-using System.Collections.Generic;
+using System.Buffers.Binary;
+using System.Globalization;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
 
 namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
 {
     class IUser : IpcService
     {
+        private ulong  _appletResourceUserId;
+        private ulong  _mcuVersionData;
+        private byte[] _mcuData;
+
         private State _state = State.NonInitialized;
 
         private KEvent _availabilityChangeEvent;
-        private int    _availabilityChangeEventHandle = 0;
 
-        private List<Device> _devices = new List<Device>();
+        private CancellationTokenSource _cancelTokenSource;
 
         public IUser() { }
 
@@ -25,32 +34,30 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
         // Initialize(u64, u64, pid, buffer<unknown, 5>)
         public ResultCode Initialize(ServiceCtx context)
         {
-            long appletResourceUserId = context.RequestData.ReadInt64();
-            long mcuVersionData       = context.RequestData.ReadInt64();
+            _appletResourceUserId = context.RequestData.ReadUInt64();
+            _mcuVersionData       = context.RequestData.ReadUInt64();
 
             long inputPosition = context.Request.SendBuff[0].Position;
             long inputSize     = context.Request.SendBuff[0].Size;
 
-            byte[] unknownBuffer = new byte[inputSize];
+            _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.
-            //       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: The mcuData buffer seems to contains entries with a size of 0x40 bytes each. Usage of the data needs to be determined.
 
-            // TODO: Add an instance of nn::nfc::server::Manager when it will be implemented.
-            //       Add an instance of nn::nfc::server::SaveData when it will be implemented.
-
-            // TODO: When we will be able to add multiple controllers add one entry by controller here.
-            Device device1 = new Device
+            // TODO: Handle this in a controller class directly.
+            //       Every functions which use the Handle call nn::hid::system::GetXcdHandleForNpadWithNfc().
+            NfpDevice devicePlayer1 = new NfpDevice
             {
                 NpadIdType = NpadIdType.Player1,
                 Handle     = HidUtils.GetIndexFromNpadIdType(NpadIdType.Player1),
-                State      = DeviceState.Initialized
+                State      = NfpDeviceState.Initialized
             };
 
-            _devices.Add(device1);
+            context.Device.System.NfpDevices.Add(devicePlayer1);
+
+            // TODO: It mounts 0x8000000000000020 save data and stores a random generate value inside. Usage of the data needs to be determined.
 
             _state = State.Initialized;
 
@@ -61,13 +68,18 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
         // Finalize()
         public ResultCode Finalize(ServiceCtx context)
         {
-            // TODO: Call StopDetection() and Unmount() when they will be implemented.
-            //       Remove the instance of nn::nfc::server::Manager when it will be implemented.
-            //       Remove the instance of nn::nfc::server::SaveData when it will be implemented.
+            if (_state == State.Initialized)
+            {
+                if (_cancelTokenSource != null)
+                {
+                    _cancelTokenSource.Cancel();
+                }
 
-            _devices.Clear();
+                // NOTE: All events are destroyed here.
+                context.Device.System.NfpDevices.Clear();
 
-            _state = State.NonInitialized;
+                _state = State.NonInitialized;
+            }
 
             return ResultCode.Success;
         }
@@ -78,23 +90,32 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
         {
             if (context.Request.RecvListBuff.Count == 0)
             {
-                return ResultCode.DevicesBufferIsNull;
+                return ResultCode.WrongArgument;
             }
 
             long outputPosition = context.Request.RecvListBuff[0].Position;
-            long outputSize     = context.Request.RecvListBuff[0].Size;
+            long outputSize      = context.Request.RecvListBuff[0].Size;
 
-            if (_devices.Count == 0)
+            if (context.Device.System.NfpDevices.Count == 0)
             {
                 return ResultCode.DeviceNotFound;
             }
 
-            for (int i = 0; i < _devices.Count; i++)
-            {
-                context.Memory.Write((ulong)(outputPosition + (i * sizeof(long))), (uint)_devices[i].Handle);
-            }
+            MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize);
 
-            context.ResponseData.Write(_devices.Count);
+            if (CheckNfcIsEnabled() == ResultCode.Success)
+            {
+                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(context.Device.System.NfpDevices.Count);
+            }
+            else
+            {
+                context.ResponseData.Write(0);
+            }
 
             return ResultCode.Success;
         }
@@ -103,56 +124,376 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
         // StartDetection(bytes<8, 4>)
         public ResultCode StartDetection(ServiceCtx context)
         {
-            throw new ServiceNotImplementedException(this, context);
+            ResultCode resultCode = CheckNfcIsEnabled();
+
+            if (resultCode != ResultCode.Success)
+            {
+                return resultCode;
+            }
+
+            uint deviceHandle = (uint)context.RequestData.ReadUInt64();
+
+            for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
+            {
+                if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle)
+                {
+                    context.Device.System.NfpDevices[i].State = NfpDeviceState.SearchingForTag;
+
+                    break;
+                }
+            }
+
+            _cancelTokenSource = new CancellationTokenSource();
+
+            Task.Run(() =>
+            {
+                while (true)
+                {
+                    if (_cancelTokenSource.Token.IsCancellationRequested)
+                    {
+                        break;
+                    }
+
+                    for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
+                    {
+                        if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagFound)
+                        {
+                            context.Device.System.NfpDevices[i].SignalActivate();
+                            Thread.Sleep(50); // NOTE: Simulate amiibo scanning delay.
+                            context.Device.System.NfpDevices[i].SignalDeactivate();
+
+                            break;
+                        }
+                    }
+                }
+            }, _cancelTokenSource.Token);
+
+            return ResultCode.Success;
         }
 
         [Command(4)]
         // StopDetection(bytes<8, 4>)
         public ResultCode StopDetection(ServiceCtx context)
         {
-            throw new ServiceNotImplementedException(this, context);
+            ResultCode resultCode = CheckNfcIsEnabled();
+
+            if (resultCode != ResultCode.Success)
+            {
+                return resultCode;
+            }
+
+            if (_cancelTokenSource != null)
+            {
+                _cancelTokenSource.Cancel();
+            }
+
+            uint deviceHandle = (uint)context.RequestData.ReadUInt64();
+
+            for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
+            {
+                if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle)
+                {
+                    context.Device.System.NfpDevices[i].State = NfpDeviceState.Initialized;
+
+                    break;
+                }
+            }
+
+            return ResultCode.Success;
         }
 
         [Command(5)]
         // Mount(bytes<8, 4>, u32, u32)
         public ResultCode Mount(ServiceCtx context)
         {
-            throw new ServiceNotImplementedException(this, context);
+            ResultCode resultCode = CheckNfcIsEnabled();
+
+            if (resultCode != ResultCode.Success)
+            {
+                return resultCode;
+            }
+
+            uint                   deviceHandle = (uint)context.RequestData.ReadUInt64();
+            UserManager.DeviceType deviceType   = (UserManager.DeviceType)context.RequestData.ReadUInt32();
+            MountTarget            mountTarget  = (MountTarget)context.RequestData.ReadUInt32();
+
+            if (deviceType != 0)
+            {
+                return ResultCode.WrongArgument;
+            }
+
+            if (((uint)mountTarget & 3) == 0)
+            {
+                return ResultCode.WrongArgument;
+            }
+
+            // TODO: Found how the MountTarget is handled.
+
+            for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
+            {
+                if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle)
+                {
+                    if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved)
+                    {
+                        resultCode = ResultCode.TagNotFound;
+                    }
+                    else
+                    {
+                        if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagFound)
+                        {
+                            // NOTE: This mount the amiibo data, which isn't needed in our case.
+
+                            context.Device.System.NfpDevices[i].State = NfpDeviceState.TagMounted;
+
+                            resultCode = ResultCode.Success;
+                        }
+                        else
+                        {
+                            resultCode = ResultCode.WrongDeviceState;
+                        }
+                    }
+
+                    break;
+                }
+            }
+
+            return resultCode;
         }
 
         [Command(6)]
         // Unmount(bytes<8, 4>)
         public ResultCode Unmount(ServiceCtx context)
         {
-            throw new ServiceNotImplementedException(this, context);
+            ResultCode resultCode = CheckNfcIsEnabled();
+
+            if (resultCode != ResultCode.Success)
+            {
+                return resultCode;
+            }
+
+            uint deviceHandle = (uint)context.RequestData.ReadUInt64();
+
+            if (context.Device.System.NfpDevices.Count == 0)
+            {
+                return ResultCode.DeviceNotFound;
+            }
+
+            for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
+            {
+                if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle)
+                {
+                    if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved)
+                    {
+                        resultCode = ResultCode.TagNotFound;
+                    }
+                    else
+                    {
+                        // NOTE: This mount the amiibo data, which isn't needed in our case.
+
+                        context.Device.System.NfpDevices[i].State = NfpDeviceState.TagFound;
+
+                        resultCode = ResultCode.Success;
+                    }
+
+                    break;
+                }
+            }
+
+            return resultCode;
         }
 
         [Command(7)]
         // OpenApplicationArea(bytes<8, 4>, u32)
         public ResultCode OpenApplicationArea(ServiceCtx context)
         {
-            throw new ServiceNotImplementedException(this, context);
+            ResultCode resultCode = CheckNfcIsEnabled();
+
+            if (resultCode != ResultCode.Success)
+            {
+                return resultCode;
+            }
+
+            uint deviceHandle = (uint)context.RequestData.ReadUInt64();
+
+            if (context.Device.System.NfpDevices.Count == 0)
+            {
+                return ResultCode.DeviceNotFound;
+            }
+
+            uint applicationAreaId = context.RequestData.ReadUInt32();
+
+            bool isOpened = false;
+
+            for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
+            {
+                if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle)
+                {
+                    if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved)
+                    {
+                        resultCode = ResultCode.TagNotFound;
+                    }
+                    else
+                    {
+                        if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted)
+                        {
+                            isOpened = VirtualAmiibo.OpenApplicationArea(context.Device.System.NfpDevices[i].AmiiboId, applicationAreaId);
+
+                            resultCode = ResultCode.Success;
+                        }
+                        else
+                        {
+                            resultCode = ResultCode.WrongDeviceState;
+                        }
+                    }
+
+                    break;
+                }
+            }
+
+            if (!isOpened)
+            {
+                resultCode = ResultCode.ApplicationAreaIsNull;
+            }
+
+            return resultCode;
         }
 
         [Command(8)]
         // GetApplicationArea(bytes<8, 4>) -> (u32, buffer<unknown, 6>)
         public ResultCode GetApplicationArea(ServiceCtx context)
         {
-            throw new ServiceNotImplementedException(this, context);
+            ResultCode resultCode = CheckNfcIsEnabled();
+
+            if (resultCode != ResultCode.Success)
+            {
+                return resultCode;
+            }
+
+            uint deviceHandle = (uint)context.RequestData.ReadUInt64();
+
+            if (context.Device.System.NfpDevices.Count == 0)
+            {
+                return ResultCode.DeviceNotFound;
+            }
+
+            long outputPosition = context.Request.ReceiveBuff[0].Position;
+            long outputSize     = context.Request.ReceiveBuff[0].Size;
+
+            MemoryHelper.FillWithZeros(context.Memory, outputPosition, (int)outputSize);
+
+            uint size = 0;
+
+            for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
+            {
+                if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle)
+                {
+                    if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved)
+                    {
+                        resultCode = ResultCode.TagNotFound;
+                    }
+                    else
+                    {
+                        if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted)
+                        {
+                            byte[] applicationArea = VirtualAmiibo.GetApplicationArea(context.Device.System.NfpDevices[i].AmiiboId);
+
+                            context.Memory.Write((ulong)outputPosition, applicationArea);
+
+                            size = (uint)applicationArea.Length;
+
+                            resultCode = ResultCode.Success;
+                        }
+                        else
+                        {
+                            resultCode = ResultCode.WrongDeviceState;
+                        }
+                    }
+                }
+            }
+
+            if (resultCode != ResultCode.Success)
+            {
+                return resultCode;
+            }
+
+            if (size == 0)
+            {
+                return ResultCode.ApplicationAreaIsNull;
+            }
+
+            context.ResponseData.Write(size);
+
+            return ResultCode.Success;
         }
 
         [Command(9)]
         // SetApplicationArea(bytes<8, 4>, buffer<unknown, 5>)
         public ResultCode SetApplicationArea(ServiceCtx context)
         {
-            throw new ServiceNotImplementedException(this, context);
+            ResultCode resultCode = CheckNfcIsEnabled();
+
+            if (resultCode != ResultCode.Success)
+            {
+                return resultCode;
+            }
+
+            uint deviceHandle = (uint)context.RequestData.ReadUInt64();
+
+            if (context.Device.System.NfpDevices.Count == 0)
+            {
+                return ResultCode.DeviceNotFound;
+            }
+
+            long inputPosition = context.Request.SendBuff[0].Position;
+            long inputSize     = context.Request.SendBuff[0].Size;
+
+            byte[] applicationArea = new byte[inputSize];
+
+            context.Memory.Read((ulong)inputPosition, applicationArea);
+
+            for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
+            {
+                if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle)
+                {
+                    if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved)
+                    {
+                        resultCode = ResultCode.TagNotFound;
+                    }
+                    else
+                    {
+                        if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted)
+                        {
+                            VirtualAmiibo.SetApplicationArea(context.Device.System.NfpDevices[i].AmiiboId, applicationArea);
+
+                            resultCode = ResultCode.Success;
+                        }
+                        else
+                        {
+                            resultCode = ResultCode.WrongDeviceState;
+                        }
+                    }
+
+                    break;
+                }
+            }
+
+            return resultCode;
         }
 
         [Command(10)]
         // Flush(bytes<8, 4>)
         public ResultCode Flush(ServiceCtx context)
         {
-            throw new ServiceNotImplementedException(this, context);
+            uint deviceHandle = (uint)context.RequestData.ReadUInt64();
+
+            if (context.Device.System.NfpDevices.Count == 0)
+            {
+                return ResultCode.DeviceNotFound;
+            }
+
+            // NOTE: Since we handle amiibo through VirtualAmiibo, we don't have to flush anything in our case.
+
+            return ResultCode.Success;
         }
 
         [Command(11)]
@@ -166,35 +507,328 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
         // CreateApplicationArea(bytes<8, 4>, u32, buffer<unknown, 5>)
         public ResultCode CreateApplicationArea(ServiceCtx context)
         {
-            throw new ServiceNotImplementedException(this, context);
+            ResultCode resultCode = CheckNfcIsEnabled();
+
+            if (resultCode != ResultCode.Success)
+            {
+                return resultCode;
+            }
+
+            uint deviceHandle = (uint)context.RequestData.ReadUInt64();
+
+            if (context.Device.System.NfpDevices.Count == 0)
+            {
+                return ResultCode.DeviceNotFound;
+            }
+
+            uint applicationAreaId = context.RequestData.ReadUInt32();
+
+            long inputPosition = context.Request.SendBuff[0].Position;
+            long inputSize     = context.Request.SendBuff[0].Size;
+
+            byte[] applicationArea = new byte[inputSize];
+
+            context.Memory.Read((ulong)inputPosition, applicationArea);
+
+            bool isCreated = false;
+
+            for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
+            {
+                if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle)
+                {
+                    if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved)
+                    {
+                        resultCode = ResultCode.TagNotFound;
+                    }
+                    else
+                    {
+                        if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted)
+                        {
+                            isCreated = VirtualAmiibo.CreateApplicationArea(context.Device.System.NfpDevices[i].AmiiboId, applicationAreaId, applicationArea);
+
+                            resultCode = ResultCode.Success;
+                        }
+                        else
+                        {
+                            resultCode = ResultCode.WrongDeviceState;
+                        }
+                    }
+
+                    break;
+                }
+            }
+
+            if (!isCreated)
+            {
+                resultCode = ResultCode.ApplicationAreaIsNull;
+            }
+
+            return resultCode;
         }
 
         [Command(13)]
         // GetTagInfo(bytes<8, 4>) -> buffer<unknown<0x58>, 0x1a>
         public ResultCode GetTagInfo(ServiceCtx context)
         {
-            throw new ServiceNotImplementedException(this, context);
+            ResultCode resultCode = CheckNfcIsEnabled();
+
+            if (resultCode != ResultCode.Success)
+            {
+                return resultCode;
+            }
+
+            if (context.Request.RecvListBuff.Count == 0)
+            {
+                return ResultCode.WrongArgument;
+            }
+
+            long outputPosition = context.Request.RecvListBuff[0].Position;
+
+            context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize(Marshal.SizeOf(typeof(TagInfo)));
+
+            MemoryHelper.FillWithZeros(context.Memory, outputPosition, Marshal.SizeOf(typeof(TagInfo)));
+
+            uint deviceHandle = (uint)context.RequestData.ReadUInt64();
+
+            if (context.Device.System.NfpDevices.Count == 0)
+            {
+                return ResultCode.DeviceNotFound;
+            }
+
+            for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
+            {
+                if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle)
+                {
+                    if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved)
+                    {
+                        resultCode = ResultCode.TagNotFound;
+                    }
+                    else
+                    {
+                        if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted || context.Device.System.NfpDevices[i].State == NfpDeviceState.TagFound)
+                        {
+                            byte[] Uuid = VirtualAmiibo.GenerateUuid(context.Device.System.NfpDevices[i].AmiiboId, context.Device.System.NfpDevices[i].UseRandomUuid);
+
+                            if (Uuid.Length > AmiiboConstants.UuidMaxLength)
+                            {
+                                throw new ArgumentOutOfRangeException();
+                            }
+
+                            TagInfo tagInfo = new TagInfo
+                            {
+                                UuidLength = (byte)Uuid.Length,
+                                Reserved1  = new Array21<byte>(),
+                                Protocol   = uint.MaxValue, // All Protocol
+                                TagType    = uint.MaxValue, // All Type
+                                Reserved2  = new Array6<byte>()
+                            };
+
+                            Uuid.CopyTo(tagInfo.Uuid.ToSpan());
+
+                            context.Memory.Write((ulong)outputPosition, tagInfo);
+
+                            resultCode = ResultCode.Success;
+                        }
+                        else
+                        {
+                            resultCode = ResultCode.WrongDeviceState;
+                        }
+                    }
+
+                    break;
+                }
+            }
+
+            return resultCode;
         }
 
         [Command(14)]
         // GetRegisterInfo(bytes<8, 4>) -> buffer<unknown<0x100>, 0x1a>
         public ResultCode GetRegisterInfo(ServiceCtx context)
         {
-            throw new ServiceNotImplementedException(this, context);
+            ResultCode resultCode = CheckNfcIsEnabled();
+
+            if (resultCode != ResultCode.Success)
+            {
+                return resultCode;
+            }
+
+            if (context.Request.RecvListBuff.Count == 0)
+            {
+                return ResultCode.WrongArgument;
+            }
+
+            long outputPosition = context.Request.RecvListBuff[0].Position;
+
+            context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize(Marshal.SizeOf(typeof(RegisterInfo)));
+
+            MemoryHelper.FillWithZeros(context.Memory, outputPosition, Marshal.SizeOf(typeof(RegisterInfo)));
+
+            uint deviceHandle = (uint)context.RequestData.ReadUInt64();
+
+            if (context.Device.System.NfpDevices.Count == 0)
+            {
+                return ResultCode.DeviceNotFound;
+            }
+
+            for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
+            {
+                if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle)
+                {
+                    if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved)
+                    {
+                        resultCode = ResultCode.TagNotFound;
+                    }
+                    else
+                    {
+                        if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted)
+                        {
+                            RegisterInfo registerInfo = VirtualAmiibo.GetRegisterInfo(context.Device.System.NfpDevices[i].AmiiboId);
+
+                            context.Memory.Write((ulong)outputPosition, registerInfo);
+
+                            resultCode = ResultCode.Success;
+                        }
+                        else
+                        {
+                            resultCode = ResultCode.WrongDeviceState;
+                        }
+                    }
+
+                    break;
+                }
+            }
+
+            return resultCode;
         }
 
         [Command(15)]
         // GetCommonInfo(bytes<8, 4>) -> buffer<unknown<0x40>, 0x1a>
         public ResultCode GetCommonInfo(ServiceCtx context)
         {
-            throw new ServiceNotImplementedException(this, context);
+            ResultCode resultCode = CheckNfcIsEnabled();
+
+            if (resultCode != ResultCode.Success)
+            {
+                return resultCode;
+            }
+
+            if (context.Request.RecvListBuff.Count == 0)
+            {
+                return ResultCode.WrongArgument;
+            }
+
+            long outputPosition = context.Request.RecvListBuff[0].Position;
+
+            context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize(Marshal.SizeOf(typeof(CommonInfo)));
+
+            MemoryHelper.FillWithZeros(context.Memory, outputPosition, Marshal.SizeOf(typeof(CommonInfo)));
+
+            uint deviceHandle = (uint)context.RequestData.ReadUInt64();
+
+            if (context.Device.System.NfpDevices.Count == 0)
+            {
+                return ResultCode.DeviceNotFound;
+            }
+
+            for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
+            {
+                if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle)
+                {
+                    if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved)
+                    {
+                        resultCode = ResultCode.TagNotFound;
+                    }
+                    else
+                    {
+                        if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted)
+                        {
+                            CommonInfo commonInfo = VirtualAmiibo.GetCommonInfo(context.Device.System.NfpDevices[i].AmiiboId);
+
+                            context.Memory.Write((ulong)outputPosition, commonInfo);
+
+                            resultCode = ResultCode.Success;
+                        }
+                        else
+                        {
+                            resultCode = ResultCode.WrongDeviceState;
+                        }
+                    }
+
+                    break;
+                }
+            }
+
+            return resultCode;
         }
 
         [Command(16)]
         // GetModelInfo(bytes<8, 4>) -> buffer<unknown<0x40>, 0x1a>
         public ResultCode GetModelInfo(ServiceCtx context)
         {
-            throw new ServiceNotImplementedException(this, context);
+            ResultCode resultCode = CheckNfcIsEnabled();
+
+            if (resultCode != ResultCode.Success)
+            {
+                return resultCode;
+            }
+
+            if (context.Request.RecvListBuff.Count == 0)
+            {
+                return ResultCode.WrongArgument;
+            }
+
+            long outputPosition = context.Request.RecvListBuff[0].Position;
+
+            context.Response.PtrBuff[0] = context.Response.PtrBuff[0].WithSize(Marshal.SizeOf(typeof(ModelInfo)));
+
+            MemoryHelper.FillWithZeros(context.Memory, outputPosition, Marshal.SizeOf(typeof(ModelInfo)));
+
+            uint deviceHandle = (uint)context.RequestData.ReadUInt64();
+
+            if (context.Device.System.NfpDevices.Count == 0)
+            {
+                return ResultCode.DeviceNotFound;
+            }
+
+            for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
+            {
+                if (context.Device.System.NfpDevices[i].Handle == (PlayerIndex)deviceHandle)
+                {
+                    if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagRemoved)
+                    {
+                        resultCode = ResultCode.TagNotFound;
+                    }
+                    else
+                    {
+                        if (context.Device.System.NfpDevices[i].State == NfpDeviceState.TagMounted)
+                        {
+                            ModelInfo modelInfo = new ModelInfo
+                            {
+                                Reserved = new Array57<byte>()
+                            };
+
+                            modelInfo.CharacterId      = BinaryPrimitives.ReverseEndianness(ushort.Parse(context.Device.System.NfpDevices[i].AmiiboId.Substring(0, 4), NumberStyles.HexNumber));
+                            modelInfo.CharacterVariant = byte.Parse(context.Device.System.NfpDevices[i].AmiiboId.Substring(4, 2), NumberStyles.HexNumber);
+                            modelInfo.Series           = byte.Parse(context.Device.System.NfpDevices[i].AmiiboId.Substring(12, 2), NumberStyles.HexNumber);
+                            modelInfo.ModelNumber      = ushort.Parse(context.Device.System.NfpDevices[i].AmiiboId.Substring(8, 4), NumberStyles.HexNumber);
+                            modelInfo.Type             = byte.Parse(context.Device.System.NfpDevices[i].AmiiboId.Substring(6, 2), NumberStyles.HexNumber);
+
+                            context.Memory.Write((ulong)outputPosition, modelInfo);
+
+                            resultCode = ResultCode.Success;
+                        }
+                        else
+                        {
+                            resultCode = ResultCode.WrongDeviceState;
+                        }
+                    }
+
+                    break;
+                }
+            }
+
+            return resultCode;
         }
 
         [Command(17)]
@@ -203,21 +837,18 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
         {
             uint deviceHandle = context.RequestData.ReadUInt32();
 
-            for (int i = 0; i < _devices.Count; i++)
+            for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
             {
-                if ((uint)_devices[i].Handle == deviceHandle)
+                if ((uint)context.Device.System.NfpDevices[i].Handle == deviceHandle)
                 {
-                    if (_devices[i].ActivateEventHandle == 0)
-                    {
-                        _devices[i].ActivateEvent = new KEvent(context.Device.System.KernelContext);
+                    context.Device.System.NfpDevices[i].ActivateEvent = new KEvent(context.Device.System.KernelContext);
 
-                        if (context.Process.HandleTable.GenerateHandle(_devices[i].ActivateEvent.ReadableEvent, out _devices[i].ActivateEventHandle) != KernelResult.Success)
-                        {
-                            throw new InvalidOperationException("Out of handles!");
-                        }
+                    if (context.Process.HandleTable.GenerateHandle(context.Device.System.NfpDevices[i].ActivateEvent.ReadableEvent, out int activateEventHandle) != KernelResult.Success)
+                    {
+                        throw new InvalidOperationException("Out of handles!");
                     }
 
-                    context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_devices[i].ActivateEventHandle);
+                    context.Response.HandleDesc = IpcHandleDesc.MakeCopy(activateEventHandle);
 
                     return ResultCode.Success;
                 }
@@ -232,21 +863,18 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
         {
             uint deviceHandle = context.RequestData.ReadUInt32();
 
-            for (int i = 0; i < _devices.Count; i++)
+            for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
             {
-                if ((uint)_devices[i].Handle == deviceHandle)
+                if ((uint)context.Device.System.NfpDevices[i].Handle == deviceHandle)
                 {
-                    if (_devices[i].DeactivateEventHandle == 0)
-                    {
-                        _devices[i].DeactivateEvent = new KEvent(context.Device.System.KernelContext);
+                    context.Device.System.NfpDevices[i].DeactivateEvent = new KEvent(context.Device.System.KernelContext);
 
-                        if (context.Process.HandleTable.GenerateHandle(_devices[i].DeactivateEvent.ReadableEvent, out _devices[i].DeactivateEventHandle) != KernelResult.Success)
-                        {
-                            throw new InvalidOperationException("Out of handles!");
-                        }
+                    if (context.Process.HandleTable.GenerateHandle(context.Device.System.NfpDevices[i].DeactivateEvent.ReadableEvent, out int deactivateEventHandle) != KernelResult.Success)
+                    {
+                        throw new InvalidOperationException("Out of handles!");
                     }
 
-                    context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_devices[i].DeactivateEventHandle);
+                    context.Response.HandleDesc = IpcHandleDesc.MakeCopy(deactivateEventHandle);
 
                     return ResultCode.Success;
                 }
@@ -270,17 +898,22 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
         {
             uint deviceHandle = context.RequestData.ReadUInt32();
 
-            for (int i = 0; i < _devices.Count; i++)
+            for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
             {
-                if ((uint)_devices[i].Handle == deviceHandle)
+                if ((uint)context.Device.System.NfpDevices[i].Handle == deviceHandle)
                 {
-                    context.ResponseData.Write((uint)_devices[i].State);
+                    if (context.Device.System.NfpDevices[i].State > NfpDeviceState.Finalized)
+                    {
+                        throw new ArgumentOutOfRangeException();
+                    }
+                    
+                    context.ResponseData.Write((uint)context.Device.System.NfpDevices[i].State);
 
                     return ResultCode.Success;
                 }
             }
 
-            context.ResponseData.Write((uint)DeviceState.Unavailable);
+            context.ResponseData.Write((uint)NfpDeviceState.Unavailable);
 
             return ResultCode.DeviceNotFound;
         }
@@ -291,11 +924,11 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
         {
             uint deviceHandle = context.RequestData.ReadUInt32();
 
-            for (int i = 0; i < _devices.Count; i++)
+            for (int i = 0; i < context.Device.System.NfpDevices.Count; i++)
             {
-                if ((uint)_devices[i].Handle == deviceHandle)
+                if ((uint)context.Device.System.NfpDevices[i].Handle == deviceHandle)
                 {
-                    context.ResponseData.Write((uint)HidUtils.GetNpadIdTypeFromIndex(_devices[i].Handle));
+                    context.ResponseData.Write((uint)HidUtils.GetNpadIdTypeFromIndex(context.Device.System.NfpDevices[i].Handle));
 
                     return ResultCode.Success;
                 }
@@ -305,27 +938,26 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
         }
 
         [Command(22)]
-        // GetApplicationAreaSize(bytes<8, 4>) -> u32
+        // GetApplicationAreaSize() -> u32
         public ResultCode GetApplicationAreaSize(ServiceCtx context)
         {
-            throw new ServiceNotImplementedException(this, context);
+            context.ResponseData.Write(AmiiboConstants.ApplicationAreaSize);
+
+            return ResultCode.Success;
         }
 
         [Command(23)] // 3.0.0+
         // AttachAvailabilityChangeEvent() -> handle<copy>
         public ResultCode AttachAvailabilityChangeEvent(ServiceCtx context)
         {
-            if (_availabilityChangeEventHandle == 0)
-            {
-                _availabilityChangeEvent = new KEvent(context.Device.System.KernelContext);
+            _availabilityChangeEvent = new KEvent(context.Device.System.KernelContext);
 
-                if (context.Process.HandleTable.GenerateHandle(_availabilityChangeEvent.ReadableEvent, out _availabilityChangeEventHandle) != KernelResult.Success)
-                {
-                    throw new InvalidOperationException("Out of handles!");
-                }
+            if (context.Process.HandleTable.GenerateHandle(_availabilityChangeEvent.ReadableEvent, out int availabilityChangeEventHandle) != KernelResult.Success)
+            {
+                throw new InvalidOperationException("Out of handles!");
             }
 
-            context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_availabilityChangeEventHandle);
+            context.Response.HandleDesc = IpcHandleDesc.MakeCopy(availabilityChangeEventHandle);
 
             return ResultCode.Success;
         }
@@ -336,5 +968,11 @@ namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
         {
             throw new ServiceNotImplementedException(this, context);
         }
+
+        private ResultCode CheckNfcIsEnabled()
+        {
+            // TODO: Call nn::settings::detail::GetNfcEnableFlag when it will be implemented.
+            return true ? ResultCode.Success : ResultCode.NfcDisabled;
+        }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/AmiiboConstants.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/AmiiboConstants.cs
new file mode 100644
index 0000000000..47f6f0fa7c
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/AmiiboConstants.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager
+{
+    static class AmiiboConstants
+    {
+        public const int UuidMaxLength       = 10;
+        public const int ApplicationAreaSize = 0xD8;
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/CommonInfo.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/CommonInfo.cs
new file mode 100644
index 0000000000..da055dc364
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/CommonInfo.cs
@@ -0,0 +1,17 @@
+using Ryujinx.Common.Memory;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x40)]
+    struct CommonInfo
+    {
+        public ushort        LastWriteYear;
+        public byte          LastWriteMonth;
+        public byte          LastWriteDay;
+        public ushort        WriteCounter;
+        public ushort        Version;
+        public uint          ApplicationAreaSize;
+        public Array52<byte> Reserved;
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/Device.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/Device.cs
deleted file mode 100644
index 3ff3489bbe..0000000000
--- a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/Device.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using Ryujinx.HLE.HOS.Kernel.Threading;
-using Ryujinx.HLE.HOS.Services.Hid;
-
-namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager
-{
-    class Device
-    {
-        public KEvent ActivateEvent;
-        public int    ActivateEventHandle;
-
-        public KEvent DeactivateEvent;
-        public int    DeactivateEventHandle;
-
-        public DeviceState State = DeviceState.Unavailable;
-
-        public PlayerIndex Handle;
-        public NpadIdType  NpadIdType;
-    }
-}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/DeviceType.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/DeviceType.cs
new file mode 100644
index 0000000000..753b91a936
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/DeviceType.cs
@@ -0,0 +1,7 @@
+namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager
+{
+    enum DeviceType : uint
+    {
+        Amiibo
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/ModelInfo.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/ModelInfo.cs
new file mode 100644
index 0000000000..1b6a3d32a8
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/ModelInfo.cs
@@ -0,0 +1,16 @@
+using Ryujinx.Common.Memory;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x40)]
+    struct ModelInfo
+    {
+        public ushort        CharacterId;
+        public byte          CharacterVariant;
+        public byte          Series;
+        public ushort        ModelNumber;
+        public byte          Type;
+        public Array57<byte> Reserved;
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/MountTarget.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/MountTarget.cs
new file mode 100644
index 0000000000..11520bc6d4
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/MountTarget.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager
+{
+    enum MountTarget : uint
+    {
+        Rom = 1,
+        Ram = 2,
+        All = 3
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/NfpDevice.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/NfpDevice.cs
new file mode 100644
index 0000000000..b0d9c80601
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/NfpDevice.cs
@@ -0,0 +1,23 @@
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.HLE.HOS.Services.Hid;
+
+namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager
+{
+    class NfpDevice
+    {
+        public KEvent ActivateEvent;
+        public KEvent DeactivateEvent;
+
+        public void SignalActivate()   => ActivateEvent.ReadableEvent.Signal();
+        public void SignalDeactivate() => DeactivateEvent.ReadableEvent.Signal();
+
+        public NfpDeviceState State = NfpDeviceState.Unavailable;
+
+        public PlayerIndex Handle;
+        public NpadIdType  NpadIdType;
+
+        public string AmiiboId;
+
+        public bool UseRandomUuid;
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/DeviceState.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/NfpDeviceState.cs
similarity index 91%
rename from Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/DeviceState.cs
rename to Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/NfpDeviceState.cs
index 7e37349411..0e75325079 100644
--- a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/DeviceState.cs
+++ b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/NfpDeviceState.cs
@@ -1,6 +1,6 @@
 namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager
 {
-    enum DeviceState
+    enum NfpDeviceState
     {
         Initialized     = 0,
         SearchingForTag = 1,
diff --git a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/RegisterInfo.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/RegisterInfo.cs
new file mode 100644
index 0000000000..3c72a9715a
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/RegisterInfo.cs
@@ -0,0 +1,19 @@
+using Ryujinx.Common.Memory;
+using Ryujinx.HLE.HOS.Services.Mii.Types;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x100)]
+    struct RegisterInfo
+    {
+        public CharInfo      MiiCharInfo;
+        public ushort        FirstWriteYear;
+        public byte          FirstWriteMonth;
+        public byte          FirstWriteDay;
+        public Array11<byte> Nickname;
+        public byte          FontRegion;
+        public Array64<byte> Reserved1;
+        public Array58<byte> Reserved2;
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/TagInfo.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/TagInfo.cs
new file mode 100644
index 0000000000..950f8c1045
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/TagInfo.cs
@@ -0,0 +1,16 @@
+using Ryujinx.Common.Memory;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager
+{
+    [StructLayout(LayoutKind.Sequential, Size = 0x58)]
+    struct TagInfo
+    {
+        public Array10<byte> Uuid;
+        public byte          UuidLength;
+        public Array21<byte> Reserved1;
+        public uint          Protocol;
+        public uint          TagType;
+        public Array6<byte>  Reserved2;
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/VirtualAmiiboFile.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/VirtualAmiiboFile.cs
new file mode 100644
index 0000000000..5265c0389c
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/UserManager/Types/VirtualAmiiboFile.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager
+{
+    struct VirtualAmiiboFile
+    {
+        public uint     FileVersion    { get; set; }
+        public byte[]   TagUuid        { get; set; }
+        public string   AmiiboId       { get; set; }
+        public DateTime FirstWriteDate { get; set; }
+        public DateTime LastWriteDate  { get; set; }
+        public ushort   WriteCounter   { get; set; }
+        public List<VirtualAmiiboApplicationArea> ApplicationAreas { get; set; }
+    }
+
+    struct VirtualAmiiboApplicationArea
+    {
+        public uint   ApplicationAreaId { get; set; }
+        public byte[] ApplicationArea   { get; set; }
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs
new file mode 100644
index 0000000000..bd810d96aa
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Nfc/Nfp/VirtualAmiibo.cs
@@ -0,0 +1,205 @@
+using Ryujinx.Common.Configuration;
+using Ryujinx.Common.Memory;
+using Ryujinx.HLE.HOS.Services.Mii;
+using Ryujinx.HLE.HOS.Services.Mii.Types;
+using Ryujinx.HLE.HOS.Services.Nfc.Nfp.UserManager;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Text.Json;
+
+namespace Ryujinx.HLE.HOS.Services.Nfc.Nfp
+{
+    static class VirtualAmiibo
+    {
+        private static uint _openedApplicationAreaId;
+
+        public static byte[] GenerateUuid(string amiiboId, bool useRandomUuid)
+        {
+            if (useRandomUuid)
+            {
+                return GenerateRandomUuid();
+            }
+
+            VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId);
+
+            if (virtualAmiiboFile.TagUuid.Length == 0)
+            {
+                virtualAmiiboFile.TagUuid = GenerateRandomUuid();
+
+                SaveAmiiboFile(virtualAmiiboFile);
+            }
+
+            return virtualAmiiboFile.TagUuid;
+        }
+
+        private static byte[] GenerateRandomUuid()
+        {
+            byte[] uuid = new byte[9];
+
+            new Random().NextBytes(uuid);
+
+            uuid[3] = (byte)(0x88    ^ uuid[0] ^ uuid[1] ^ uuid[2]);
+            uuid[8] = (byte)(uuid[3] ^ uuid[4] ^ uuid[5] ^ uuid[6]);
+
+            return uuid;
+        }
+
+        public static CommonInfo GetCommonInfo(string amiiboId)
+        {
+            VirtualAmiiboFile amiiboFile = LoadAmiiboFile(amiiboId);
+
+            return new CommonInfo()
+            {
+                LastWriteYear       = (ushort)amiiboFile.LastWriteDate.Year,
+                LastWriteMonth      = (byte)amiiboFile.LastWriteDate.Month,
+                LastWriteDay        = (byte)amiiboFile.LastWriteDate.Day,
+                WriteCounter        = amiiboFile.WriteCounter,
+                Version             = 1,
+                ApplicationAreaSize = AmiiboConstants.ApplicationAreaSize,
+                Reserved            = new Array52<byte>()
+            };
+        }
+
+        public static RegisterInfo GetRegisterInfo(string amiiboId)
+        {
+            VirtualAmiiboFile amiiboFile = LoadAmiiboFile(amiiboId);
+
+            UtilityImpl utilityImpl = new UtilityImpl();
+            CharInfo    charInfo    = new CharInfo();
+
+            charInfo.SetFromStoreData(StoreData.BuildDefault(utilityImpl, 0));
+
+            // TODO: Maybe change the "no name" by the player name when user profile will be implemented.
+            // charInfo.Nickname = Nickname.FromString("Nickname");
+
+            RegisterInfo registerInfo = new RegisterInfo()
+            {
+                MiiCharInfo     = charInfo,
+                FirstWriteYear  = (ushort)amiiboFile.FirstWriteDate.Year,
+                FirstWriteMonth = (byte)amiiboFile.FirstWriteDate.Month,
+                FirstWriteDay   = (byte)amiiboFile.FirstWriteDate.Day,
+                FontRegion      = 0,
+                Reserved1       = new Array64<byte>(),
+                Reserved2       = new Array58<byte>()
+            };
+
+            Encoding.ASCII.GetBytes("Ryujinx").CopyTo(registerInfo.Nickname.ToSpan());
+
+            return registerInfo;
+        }
+
+        public static bool OpenApplicationArea(string amiiboId, uint applicationAreaId)
+        {
+            VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId);
+
+            if (virtualAmiiboFile.ApplicationAreas.Any(item => item.ApplicationAreaId == applicationAreaId))
+            {
+                _openedApplicationAreaId = applicationAreaId;
+
+                return true;
+            }
+
+            return false;
+        }
+
+        public static byte[] GetApplicationArea(string amiiboId)
+        {
+            VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId);
+
+            foreach (VirtualAmiiboApplicationArea applicationArea in virtualAmiiboFile.ApplicationAreas)
+            {
+                if (applicationArea.ApplicationAreaId == _openedApplicationAreaId)
+                {
+                    return applicationArea.ApplicationArea;
+                }
+            }
+
+            return Array.Empty<byte>();
+        }
+
+        public static bool CreateApplicationArea(string amiiboId, uint applicationAreaId, byte[] applicationAreaData)
+        {
+            VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId);
+
+            if (virtualAmiiboFile.ApplicationAreas.Any(item => item.ApplicationAreaId == applicationAreaId))
+            {
+                return false;
+            }
+
+            virtualAmiiboFile.ApplicationAreas.Add(new VirtualAmiiboApplicationArea()
+            {
+                ApplicationAreaId = applicationAreaId,
+                ApplicationArea   = applicationAreaData
+            });
+
+            SaveAmiiboFile(virtualAmiiboFile);
+
+            return true;
+        }
+
+        public static void SetApplicationArea(string amiiboId, byte[] applicationAreaData)
+        {
+            VirtualAmiiboFile virtualAmiiboFile = LoadAmiiboFile(amiiboId);
+
+            if (virtualAmiiboFile.ApplicationAreas.Any(item => item.ApplicationAreaId == _openedApplicationAreaId))
+            {
+                for (int i = 0; i < virtualAmiiboFile.ApplicationAreas.Count; i++)
+                {
+                    if (virtualAmiiboFile.ApplicationAreas[i].ApplicationAreaId == _openedApplicationAreaId)
+                    {
+                        virtualAmiiboFile.ApplicationAreas[i] = new VirtualAmiiboApplicationArea()
+                        {
+                            ApplicationAreaId = _openedApplicationAreaId,
+                            ApplicationArea   = applicationAreaData
+                        };
+
+                        break;
+                    }
+                }
+
+                SaveAmiiboFile(virtualAmiiboFile);
+            }
+        }
+
+        private static VirtualAmiiboFile LoadAmiiboFile(string amiiboId)
+        {
+            Directory.CreateDirectory(Path.Join(AppDataManager.BaseDirPath, "system", "amiibo"));
+
+            string filePath = Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", $"{amiiboId}.json");
+
+            VirtualAmiiboFile virtualAmiiboFile;
+
+            if (File.Exists(filePath))
+            {
+                virtualAmiiboFile = JsonSerializer.Deserialize<VirtualAmiiboFile>(File.ReadAllText(filePath));
+            }
+            else
+            {
+                virtualAmiiboFile = new VirtualAmiiboFile()
+                {
+                    FileVersion      = 0,
+                    TagUuid          = Array.Empty<byte>(),
+                    AmiiboId         = amiiboId,
+                    FirstWriteDate   = DateTime.Now,
+                    LastWriteDate    = DateTime.Now,
+                    WriteCounter     = 0,
+                    ApplicationAreas = new List<VirtualAmiiboApplicationArea>()
+                };
+
+                SaveAmiiboFile(virtualAmiiboFile);
+            }
+
+            return virtualAmiiboFile;
+        }
+
+        private static void SaveAmiiboFile(VirtualAmiiboFile virtualAmiiboFile)
+        {
+            string filePath = Path.Join(AppDataManager.BaseDirPath, "system", "amiibo", $"{virtualAmiiboFile.AmiiboId}.json");
+
+            File.WriteAllText(filePath, JsonSerializer.Serialize(virtualAmiiboFile));
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx/Ryujinx.csproj b/Ryujinx/Ryujinx.csproj
index 5dd250f4a3..4a5d75087a 100644
--- a/Ryujinx/Ryujinx.csproj
+++ b/Ryujinx/Ryujinx.csproj
@@ -70,6 +70,7 @@
     <None Remove="Ui\Resources\Icon_NSO.png" />
     <None Remove="Ui\Resources\Icon_NSP.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_GitHub.png" />
     <None Remove="Ui\Resources\Logo_Patreon.png" />
@@ -94,6 +95,7 @@
     <EmbeddedResource Include="Ui\Resources\Icon_NSO.png" />
     <EmbeddedResource Include="Ui\Resources\Icon_NSP.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_GitHub.png" />
     <EmbeddedResource Include="Ui\Resources\Logo_Patreon.png" />
diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs
index 7d48422c12..634a17816c 100644
--- a/Ryujinx/Ui/MainWindow.cs
+++ b/Ryujinx/Ui/MainWindow.cs
@@ -57,6 +57,9 @@ namespace Ryujinx.Ui
 
         private string _currentEmulatedGamePath = null;
 
+        private string _lastScannedAmiiboId = "";
+        private bool   _lastScannedAmiiboShowAll = false;
+
         public GlRenderer GlRendererWidget;
 
 #pragma warning disable CS0169, CS0649, IDE0044
@@ -66,8 +69,11 @@ namespace Ryujinx.Ui
         [GUI] MenuBar         _menuBar;
         [GUI] Box             _footerBox;
         [GUI] Box             _statusBar;
+        [GUI] MenuItem        _optionMenu;
+        [GUI] MenuItem        _actionMenu;
         [GUI] MenuItem        _stopEmulation;
         [GUI] MenuItem        _simulateWakeUpMessage;
+        [GUI] MenuItem        _scanAmiibo;
         [GUI] MenuItem        _fullScreen;
         [GUI] CheckMenuItem   _startFullScreen;
         [GUI] CheckMenuItem   _favToggle;
@@ -141,6 +147,8 @@ namespace Ryujinx.Ui
             _applicationLibrary.ApplicationAdded        += Application_Added;
             _applicationLibrary.ApplicationCountUpdated += ApplicationCount_Updated;
 
+            _actionMenu.StateChanged += ActionMenu_StateChanged;
+
             _gameTable.ButtonReleaseEvent += Row_Clicked;
             _fullScreen.Activated         += FullScreen_Toggled;
 
@@ -151,8 +159,7 @@ namespace Ryujinx.Ui
                 _startFullScreen.Active = true;
             }
 
-            _stopEmulation.Sensitive         = false;
-            _simulateWakeUpMessage.Sensitive = false;
+            _actionMenu.Sensitive = false;
 
             if (ConfigurationState.Instance.Ui.GuiColumns.FavColumn)        _favToggle.Active        = true;
             if (ConfigurationState.Instance.Ui.GuiColumns.IconColumn)       _iconToggle.Active       = true;
@@ -594,9 +601,10 @@ namespace Ryujinx.Ui
                 windowThread.Start();
 #endif
 
-                _gameLoaded                      = true;
-                _stopEmulation.Sensitive         = true;
-                _simulateWakeUpMessage.Sensitive = true;
+                _gameLoaded           = true;
+                _actionMenu.Sensitive = true;
+
+                _lastScannedAmiiboId = "";
 
                 _firmwareInstallFile.Sensitive      = false;
                 _firmwareInstallDirectory.Sensitive = false;
@@ -692,8 +700,7 @@ namespace Ryujinx.Ui
                 Task.Run(RefreshFirmwareLabel);
                 Task.Run(HandleRelaunch);
 
-                _stopEmulation.Sensitive            = false;
-                _simulateWakeUpMessage.Sensitive    = false;
+                _actionMenu.Sensitive               = false;
                 _firmwareInstallFile.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)
         {
             if (Updater.CanUpdate(true))
diff --git a/Ryujinx/Ui/MainWindow.glade b/Ryujinx/Ui/MainWindow.glade
index 5558403bac..beeed265a3 100644
--- a/Ryujinx/Ui/MainWindow.glade
+++ b/Ryujinx/Ui/MainWindow.glade
@@ -95,7 +95,7 @@
               </object>
             </child>
             <child>
-              <object class="GtkMenuItem" id="OptionsMenu">
+              <object class="GtkMenuItem" id="_optionMenu">
                 <property name="visible">True</property>
                 <property name="can_focus">False</property>
                 <property name="label" translatable="yes">Options</property>
@@ -127,32 +127,6 @@
                         <property name="can_focus">False</property>
                       </object>
                     </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>
                       <object class="GtkMenuItem" id="GUIColumns">
                         <property name="visible">True</property>
@@ -278,6 +252,56 @@
                 </child>
               </object>
             </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>
               <object class="GtkMenuItem" id="_toolsMenu">
                 <property name="visible">True</property>
diff --git a/Ryujinx/Ui/Resources/Logo_Amiibo.png b/Ryujinx/Ui/Resources/Logo_Amiibo.png
new file mode 100644
index 0000000000000000000000000000000000000000..05e7c944ea43850a9eefa1ff1185abf4da01a031
GIT binary patch
literal 11676
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
F`wvgCzykmP

literal 0
HcmV?d00001

diff --git a/Ryujinx/Ui/Windows/AmiiboWindow.Designer.cs b/Ryujinx/Ui/Windows/AmiiboWindow.Designer.cs
new file mode 100644
index 0000000000..3480c6e8a8
--- /dev/null
+++ b/Ryujinx/Ui/Windows/AmiiboWindow.Designer.cs
@@ -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();
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx/Ui/Windows/AmiiboWindow.cs b/Ryujinx/Ui/Windows/AmiiboWindow.cs
new file mode 100644
index 0000000000..ac087ce12d
--- /dev/null
+++ b/Ryujinx/Ui/Windows/AmiiboWindow.cs
@@ -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);
+        }
+    }
+}
\ No newline at end of file