From 3d0a3c2c45cb6322ca9ae3a4fa570918339770c0 Mon Sep 17 00:00:00 2001
From: Narr the Reg <juangerman-13@hotmail.com>
Date: Fri, 30 Jun 2023 14:15:58 -0600
Subject: [PATCH] service: nfc: Implement amiibo encryption and appdata (#6340)

---
 .../org/citra/citra_emu/NativeLibrary.java    |    2 +-
 .../activities/EmulationActivity.java         |   10 +-
 src/android/app/src/main/jni/native.cpp       |   12 +-
 src/android/app/src/main/jni/native.h         |    2 +-
 src/citra_qt/main.cpp                         |   39 +-
 src/core/CMakeLists.txt                       |    6 +
 src/core/hle/service/nfc/amiibo_crypto.cpp    |  374 ++++++
 src/core/hle/service/nfc/amiibo_crypto.h      |  109 ++
 src/core/hle/service/nfc/nfc.cpp              |  745 +++++++----
 src/core/hle/service/nfc/nfc.h                |  234 +++-
 src/core/hle/service/nfc/nfc_device.cpp       | 1110 +++++++++++++++++
 src/core/hle/service/nfc/nfc_device.h         |  111 ++
 src/core/hle/service/nfc/nfc_m.cpp            |   32 +-
 src/core/hle/service/nfc/nfc_results.h        |   62 +
 src/core/hle/service/nfc/nfc_types.h          |  460 +++++++
 src/core/hle/service/nfc/nfc_u.cpp            |   31 +-
 16 files changed, 3016 insertions(+), 323 deletions(-)
 create mode 100644 src/core/hle/service/nfc/amiibo_crypto.cpp
 create mode 100644 src/core/hle/service/nfc/amiibo_crypto.h
 create mode 100644 src/core/hle/service/nfc/nfc_device.cpp
 create mode 100644 src/core/hle/service/nfc/nfc_device.h
 create mode 100644 src/core/hle/service/nfc/nfc_results.h
 create mode 100644 src/core/hle/service/nfc/nfc_types.h

diff --git a/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.java b/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.java
index 91c9f5be7..2e431eb92 100644
--- a/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.java
+++ b/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.java
@@ -585,7 +585,7 @@ public final class NativeLibrary {
     /// Notifies that the activity is now in foreground and camera devices can now be reloaded
     public static native void ReloadCameraDevices();
 
-    public static native boolean LoadAmiibo(byte[] bytes);
+    public static native boolean LoadAmiibo(String path);
 
     public static native void RemoveAmiibo();
 
diff --git a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.java b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.java
index 88bb0f3a5..2e2d0d112 100644
--- a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.java
+++ b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.java
@@ -570,15 +570,7 @@ public final class EmulationActivity extends AppCompatActivity {
     }
 
     private void onAmiiboSelected(String selectedFile) {
-        boolean success = false;
-        try {
-            Uri uri = Uri.parse(selectedFile);
-            DocumentFile file = DocumentFile.fromSingleUri(this, uri);
-            byte[] bytes = FileUtil.getBytesFromFile(this, file);
-            success = NativeLibrary.LoadAmiibo(bytes);
-        } catch (IOException e) {
-            e.printStackTrace();
-        }
+        boolean success = NativeLibrary.LoadAmiibo(selectedFile);
 
         if (!success) {
             new MaterialAlertDialogBuilder(this)
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index e9ea0cb37..228db6d86 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -569,20 +569,16 @@ void Java_org_citra_citra_1emu_NativeLibrary_ReloadCameraDevices(JNIEnv* env, jc
 }
 
 jboolean Java_org_citra_citra_1emu_NativeLibrary_LoadAmiibo(JNIEnv* env, jclass clazz,
-                                                            jbyteArray bytes) {
+                                                            jstring j_file) {
+    std::string filepath = GetJString(env, j_file);
     Core::System& system{Core::System::GetInstance()};
     Service::SM::ServiceManager& sm = system.ServiceManager();
     auto nfc = sm.GetService<Service::NFC::Module::Interface>("nfc:u");
-    if (nfc == nullptr || env->GetArrayLength(bytes) != sizeof(Service::NFC::AmiiboData)) {
+    if (nfc == nullptr) {
         return static_cast<jboolean>(false);
     }
 
-    Service::NFC::AmiiboData amiibo_data{};
-    env->GetByteArrayRegion(bytes, 0, sizeof(Service::NFC::AmiiboData),
-                            reinterpret_cast<jbyte*>(&amiibo_data));
-
-    nfc->LoadAmiibo(amiibo_data);
-    return static_cast<jboolean>(true);
+    return static_cast<jboolean>(nfc->LoadAmiibo(filepath));
 }
 
 void Java_org_citra_citra_1emu_NativeLibrary_RemoveAmiibo(JNIEnv* env, jclass clazz) {
diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h
index 30f012653..5733a7b56 100644
--- a/src/android/app/src/main/jni/native.h
+++ b/src/android/app/src/main/jni/native.h
@@ -142,7 +142,7 @@ JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_ReloadCameraDevic
                                                                                    jclass clazz);
 
 JNIEXPORT jboolean Java_org_citra_citra_1emu_NativeLibrary_LoadAmiibo(JNIEnv* env, jclass clazz,
-                                                                      jbyteArray bytes);
+                                                                      jstring j_file);
 
 JNIEXPORT void Java_org_citra_citra_1emu_NativeLibrary_RemoveAmiibo(JNIEnv* env, jclass clazz);
 
diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp
index c00d277fd..4f889cf22 100644
--- a/src/citra_qt/main.cpp
+++ b/src/citra_qt/main.cpp
@@ -2017,6 +2017,25 @@ void GMainWindow::OnLoadAmiibo() {
         return;
     }
 
+    Core::System& system{Core::System::GetInstance()};
+    Service::SM::ServiceManager& sm = system.ServiceManager();
+    auto nfc = sm.GetService<Service::NFC::Module::Interface>("nfc:u");
+    if (nfc == nullptr) {
+        return;
+    }
+
+    if (nfc->IsTagActive()) {
+        QMessageBox::warning(this, tr("Error opening amiibo data file"),
+                             tr("A tag is already in use."));
+        return;
+    }
+
+    if (!nfc->IsSearchingForAmiibos()) {
+        QMessageBox::warning(this, tr("Error opening amiibo data file"),
+                             tr("Game is not looking for amiibos."));
+        return;
+    }
+
     const QString extensions{QStringLiteral("*.bin")};
     const QString file_filter = tr("Amiibo File (%1);; All Files (*.*)").arg(extensions);
     const QString filename = QFileDialog::getOpenFileName(this, tr("Load Amiibo"), {}, file_filter);
@@ -2035,26 +2054,12 @@ void GMainWindow::LoadAmiibo(const QString& filename) {
         return;
     }
 
-    QFile nfc_file{filename};
-    if (!nfc_file.open(QIODevice::ReadOnly)) {
-        QMessageBox::warning(this, tr("Error opening Amiibo data file"),
-                             tr("Unable to open Amiibo file \"%1\" for reading.").arg(filename));
+    if (!nfc->LoadAmiibo(filename.toStdString())) {
+        QMessageBox::warning(this, tr("Error opening amiibo data file"),
+                             tr("Unable to open amiibo file \"%1\" for reading.").arg(filename));
         return;
     }
 
-    Service::NFC::AmiiboData amiibo_data{};
-    const u64 read_size =
-        nfc_file.read(reinterpret_cast<char*>(&amiibo_data), sizeof(Service::NFC::AmiiboData));
-    if (read_size != sizeof(Service::NFC::AmiiboData)) {
-        QMessageBox::warning(this, tr("Error reading Amiibo data file"),
-                             tr("Unable to fully read Amiibo data. Expected to read %1 bytes, but "
-                                "was only able to read %2 bytes.")
-                                 .arg(sizeof(Service::NFC::AmiiboData))
-                                 .arg(read_size));
-        return;
-    }
-
-    nfc->LoadAmiibo(amiibo_data);
     ui->action_Remove_Amiibo->setEnabled(true);
 }
 
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index b8a9d778e..0e628cb55 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -333,10 +333,16 @@ add_library(citra_core STATIC
     hle/service/news/news_s.h
     hle/service/news/news_u.cpp
     hle/service/news/news_u.h
+    hle/service/nfc/amiibo_crypto.cpp
+    hle/service/nfc/amiibo_crypto.h
     hle/service/nfc/nfc.cpp
     hle/service/nfc/nfc.h
+    hle/service/nfc/nfc_device.cpp
+    hle/service/nfc/nfc_device.h
     hle/service/nfc/nfc_m.cpp
     hle/service/nfc/nfc_m.h
+    hle/service/nfc/nfc_results.h
+    hle/service/nfc/nfc_types.h
     hle/service/nfc/nfc_u.cpp
     hle/service/nfc/nfc_u.h
     hle/service/nim/nim.cpp
diff --git a/src/core/hle/service/nfc/amiibo_crypto.cpp b/src/core/hle/service/nfc/amiibo_crypto.cpp
new file mode 100644
index 000000000..d57d80aba
--- /dev/null
+++ b/src/core/hle/service/nfc/amiibo_crypto.cpp
@@ -0,0 +1,374 @@
+// Copyright 2022 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+// Copyright 2017 socram8888/amiitool
+// Licensed under MIT
+// Refer to the license.txt file included.
+
+#include <array>
+#include <cryptopp/aes.h>
+#include <cryptopp/hmac.h>
+#include <cryptopp/modes.h>
+#include <cryptopp/sha.h>
+
+#include "common/file_util.h"
+#include "common/logging/log.h"
+#include "core/hle/service/nfc/amiibo_crypto.h"
+
+namespace Service::NFC::AmiiboCrypto {
+
+bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) {
+    const auto& amiibo_data = ntag_file.user_memory;
+    LOG_DEBUG(Service_NFC, "uuid_lock=0x{0:x}", ntag_file.static_lock);
+    LOG_DEBUG(Service_NFC, "compability_container=0x{0:x}", ntag_file.compability_container);
+    LOG_DEBUG(Service_NFC, "write_count={}", static_cast<u16>(amiibo_data.write_counter));
+
+    LOG_DEBUG(Service_NFC, "character_id=0x{0:x}", amiibo_data.model_info.character_id);
+    LOG_DEBUG(Service_NFC, "character_variant={}", amiibo_data.model_info.character_variant);
+    LOG_DEBUG(Service_NFC, "amiibo_type={}", amiibo_data.model_info.amiibo_type);
+    LOG_DEBUG(Service_NFC, "model_number=0x{0:x}",
+              static_cast<u16>(amiibo_data.model_info.model_number));
+    LOG_DEBUG(Service_NFC, "series={}", amiibo_data.model_info.series);
+    LOG_DEBUG(Service_NFC, "tag_type=0x{0:x}", amiibo_data.model_info.tag_type);
+
+    LOG_DEBUG(Service_NFC, "tag_dynamic_lock=0x{0:x}", ntag_file.dynamic_lock);
+    LOG_DEBUG(Service_NFC, "tag_CFG0=0x{0:x}", ntag_file.CFG0);
+    LOG_DEBUG(Service_NFC, "tag_CFG1=0x{0:x}", ntag_file.CFG1);
+
+    // Validate UUID
+    constexpr u8 CT = 0x88; // As defined in `ISO / IEC 14443 - 3`
+    if ((CT ^ ntag_file.uuid.uid[0] ^ ntag_file.uuid.uid[1] ^ ntag_file.uuid.uid[2]) !=
+        ntag_file.uuid.uid[3]) {
+        return false;
+    }
+    if ((ntag_file.uuid.uid[4] ^ ntag_file.uuid.uid[5] ^ ntag_file.uuid.uid[6] ^
+         ntag_file.uuid.nintendo_id) != ntag_file.uuid.lock_bytes[0]) {
+        return false;
+    }
+
+    // Check against all know constants on an amiibo binary
+    if (ntag_file.static_lock != 0xE00F) {
+        return false;
+    }
+    if (ntag_file.compability_container != 0xEEFF10F1U) {
+        return false;
+    }
+    if (amiibo_data.model_info.tag_type != PackedTagType::Type2) {
+        return false;
+    }
+    if ((ntag_file.dynamic_lock & 0xFFFFFF) != 0x0F0001U) {
+        return false;
+    }
+    if (ntag_file.CFG0 != 0x04000000U) {
+        return false;
+    }
+    if (ntag_file.CFG1 != 0x5F) {
+        return false;
+    }
+    return true;
+}
+
+bool IsAmiiboValid(const NTAG215File& ntag_file) {
+    return IsAmiiboValid(EncodedDataToNfcData(ntag_file));
+}
+
+NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) {
+    NTAG215File encoded_data{};
+
+    encoded_data.uid = nfc_data.uuid.uid;
+    encoded_data.nintendo_id = nfc_data.uuid.nintendo_id;
+    encoded_data.static_lock = nfc_data.static_lock;
+    encoded_data.compability_container = nfc_data.compability_container;
+    encoded_data.hmac_data = nfc_data.user_memory.hmac_data;
+    encoded_data.constant_value = nfc_data.user_memory.constant_value;
+    encoded_data.write_counter = nfc_data.user_memory.write_counter;
+    encoded_data.amiibo_version = nfc_data.user_memory.amiibo_version;
+    encoded_data.settings = nfc_data.user_memory.settings;
+    encoded_data.owner_mii = nfc_data.user_memory.owner_mii;
+    encoded_data.padding = nfc_data.user_memory.padding;
+    encoded_data.owner_mii_aes_ccm = nfc_data.user_memory.owner_mii_aes_ccm;
+    encoded_data.application_id = nfc_data.user_memory.application_id;
+    encoded_data.application_write_counter = nfc_data.user_memory.application_write_counter;
+    encoded_data.application_area_id = nfc_data.user_memory.application_area_id;
+    encoded_data.application_id_byte = nfc_data.user_memory.application_id_byte;
+    encoded_data.unknown = nfc_data.user_memory.unknown;
+    encoded_data.mii_extension = nfc_data.user_memory.mii_extension;
+    encoded_data.unknown2 = nfc_data.user_memory.unknown2;
+    encoded_data.register_info_crc = nfc_data.user_memory.register_info_crc;
+    encoded_data.application_area = nfc_data.user_memory.application_area;
+    encoded_data.hmac_tag = nfc_data.user_memory.hmac_tag;
+    encoded_data.lock_bytes = nfc_data.uuid.lock_bytes;
+    encoded_data.model_info = nfc_data.user_memory.model_info;
+    encoded_data.keygen_salt = nfc_data.user_memory.keygen_salt;
+    encoded_data.dynamic_lock = nfc_data.dynamic_lock;
+    encoded_data.CFG0 = nfc_data.CFG0;
+    encoded_data.CFG1 = nfc_data.CFG1;
+    encoded_data.password = nfc_data.password;
+
+    return encoded_data;
+}
+
+EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) {
+    EncryptedNTAG215File nfc_data{};
+
+    nfc_data.uuid.uid = encoded_data.uid;
+    nfc_data.uuid.nintendo_id = encoded_data.nintendo_id;
+    nfc_data.uuid.lock_bytes = encoded_data.lock_bytes;
+    nfc_data.static_lock = encoded_data.static_lock;
+    nfc_data.compability_container = encoded_data.compability_container;
+    nfc_data.user_memory.hmac_data = encoded_data.hmac_data;
+    nfc_data.user_memory.constant_value = encoded_data.constant_value;
+    nfc_data.user_memory.write_counter = encoded_data.write_counter;
+    nfc_data.user_memory.amiibo_version = encoded_data.amiibo_version;
+    nfc_data.user_memory.settings = encoded_data.settings;
+    nfc_data.user_memory.owner_mii = encoded_data.owner_mii;
+    nfc_data.user_memory.padding = encoded_data.padding;
+    nfc_data.user_memory.owner_mii_aes_ccm = encoded_data.owner_mii_aes_ccm;
+    nfc_data.user_memory.application_id = encoded_data.application_id;
+    nfc_data.user_memory.application_write_counter = encoded_data.application_write_counter;
+    nfc_data.user_memory.application_area_id = encoded_data.application_area_id;
+    nfc_data.user_memory.application_id_byte = encoded_data.application_id_byte;
+    nfc_data.user_memory.unknown = encoded_data.unknown;
+    nfc_data.user_memory.mii_extension = encoded_data.mii_extension;
+    nfc_data.user_memory.unknown2 = encoded_data.unknown2;
+    nfc_data.user_memory.register_info_crc = encoded_data.register_info_crc;
+    nfc_data.user_memory.application_area = encoded_data.application_area;
+    nfc_data.user_memory.hmac_tag = encoded_data.hmac_tag;
+    nfc_data.user_memory.model_info = encoded_data.model_info;
+    nfc_data.user_memory.keygen_salt = encoded_data.keygen_salt;
+    nfc_data.dynamic_lock = encoded_data.dynamic_lock;
+    nfc_data.CFG0 = encoded_data.CFG0;
+    nfc_data.CFG1 = encoded_data.CFG1;
+    nfc_data.password = encoded_data.password;
+
+    return nfc_data;
+}
+
+HashSeed GetSeed(const NTAG215File& data) {
+    HashSeed seed{
+        .magic = data.write_counter,
+        .padding = {},
+        .uid_1 = data.uid,
+        .nintendo_id_1 = data.nintendo_id,
+        .uid_2 = data.uid,
+        .nintendo_id_2 = data.nintendo_id,
+        .keygen_salt = data.keygen_salt,
+    };
+
+    return seed;
+}
+
+std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed) {
+    const std::size_t seed_part1_len = sizeof(key.magic_bytes) - key.magic_length;
+    const std::size_t string_size = key.type_string.size();
+    std::vector<u8> output(string_size + seed_part1_len);
+
+    // Copy whole type string
+    memccpy(output.data(), key.type_string.data(), '\0', string_size);
+
+    // Append (16 - magic_length) from the input seed
+    memcpy(output.data() + string_size, &seed, seed_part1_len);
+
+    // Append all bytes from magicBytes
+    output.insert(output.end(), key.magic_bytes.begin(),
+                  key.magic_bytes.begin() + key.magic_length);
+
+    output.insert(output.end(), seed.uid_1.begin(), seed.uid_1.end());
+    output.emplace_back(seed.nintendo_id_1);
+    output.insert(output.end(), seed.uid_2.begin(), seed.uid_2.end());
+    output.emplace_back(seed.nintendo_id_2);
+
+    for (std::size_t i = 0; i < sizeof(seed.keygen_salt); i++) {
+        output.emplace_back(static_cast<u8>(seed.keygen_salt[i] ^ key.xor_pad[i]));
+    }
+
+    return output;
+}
+
+void CryptoInit(CryptoCtx& ctx, CryptoPP::HMAC<CryptoPP::SHA256>& hmac_ctx, const HmacKey& hmac_key,
+                const std::vector<u8>& seed) {
+    // Initialize context
+    ctx.used = false;
+    ctx.counter = 0;
+    ctx.buffer_size = sizeof(ctx.counter) + seed.size();
+    memcpy(ctx.buffer.data() + sizeof(u16), seed.data(), seed.size());
+
+    // Initialize HMAC context
+    hmac_ctx.SetKey(hmac_key.data(), hmac_key.size());
+}
+
+void CryptoStep(CryptoCtx& ctx, CryptoPP::HMAC<CryptoPP::SHA256>& hmac_ctx, DrgbOutput& output) {
+    // If used at least once, reinitialize the HMAC
+    if (ctx.used) {
+        hmac_ctx.Restart();
+    }
+
+    ctx.used = true;
+
+    // Store counter in big endian, and increment it
+    ctx.buffer[0] = static_cast<u8>(ctx.counter >> 8);
+    ctx.buffer[1] = static_cast<u8>(ctx.counter >> 0);
+    ctx.counter++;
+
+    // Do HMAC magic
+    hmac_ctx.CalculateDigest(
+        output.data(), reinterpret_cast<const unsigned char*>(ctx.buffer.data()), ctx.buffer_size);
+}
+
+DerivedKeys GenerateKey(const InternalKey& key, const NTAG215File& data) {
+    const auto seed = GetSeed(data);
+
+    // Generate internal seed
+    const std::vector<u8> internal_key = GenerateInternalKey(key, seed);
+
+    // Initialize context
+    CryptoCtx ctx{};
+    CryptoPP::HMAC<CryptoPP::SHA256> hmac_ctx;
+    CryptoInit(ctx, hmac_ctx, key.hmac_key, internal_key);
+
+    // Generate derived keys
+    DerivedKeys derived_keys{};
+    std::array<DrgbOutput, 2> temp{};
+    CryptoStep(ctx, hmac_ctx, temp[0]);
+    CryptoStep(ctx, hmac_ctx, temp[1]);
+    memcpy(&derived_keys, temp.data(), sizeof(DerivedKeys));
+
+    return derived_keys;
+}
+
+void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& out_data) {
+    CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption d2;
+    d2.SetKeyWithIV(keys.aes_key.data(), keys.aes_key.size(), keys.aes_iv.data(),
+                    keys.aes_iv.size());
+
+    constexpr std::size_t encrypted_data_size = HMAC_TAG_START - SETTINGS_START;
+    d2.ProcessData(reinterpret_cast<unsigned char*>(&out_data.settings),
+                   reinterpret_cast<const unsigned char*>(&in_data.settings), encrypted_data_size);
+
+    // Copy the rest of the data directly
+    out_data.uid = in_data.uid;
+    out_data.nintendo_id = in_data.nintendo_id;
+    out_data.lock_bytes = in_data.lock_bytes;
+    out_data.static_lock = in_data.static_lock;
+    out_data.compability_container = in_data.compability_container;
+
+    out_data.constant_value = in_data.constant_value;
+    out_data.write_counter = in_data.write_counter;
+
+    out_data.model_info = in_data.model_info;
+    out_data.keygen_salt = in_data.keygen_salt;
+    out_data.dynamic_lock = in_data.dynamic_lock;
+    out_data.CFG0 = in_data.CFG0;
+    out_data.CFG1 = in_data.CFG1;
+    out_data.password = in_data.password;
+}
+
+bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info) {
+    const auto citra_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir);
+    auto keys_file = FileUtil::IOFile(citra_keys_dir + "key_retail.bin", "rb");
+
+    if (!keys_file.IsOpen()) {
+        LOG_ERROR(Service_NFC, "No keys detected");
+        return false;
+    }
+
+    if (keys_file.ReadBytes(&unfixed_info, sizeof(InternalKey)) != sizeof(InternalKey)) {
+        LOG_ERROR(Service_NFC, "Failed to read unfixed_info");
+        return false;
+    }
+    if (keys_file.ReadBytes(&locked_secret, sizeof(InternalKey)) != sizeof(InternalKey)) {
+        LOG_ERROR(Service_NFC, "Failed to read locked-secret");
+        return false;
+    }
+
+    return true;
+}
+
+bool IsKeyAvailable() {
+    const auto citra_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir);
+    return FileUtil::Exists(citra_keys_dir + "key_retail.bin");
+}
+
+bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& tag_data) {
+    InternalKey locked_secret{};
+    InternalKey unfixed_info{};
+
+    if (!LoadKeys(locked_secret, unfixed_info)) {
+        return false;
+    }
+
+    // Generate keys
+    NTAG215File encoded_data = NfcDataToEncodedData(encrypted_tag_data);
+    const auto data_keys = GenerateKey(unfixed_info, encoded_data);
+    const auto tag_keys = GenerateKey(locked_secret, encoded_data);
+
+    // Decrypt
+    Cipher(data_keys, encoded_data, tag_data);
+
+    // Regenerate tag HMAC. Note: order matters, data HMAC depends on tag HMAC!
+    constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START;
+    CryptoPP::HMAC<CryptoPP::SHA256> tag_hmac(tag_keys.hmac_key.data(), sizeof(HmacKey));
+    tag_hmac.CalculateDigest(reinterpret_cast<unsigned char*>(&tag_data.hmac_tag),
+                             reinterpret_cast<const unsigned char*>(&tag_data.uid), input_length);
+
+    // Regenerate data HMAC
+    constexpr std::size_t input_length2 = DYNAMIC_LOCK_START - WRITE_COUNTER_START;
+    CryptoPP::HMAC<CryptoPP::SHA256> data_hmac(data_keys.hmac_key.data(), sizeof(HmacKey));
+    data_hmac.CalculateDigest(reinterpret_cast<unsigned char*>(&tag_data.hmac_data),
+                              reinterpret_cast<const unsigned char*>(&tag_data.write_counter),
+                              input_length2);
+
+    if (tag_data.hmac_data != encrypted_tag_data.user_memory.hmac_data) {
+        LOG_ERROR(Service_NFC, "hmac_data doesn't match");
+        return false;
+    }
+
+    if (tag_data.hmac_tag != encrypted_tag_data.user_memory.hmac_tag) {
+        LOG_ERROR(Service_NFC, "hmac_tag doesn't match");
+        return false;
+    }
+
+    return true;
+}
+
+bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_tag_data) {
+    InternalKey locked_secret{};
+    InternalKey unfixed_info{};
+
+    if (!LoadKeys(locked_secret, unfixed_info)) {
+        return false;
+    }
+
+    // Generate keys
+    const auto data_keys = GenerateKey(unfixed_info, tag_data);
+    const auto tag_keys = GenerateKey(locked_secret, tag_data);
+
+    NTAG215File encoded_tag_data{};
+
+    // Generate tag HMAC
+    constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START;
+    constexpr std::size_t input_length2 = HMAC_TAG_START - WRITE_COUNTER_START;
+    CryptoPP::HMAC<CryptoPP::SHA256> tag_hmac(tag_keys.hmac_key.data(), sizeof(HmacKey));
+    tag_hmac.CalculateDigest(reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag),
+                             reinterpret_cast<const unsigned char*>(&tag_data.uid), input_length);
+
+    // Generate data HMAC
+    CryptoPP::HMAC<CryptoPP::SHA256> data_hmac(data_keys.hmac_key.data(), sizeof(HmacKey));
+    data_hmac.Update(reinterpret_cast<const unsigned char*>(&tag_data.write_counter),
+                     input_length2);
+    data_hmac.Update(reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag),
+                     sizeof(HashData));
+    data_hmac.Update(reinterpret_cast<const unsigned char*>(&tag_data.uid), input_length);
+    data_hmac.Final(reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_data));
+
+    // Encrypt
+    Cipher(data_keys, tag_data, encoded_tag_data);
+
+    // Convert back to hardware
+    encrypted_tag_data = EncodedDataToNfcData(encoded_tag_data);
+
+    return true;
+}
+
+} // namespace Service::NFC::AmiiboCrypto
diff --git a/src/core/hle/service/nfc/amiibo_crypto.h b/src/core/hle/service/nfc/amiibo_crypto.h
new file mode 100644
index 000000000..f253b7372
--- /dev/null
+++ b/src/core/hle/service/nfc/amiibo_crypto.h
@@ -0,0 +1,109 @@
+// Copyright 2022 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <vector>
+
+#include "core/hle/service/nfc/nfc_types.h"
+
+namespace CryptoPP {
+class SHA256;
+template <class T>
+class HMAC;
+} // namespace CryptoPP
+
+namespace Service::NFC::AmiiboCrypto {
+// Byte locations in Service::NFC::NTAG215File
+constexpr std::size_t HMAC_DATA_START = 0x8;
+constexpr std::size_t SETTINGS_START = 0x2c;
+constexpr std::size_t WRITE_COUNTER_START = 0x29;
+constexpr std::size_t HMAC_TAG_START = 0x1B4;
+constexpr std::size_t UUID_START = 0x1D4;
+constexpr std::size_t DYNAMIC_LOCK_START = 0x208;
+
+using HmacKey = std::array<u8, 0x10>;
+using DrgbOutput = std::array<u8, 0x20>;
+
+struct HashSeed {
+    u16_be magic;
+    std::array<u8, 0xE> padding;
+    UniqueSerialNumber uid_1;
+    u8 nintendo_id_1;
+    UniqueSerialNumber uid_2;
+    u8 nintendo_id_2;
+    std::array<u8, 0x20> keygen_salt;
+};
+static_assert(sizeof(HashSeed) == 0x40, "HashSeed is an invalid size");
+
+struct InternalKey {
+    HmacKey hmac_key;
+    std::array<char, 0xE> type_string;
+    u8 reserved;
+    u8 magic_length;
+    std::array<u8, 0x10> magic_bytes;
+    std::array<u8, 0x20> xor_pad;
+};
+static_assert(sizeof(InternalKey) == 0x50, "InternalKey is an invalid size");
+static_assert(std::is_trivially_copyable_v<InternalKey>, "InternalKey must be trivially copyable.");
+
+struct CryptoCtx {
+    std::array<char, 480> buffer;
+    bool used;
+    std::size_t buffer_size;
+    s16 counter;
+};
+
+struct DerivedKeys {
+    std::array<u8, 0x10> aes_key;
+    std::array<u8, 0x10> aes_iv;
+    std::array<u8, 0x10> hmac_key;
+};
+static_assert(sizeof(DerivedKeys) == 0x30, "DerivedKeys is an invalid size");
+
+/// Validates that the amiibo file is not corrupted
+bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file);
+
+/// Validates that the amiibo file is not corrupted
+bool IsAmiiboValid(const NTAG215File& ntag_file);
+
+/// Converts from encrypted file format to encoded file format
+NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data);
+
+/// Converts from encoded file format to encrypted file format
+EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data);
+
+// Generates Seed needed for key derivation
+HashSeed GetSeed(const NTAG215File& data);
+
+// Middle step on the generation of derived keys
+std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed);
+
+// Initializes mbedtls context
+void CryptoInit(CryptoCtx& ctx, CryptoPP::HMAC<CryptoPP::SHA256>& hmac_ctx, const HmacKey& hmac_key,
+                const std::vector<u8>& seed);
+
+// Feeds data to mbedtls context to generate the derived key
+void CryptoStep(CryptoCtx& ctx, CryptoPP::HMAC<CryptoPP::SHA256>& hmac_ctx, DrgbOutput& output);
+
+// Generates the derived key from amiibo data
+DerivedKeys GenerateKey(const InternalKey& key, const NTAG215File& data);
+
+// Encodes or decodes amiibo data
+void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& out_data);
+
+/// Loads both amiibo keys from key_retail.bin
+bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info);
+
+/// Returns true if key_retail.bin exist
+bool IsKeyAvailable();
+
+/// Decodes encripted amiibo data returns true if output is valid
+bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& tag_data);
+
+/// Encodes plain amiibo data returns true if output is valid
+bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_tag_data);
+
+} // namespace Service::NFC::AmiiboCrypto
diff --git a/src/core/hle/service/nfc/nfc.cpp b/src/core/hle/service/nfc/nfc.cpp
index 388ea771f..dc1e650be 100644
--- a/src/core/hle/service/nfc/nfc.cpp
+++ b/src/core/hle/service/nfc/nfc.cpp
@@ -18,334 +18,662 @@ namespace Service::NFC {
 
 template <class Archive>
 void Module::serialize(Archive& ar, const unsigned int) {
-    ar& tag_in_range_event;
-    ar& tag_out_of_range_event;
-    ar& nfc_tag_state;
-    ar& nfc_status;
-    ar& amiibo_data;
-    ar& amiibo_in_range;
+    ar& nfc_mode;
+    ar& device;
 }
 SERIALIZE_IMPL(Module)
 
-struct TagInfo {
-    u16_le id_offset_size;
-    u8 unk1;
-    u8 unk2;
-    std::array<u8, 7> uuid;
-    INSERT_PADDING_BYTES(0x20);
-};
-static_assert(sizeof(TagInfo) == 0x2C, "TagInfo is an invalid size");
-
-struct AmiiboConfig {
-    u16_le lastwritedate_year;
-    u8 lastwritedate_month;
-    u8 lastwritedate_day;
-    u16_le write_counter;
-    std::array<u8, 3> characterID;
-    u8 series;
-    u16_le amiiboID;
-    u8 type;
-    u8 pagex4_byte3;
-    u16_le appdata_size;
-    INSERT_PADDING_BYTES(0x30);
-};
-static_assert(sizeof(AmiiboConfig) == 0x40, "AmiiboConfig is an invalid size");
-
-struct IdentificationBlockReply {
-    u16_le char_id;
-    u8 char_variant;
-    u8 series;
-    u16_le model_number;
-    u8 figure_type;
-    INSERT_PADDING_BYTES(0x2F);
-};
-static_assert(sizeof(IdentificationBlockReply) == 0x36,
-              "IdentificationBlockReply is an invalid size");
-
 void Module::Interface::Initialize(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp(ctx, 0x01, 1, 0);
-    u8 param = rp.Pop<u8>();
+    const auto communication_mode = rp.PopEnum<CommunicationMode>();
+
+    LOG_INFO(Service_NFC, "called, communication_mode={}", communication_mode);
 
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
-    if (nfc->nfc_tag_state != TagState::NotInitialized) {
-        LOG_ERROR(Service_NFC, "Invalid TagState {}", nfc->nfc_tag_state);
-        rb.Push(ResultCode(ErrCodes::CommandInvalidForState, ErrorModule::NFC,
-                           ErrorSummary::InvalidState, ErrorLevel::Status));
+
+    if (nfc->nfc_mode != CommunicationMode::NotInitialized) {
+        rb.Push(ResultCommandInvalidForState);
         return;
     }
 
-    nfc->nfc_tag_state = TagState::NotScanning;
+    ResultCode result = RESULT_SUCCESS;
+    switch (communication_mode) {
+    case CommunicationMode::Ntag:
+    case CommunicationMode::Amiibo:
+        nfc->device->Initialize();
+        break;
+    case CommunicationMode::TrainTag:
+        LOG_ERROR(Service_NFC, "CommunicationMode  {} not implemented", communication_mode);
+        break;
+    default:
+        result = ResultInvalidArgumentValue;
+        break;
+    }
 
-    rb.Push(RESULT_SUCCESS);
-    LOG_WARNING(Service_NFC, "(STUBBED) called, param={}", param);
+    if (result.IsSuccess()) {
+        nfc->nfc_mode = communication_mode;
+    }
+
+    rb.Push(result);
 }
 
 void Module::Interface::Shutdown(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp(ctx, 0x02, 1, 0);
-    u8 param = rp.Pop<u8>();
+    const auto communication_mode = rp.PopEnum<CommunicationMode>();
 
-    nfc->nfc_tag_state = TagState::NotInitialized;
+    LOG_INFO(Service_NFC, "called, communication_mode={}", communication_mode);
 
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
-    rb.Push(RESULT_SUCCESS);
-    LOG_WARNING(Service_NFC, "(STUBBED) called, param={}", param);
+
+    if (nfc->nfc_mode != communication_mode) {
+        rb.Push(ResultCommandInvalidForState);
+        return;
+    }
+
+    ResultCode result = RESULT_SUCCESS;
+    switch (communication_mode) {
+    case CommunicationMode::Ntag:
+    case CommunicationMode::Amiibo:
+        nfc->device->Finalize();
+        break;
+    case CommunicationMode::TrainTag:
+        LOG_ERROR(Service_NFC, "CommunicationMode  {} not implemented", communication_mode);
+        break;
+    default:
+        result = ResultInvalidArgumentValue;
+        break;
+    }
+
+    if (result.IsSuccess()) {
+        nfc->nfc_mode = CommunicationMode::NotInitialized;
+    }
+
+    rb.Push(result);
 }
 
 void Module::Interface::StartCommunication(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp(ctx, 0x03, 0, 0);
 
-    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
-    rb.Push(RESULT_SUCCESS);
     LOG_WARNING(Service_NFC, "(STUBBED) called");
+
+    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+    if (nfc->nfc_mode == CommunicationMode::TrainTag) {
+        LOG_ERROR(Service_NFC, "CommunicationMode  {} not implemented", nfc->nfc_mode);
+        rb.Push(RESULT_SUCCESS);
+        return;
+    }
+
+    // TODO: call start communication instead
+    const auto result = nfc->device->StartCommunication();
+    rb.Push(result);
 }
 
 void Module::Interface::StopCommunication(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp(ctx, 0x04, 0, 0);
 
-    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
-    rb.Push(RESULT_SUCCESS);
     LOG_WARNING(Service_NFC, "(STUBBED) called");
+
+    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+    if (nfc->nfc_mode == CommunicationMode::TrainTag) {
+        LOG_ERROR(Service_NFC, "CommunicationMode  {} not implemented", nfc->nfc_mode);
+        rb.Push(RESULT_SUCCESS);
+        return;
+    }
+
+    // TODO: call stop communication instead
+    const auto result = nfc->device->StopCommunication();
+    rb.Push(result);
 }
 
 void Module::Interface::StartTagScanning(Kernel::HLERequestContext& ctx) {
-    IPC::RequestParser rp(ctx, 0x05, 1, 0); // 0x00050040
+    IPC::RequestParser rp(ctx, 0x05, 1, 0);
     u16 in_val = rp.Pop<u16>();
 
+    LOG_INFO(Service_NFC, "called, in_val={:04x}", in_val);
+
+    ResultCode result = RESULT_SUCCESS;
+    switch (nfc->nfc_mode) {
+    case CommunicationMode::Ntag:
+    case CommunicationMode::Amiibo:
+        // in_val probably correlates to the tag protocol to be detected
+        result = nfc->device->StartDetection(TagProtocol::All);
+        break;
+    default:
+        result = ResultInvalidArgumentValue;
+        break;
+    }
+
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
-    if (nfc->nfc_tag_state != TagState::NotScanning &&
-        nfc->nfc_tag_state != TagState::TagOutOfRange) {
-        LOG_ERROR(Service_NFC, "Invalid TagState {}", nfc->nfc_tag_state);
-        rb.Push(ResultCode(ErrCodes::CommandInvalidForState, ErrorModule::NFC,
-                           ErrorSummary::InvalidState, ErrorLevel::Status));
-        return;
-    }
-
-    nfc->nfc_tag_state = TagState::Scanning;
-    nfc->SyncTagState();
-
-    rb.Push(RESULT_SUCCESS);
-    LOG_WARNING(Service_NFC, "(STUBBED) called, in_val={:04x}", in_val);
-}
-
-void Module::Interface::GetTagInfo(Kernel::HLERequestContext& ctx) {
-    IPC::RequestParser rp(ctx, 0x11, 0, 0);
-
-    if (nfc->nfc_tag_state != TagState::TagInRange &&
-        nfc->nfc_tag_state != TagState::TagDataLoaded && nfc->nfc_tag_state != TagState::Unknown6) {
-        LOG_ERROR(Service_NFC, "Invalid TagState {}", nfc->nfc_tag_state);
-        IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
-        rb.Push(ResultCode(ErrCodes::CommandInvalidForState, ErrorModule::NFC,
-                           ErrorSummary::InvalidState, ErrorLevel::Status));
-        return;
-    }
-
-    TagInfo tag_info{};
-    tag_info.uuid = nfc->amiibo_data.uuid;
-    tag_info.id_offset_size = static_cast<u16>(tag_info.uuid.size());
-    tag_info.unk1 = 0x0;
-    tag_info.unk2 = 0x2;
-
-    IPC::RequestBuilder rb = rp.MakeBuilder(12, 0);
-    rb.Push(RESULT_SUCCESS);
-    rb.PushRaw<TagInfo>(tag_info);
-    LOG_WARNING(Service_NFC, "(STUBBED) called");
-}
-
-void Module::Interface::GetAmiiboConfig(Kernel::HLERequestContext& ctx) {
-    IPC::RequestParser rp(ctx, 0x18, 0, 0);
-
-    AmiiboConfig amiibo_config{};
-    amiibo_config.lastwritedate_year = 2017;
-    amiibo_config.lastwritedate_month = 10;
-    amiibo_config.lastwritedate_day = 10;
-    amiibo_config.write_counter = 0x0;
-    std::memcpy(amiibo_config.characterID.data(), &nfc->amiibo_data.char_id,
-                sizeof(nfc->amiibo_data.char_id));
-    amiibo_config.series = nfc->amiibo_data.series;
-    amiibo_config.amiiboID = nfc->amiibo_data.model_number;
-    amiibo_config.type = nfc->amiibo_data.figure_type;
-    amiibo_config.pagex4_byte3 = 0x0;
-    amiibo_config.appdata_size = 0xD8;
-
-    IPC::RequestBuilder rb = rp.MakeBuilder(17, 0);
-    rb.Push(RESULT_SUCCESS);
-    rb.PushRaw<AmiiboConfig>(amiibo_config);
-    LOG_WARNING(Service_NFC, "(STUBBED) called");
+    rb.Push(result);
 }
 
 void Module::Interface::StopTagScanning(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp(ctx, 0x06, 0, 0);
 
-    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
-    if (nfc->nfc_tag_state == TagState::NotInitialized ||
-        nfc->nfc_tag_state == TagState::NotScanning) {
-        LOG_ERROR(Service_NFC, "Invalid TagState {}", nfc->nfc_tag_state);
-        rb.Push(ResultCode(ErrCodes::CommandInvalidForState, ErrorModule::NFC,
-                           ErrorSummary::InvalidState, ErrorLevel::Status));
-        return;
+    LOG_INFO(Service_NFC, "called");
+
+    ResultCode result = RESULT_SUCCESS;
+    switch (nfc->nfc_mode) {
+    case CommunicationMode::Ntag:
+    case CommunicationMode::Amiibo:
+        result = nfc->device->StopDetection();
+        break;
+    case CommunicationMode::TrainTag:
+        LOG_ERROR(Service_NFC, "CommunicationMode  {} not implemented", nfc->nfc_mode);
+        break;
+    default:
+        result = ResultCommandInvalidForState;
+        break;
     }
 
-    nfc->nfc_tag_state = TagState::NotScanning;
-
-    rb.Push(RESULT_SUCCESS);
-    LOG_DEBUG(Service_NFC, "called");
+    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+    rb.Push(result);
 }
 
 void Module::Interface::LoadAmiiboData(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp(ctx, 0x07, 0, 0);
 
-    // TODO(FearlessTobi): Add state checking when this function gets properly implemented
+    LOG_INFO(Service_NFC, "called");
 
-    nfc->nfc_tag_state = TagState::TagDataLoaded;
+    ResultCode result = RESULT_SUCCESS;
+    switch (nfc->nfc_mode) {
+    case CommunicationMode::Ntag:
+        result = nfc->device->Mount();
+        break;
+    case CommunicationMode::Amiibo:
+        result = nfc->device->MountAmiibo();
+        break;
+    default:
+        result = ResultCommandInvalidForState;
+        break;
+    }
 
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
-    rb.Push(RESULT_SUCCESS);
-    LOG_WARNING(Service_NFC, "(STUBBED) called");
+    rb.Push(result);
 }
 
 void Module::Interface::ResetTagScanState(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp(ctx, 0x08, 0, 0);
 
-    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
-    if (nfc->nfc_tag_state != TagState::TagDataLoaded && nfc->nfc_tag_state != TagState::Unknown6) {
-        LOG_ERROR(Service_NFC, "Invalid TagState {}", nfc->nfc_tag_state);
-        rb.Push(ResultCode(ErrCodes::CommandInvalidForState, ErrorModule::NFC,
-                           ErrorSummary::InvalidState, ErrorLevel::Status));
-        return;
+    LOG_INFO(Service_NFC, "called");
+
+    ResultCode result = RESULT_SUCCESS;
+    switch (nfc->nfc_mode) {
+    case CommunicationMode::Ntag:
+    case CommunicationMode::Amiibo:
+        result = nfc->device->ResetTagScanState();
+        break;
+    default:
+        result = ResultCommandInvalidForState;
+        break;
     }
 
-    nfc->nfc_tag_state = TagState::TagInRange;
-    nfc->SyncTagState();
+    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+    rb.Push(result);
+}
 
-    rb.Push(RESULT_SUCCESS);
-    LOG_DEBUG(Service_NFC, "called");
+void Module::Interface::UpdateStoredAmiiboData(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x09, 0, 0);
+
+    LOG_INFO(Service_NFC, "called");
+
+    ResultCode result = RESULT_SUCCESS;
+    switch (nfc->nfc_mode) {
+    case CommunicationMode::Ntag:
+        LOG_ERROR(Service_NFC, "CommunicationMode  {} not implemented", nfc->nfc_mode);
+        break;
+    case CommunicationMode::Amiibo:
+        result = nfc->device->Flush();
+        break;
+    default:
+        result = ResultCommandInvalidForState;
+        break;
+    }
+
+    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+    rb.Push(result);
 }
 
 void Module::Interface::GetTagInRangeEvent(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp(ctx, 0x0B, 0, 0);
 
-    if (nfc->nfc_tag_state != TagState::NotScanning) {
-        LOG_ERROR(Service_NFC, "Invalid TagState {}", nfc->nfc_tag_state);
+    LOG_INFO(Service_NFC, "called");
+
+    if (nfc->nfc_mode == CommunicationMode::TrainTag) {
+        LOG_ERROR(Service_NFC, "CommunicationMode  {} not implemented", nfc->nfc_mode);
         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
-        rb.Push(ResultCode(ErrCodes::CommandInvalidForState, ErrorModule::NFC,
-                           ErrorSummary::InvalidState, ErrorLevel::Status));
+        rb.Push(ResultCommandInvalidForState);
         return;
     }
 
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
     rb.Push(RESULT_SUCCESS);
-    rb.PushCopyObjects(nfc->tag_in_range_event);
-    LOG_DEBUG(Service_NFC, "called");
+    rb.PushCopyObjects(nfc->device->GetActivateEvent());
 }
 
 void Module::Interface::GetTagOutOfRangeEvent(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp(ctx, 0x0C, 0, 0);
 
-    if (nfc->nfc_tag_state != TagState::NotScanning) {
-        LOG_ERROR(Service_NFC, "Invalid TagState {}", nfc->nfc_tag_state);
+    LOG_INFO(Service_NFC, "called");
+
+    if (nfc->nfc_mode == CommunicationMode::TrainTag) {
+        LOG_ERROR(Service_NFC, "CommunicationMode  {} not implemented", nfc->nfc_mode);
         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
-        rb.Push(ResultCode(ErrCodes::CommandInvalidForState, ErrorModule::NFC,
-                           ErrorSummary::InvalidState, ErrorLevel::Status));
+        rb.Push(ResultCommandInvalidForState);
         return;
     }
 
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
     rb.Push(RESULT_SUCCESS);
-    rb.PushCopyObjects(nfc->tag_out_of_range_event);
-    LOG_DEBUG(Service_NFC, "called");
+    rb.PushCopyObjects(nfc->device->GetDeactivateEvent());
 }
 
 void Module::Interface::GetTagState(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp(ctx, 0x0D, 0, 0);
+    DeviceState state = DeviceState::NotInitialized;
+
+    LOG_DEBUG(Service_NFC, "called");
+
+    if (nfc->nfc_mode == CommunicationMode::TrainTag) {
+        LOG_ERROR(Service_NFC, "CommunicationMode  {} not implemented", nfc->nfc_mode);
+    } else {
+        state = nfc->device->GetCurrentState();
+    }
 
     IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
     rb.Push(RESULT_SUCCESS);
-    rb.PushEnum(nfc->nfc_tag_state);
-    LOG_DEBUG(Service_NFC, "called");
+    rb.PushEnum(state);
 }
 
 void Module::Interface::CommunicationGetStatus(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp(ctx, 0x0F, 0, 0);
 
-    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
-    rb.Push(RESULT_SUCCESS);
-    rb.PushEnum(nfc->nfc_status);
-    LOG_DEBUG(Service_NFC, "(STUBBED) called");
-}
+    LOG_DEBUG(Service_NFC, "called");
 
-void Module::Interface::Unknown0x1A(Kernel::HLERequestContext& ctx) {
-    IPC::RequestParser rp(ctx, 0x1A, 0, 0);
-
-    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
-    if (nfc->nfc_tag_state != TagState::TagInRange) {
-        LOG_ERROR(Service_NFC, "Invalid TagState {}", nfc->nfc_tag_state);
-        rb.Push(ResultCode(ErrCodes::CommandInvalidForState, ErrorModule::NFC,
-                           ErrorSummary::InvalidState, ErrorLevel::Status));
+    if (nfc->nfc_mode == CommunicationMode::TrainTag) {
+        LOG_ERROR(Service_NFC, "CommunicationMode  {} not implemented", nfc->nfc_mode);
+        IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
+        rb.Push(RESULT_SUCCESS);
+        rb.PushEnum(CommunicationState::Idle);
         return;
     }
 
-    nfc->nfc_tag_state = TagState::Unknown6;
+    CommunicationState status{};
+    const auto result = nfc->device->GetCommunicationStatus(status);
+    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
+    rb.Push(result);
+    rb.PushEnum(status);
+}
 
+void Module::Interface::GetTagInfo2(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x10, 0, 0);
+
+    LOG_INFO(Service_NFC, "called");
+
+    if (nfc->nfc_mode == CommunicationMode::TrainTag) {
+        LOG_ERROR(Service_NFC, "CommunicationMode  {} not implemented", nfc->nfc_mode);
+        IPC::RequestBuilder rb = rp.MakeBuilder(26, 0);
+        rb.Push(RESULT_SUCCESS);
+        rb.PushRaw<TagInfo2>({});
+        return;
+    }
+
+    TagInfo2 tag_info{};
+    const auto result = nfc->device->GetTagInfo2(tag_info);
+    IPC::RequestBuilder rb = rp.MakeBuilder(26, 0);
+    rb.Push(result);
+    rb.PushRaw<TagInfo2>(tag_info);
+}
+
+void Module::Interface::GetTagInfo(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x11, 0, 0);
+
+    LOG_INFO(Service_NFC, "called");
+
+    if (nfc->nfc_mode == CommunicationMode::TrainTag) {
+        LOG_ERROR(Service_NFC, "CommunicationMode  {} not implemented", nfc->nfc_mode);
+        IPC::RequestBuilder rb = rp.MakeBuilder(12, 0);
+        rb.Push(RESULT_SUCCESS);
+        rb.PushRaw<TagInfo>({});
+        return;
+    }
+
+    TagInfo tag_info{};
+    const auto result = nfc->device->GetTagInfo(tag_info);
+    IPC::RequestBuilder rb = rp.MakeBuilder(12, 0);
+    rb.Push(result);
+    rb.PushRaw<TagInfo>(tag_info);
+}
+
+void Module::Interface::CommunicationGetResult(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x12, 0, 0);
+
+    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
     rb.Push(RESULT_SUCCESS);
-    LOG_DEBUG(Service_NFC, "called");
+    rb.Push(0);
+    LOG_WARNING(Service_NFC, "(STUBBED) called");
+}
+
+void Module::Interface::OpenAppData(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x13, 1, 0);
+    u32 access_id = rp.Pop<u32>();
+
+    LOG_INFO(Service_NFC, "called, access_id={}", access_id);
+
+    if (nfc->nfc_mode != CommunicationMode::Amiibo) {
+        IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+        rb.Push(ResultCommandInvalidForState);
+        return;
+    }
+
+    const auto result = nfc->device->OpenApplicationArea(access_id);
+
+    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+    rb.Push(result);
+}
+
+void Module::Interface::InitializeWriteAppData(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x14, 18, 2);
+    u32 access_id = rp.Pop<u32>();
+    [[maybe_unused]] u32 size = rp.Pop<u32>();
+    std::vector<u8> buffer = rp.PopStaticBuffer();
+
+    LOG_CRITICAL(Service_NFC, "called, size={}", size);
+
+    if (nfc->nfc_mode != CommunicationMode::Amiibo) {
+        IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+        rb.Push(ResultCommandInvalidForState);
+        return;
+    }
+
+    const auto result = nfc->device->CreateApplicationArea(access_id, buffer);
+
+    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+    rb.Push(result);
+}
+
+void Module::Interface::ReadAppData(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x15, 0, 0);
+
+    LOG_INFO(Service_NFC, "called");
+
+    if (nfc->nfc_mode != CommunicationMode::Amiibo) {
+        IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+        rb.Push(ResultCommandInvalidForState);
+        return;
+    }
+
+    std::vector<u8> buffer(sizeof(ApplicationArea));
+    const auto result = nfc->device->GetApplicationArea(buffer);
+
+    IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
+    rb.Push(result);
+    rb.PushStaticBuffer(buffer, 0);
+}
+
+void Module::Interface::WriteAppData(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x16, 12, 2);
+    [[maybe_unused]] u32 size = rp.Pop<u32>();
+    std::vector<u8> tag_uuid_info = rp.PopStaticBuffer();
+    std::vector<u8> buffer = rp.PopStaticBuffer();
+
+    LOG_CRITICAL(Service_NFC, "called, size={}", size);
+
+    if (nfc->nfc_mode != CommunicationMode::Amiibo) {
+        IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+        rb.Push(ResultCommandInvalidForState);
+        return;
+    }
+
+    const auto result = nfc->device->SetApplicationArea(buffer);
+
+    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+    rb.Push(result);
+}
+
+void Module::Interface::GetRegisterInfo(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x17, 0, 0);
+
+    LOG_INFO(Service_NFC, "called");
+
+    if (nfc->nfc_mode != CommunicationMode::Amiibo) {
+        IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+        rb.Push(ResultCommandInvalidForState);
+        return;
+    }
+
+    RegisterInfo settings_info{};
+    const auto result = nfc->device->GetRegisterInfo(settings_info);
+
+    IPC::RequestBuilder rb = rp.MakeBuilder(43, 0);
+    rb.Push(result);
+    rb.PushRaw<RegisterInfo>(settings_info);
+}
+
+void Module::Interface::GetCommonInfo(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x18, 0, 0);
+
+    LOG_INFO(Service_NFC, "called");
+
+    if (nfc->nfc_mode != CommunicationMode::Amiibo) {
+        IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+        rb.Push(ResultCommandInvalidForState);
+        return;
+    }
+
+    CommonInfo amiibo_config{};
+    const auto result = nfc->device->GetCommonInfo(amiibo_config);
+
+    IPC::RequestBuilder rb = rp.MakeBuilder(17, 0);
+    rb.Push(result);
+    rb.PushRaw<CommonInfo>(amiibo_config);
+}
+
+void Module::Interface::GetAppDataInitStruct(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x19, 0, 0);
+
+    LOG_INFO(Service_NFC, "called");
+
+    if (nfc->nfc_mode != CommunicationMode::Amiibo) {
+        IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+        rb.Push(ResultCommandInvalidForState);
+        return;
+    }
+
+    using InitialStruct = std::array<u8, 0x3c>;
+    InitialStruct empty{};
+
+    IPC::RequestBuilder rb = rp.MakeBuilder(16, 0);
+    rb.Push(RESULT_SUCCESS);
+    rb.PushRaw<InitialStruct>(empty);
+}
+
+void Module::Interface::LoadAmiiboPartially(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x1A, 0, 0);
+
+    LOG_INFO(Service_NFC, "called");
+
+    ResultCode result = RESULT_SUCCESS;
+    switch (nfc->nfc_mode) {
+    case CommunicationMode::Ntag:
+        result = nfc->device->PartiallyMount();
+        break;
+    case CommunicationMode::Amiibo:
+        result = nfc->device->PartiallyMountAmiibo();
+        break;
+    default:
+        result = ResultCommandInvalidForState;
+        break;
+    }
+
+    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+    rb.Push(result);
 }
 
 void Module::Interface::GetIdentificationBlock(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp(ctx, 0x1B, 0, 0);
 
-    if (nfc->nfc_tag_state != TagState::TagDataLoaded && nfc->nfc_tag_state != TagState::Unknown6) {
-        LOG_ERROR(Service_NFC, "Invalid TagState {}", nfc->nfc_tag_state);
+    LOG_INFO(Service_NFC, "called");
+
+    if (nfc->nfc_mode != CommunicationMode::Amiibo) {
         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
-        rb.Push(ResultCode(ErrCodes::CommandInvalidForState, ErrorModule::NFC,
-                           ErrorSummary::InvalidState, ErrorLevel::Status));
+        rb.Push(ResultCommandInvalidForState);
         return;
     }
 
-    IdentificationBlockReply identification_block_reply{};
-    identification_block_reply.char_id = nfc->amiibo_data.char_id;
-    identification_block_reply.char_variant = nfc->amiibo_data.char_variant;
-    identification_block_reply.series = nfc->amiibo_data.series;
-    identification_block_reply.model_number = nfc->amiibo_data.model_number;
-    identification_block_reply.figure_type = nfc->amiibo_data.figure_type;
+    ModelInfo model_info{};
+    const auto result = nfc->device->GetModelInfo(model_info);
 
     IPC::RequestBuilder rb = rp.MakeBuilder(0x1F, 0);
+    rb.Push(result);
+    rb.PushRaw<ModelInfo>(model_info);
+}
+
+void Module::Interface::Format(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x401, 3, 2);
+    [[maybe_unused]] u32 unknown1 = rp.Pop<u32>();
+    [[maybe_unused]] u32 unknown2 = rp.Pop<u32>();
+    [[maybe_unused]] u32 unknown3 = rp.Pop<u32>();
+    [[maybe_unused]] std::vector<u8> buffer = rp.PopStaticBuffer();
+
+    const auto result = nfc->device->Format();
+
+    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+    rb.Push(result);
+    LOG_WARNING(Service_NFC, "(STUBBED) called");
+}
+
+void Module::Interface::GetAdminInfo(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x402, 0, 0);
+
+    LOG_INFO(Service_NFC, "called");
+
+    if (nfc->nfc_mode != CommunicationMode::Amiibo) {
+        IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+        rb.Push(ResultCommandInvalidForState);
+        return;
+    }
+
+    AdminInfo admin_info{};
+    const auto result = nfc->device->GetAdminInfo(admin_info);
+
+    IPC::RequestBuilder rb = rp.MakeBuilder(17, 0);
+    rb.Push(result);
+    rb.PushRaw<AdminInfo>(admin_info);
+}
+
+void Module::Interface::GetEmptyRegisterInfo(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x403, 0, 0);
+
+    LOG_INFO(Service_NFC, "called");
+
+    if (nfc->nfc_mode != CommunicationMode::Amiibo) {
+        IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+        rb.Push(ResultCommandInvalidForState);
+        return;
+    }
+
+    IPC::RequestBuilder rb = rp.MakeBuilder(43, 0);
     rb.Push(RESULT_SUCCESS);
-    rb.PushRaw<IdentificationBlockReply>(identification_block_reply);
-    LOG_DEBUG(Service_NFC, "called");
+    rb.PushRaw<RegisterInfo>({});
+}
+
+void Module::Interface::SetRegisterInfo(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x404, 41, 0);
+    const auto register_info = rp.PopRaw<RegisterInfoPrivate>();
+
+    LOG_INFO(Service_NFC, "called");
+
+    if (nfc->nfc_mode != CommunicationMode::Amiibo) {
+        IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+        rb.Push(ResultCommandInvalidForState);
+        return;
+    }
+
+    const auto result = nfc->device->SetRegisterInfoPrivate(register_info);
+
+    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+    rb.Push(result);
+}
+
+void Module::Interface::DeleteRegisterInfo(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x405, 0, 0);
+
+    LOG_INFO(Service_NFC, "called");
+
+    if (nfc->nfc_mode != CommunicationMode::Amiibo) {
+        IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+        rb.Push(ResultCommandInvalidForState);
+        return;
+    }
+
+    const auto result = nfc->device->DeleteRegisterInfo();
+
+    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+    rb.Push(result);
+}
+
+void Module::Interface::DeleteApplicationArea(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x406, 0, 0);
+
+    LOG_INFO(Service_NFC, "called");
+
+    if (nfc->nfc_mode != CommunicationMode::Amiibo) {
+        IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+        rb.Push(ResultCommandInvalidForState);
+        return;
+    }
+
+    const auto result = nfc->device->DeleteApplicationArea();
+
+    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+    rb.Push(result);
+}
+
+void Module::Interface::ExistsApplicationArea(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x407, 0, 0);
+
+    if (nfc->nfc_mode != CommunicationMode::Amiibo) {
+        IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+        rb.Push(ResultCommandInvalidForState);
+        return;
+    }
+
+    bool has_application_area = false;
+    const auto result = nfc->device->ApplicationAreaExist(has_application_area);
+
+    IPC::RequestBuilder rb = rp.MakeBuilder(0x2, 0);
+    rb.Push(result);
+    rb.Push(has_application_area);
+    LOG_INFO(Service_NFC, "called");
 }
 
 std::shared_ptr<Module> Module::Interface::GetModule() const {
     return nfc;
 }
 
-void Module::Interface::LoadAmiibo(const AmiiboData& amiibo_data) {
+bool Module::Interface::IsSearchingForAmiibos() {
     std::lock_guard lock(HLE::g_hle_lock);
-    nfc->amiibo_data = amiibo_data;
-    nfc->amiibo_in_range = true;
-    nfc->SyncTagState();
+
+    const auto state = nfc->device->GetCurrentState();
+    return state == DeviceState::SearchingForTag;
+}
+
+bool Module::Interface::IsTagActive() {
+    std::lock_guard lock(HLE::g_hle_lock);
+
+    const auto state = nfc->device->GetCurrentState();
+    return state == DeviceState::TagFound || state == DeviceState::TagMounted ||
+           state == DeviceState::TagPartiallyMounted;
+}
+bool Module::Interface::LoadAmiibo(const std::string& fullpath) {
+    std::lock_guard lock(HLE::g_hle_lock);
+    return nfc->device->LoadAmiibo(fullpath);
 }
 
 void Module::Interface::RemoveAmiibo() {
     std::lock_guard lock(HLE::g_hle_lock);
-    nfc->amiibo_in_range = false;
-    nfc->SyncTagState();
-}
-
-void Module::SyncTagState() {
-    if (amiibo_in_range &&
-        (nfc_tag_state == TagState::TagOutOfRange || nfc_tag_state == TagState::Scanning)) {
-        // TODO (wwylele): Should TagOutOfRange->TagInRange transition only happen on the same tag
-        // detected on Scanning->TagInRange?
-        nfc_tag_state = TagState::TagInRange;
-        tag_in_range_event->Signal();
-    } else if (!amiibo_in_range &&
-               (nfc_tag_state == TagState::TagInRange || nfc_tag_state == TagState::TagDataLoaded ||
-                nfc_tag_state == TagState::Unknown6)) {
-        // TODO (wwylele): If a tag is removed during TagDataLoaded/Unknown6, should this event
-        // signals early?
-        nfc_tag_state = TagState::TagOutOfRange;
-        tag_out_of_range_event->Signal();
-    }
+    nfc->device->UnloadAmiibo();
 }
 
 Module::Interface::Interface(std::shared_ptr<Module> nfc, const char* name, u32 max_session)
@@ -354,10 +682,7 @@ Module::Interface::Interface(std::shared_ptr<Module> nfc, const char* name, u32
 Module::Interface::~Interface() = default;
 
 Module::Module(Core::System& system) {
-    tag_in_range_event =
-        system.Kernel().CreateEvent(Kernel::ResetType::OneShot, "NFC::tag_in_range_event");
-    tag_out_of_range_event =
-        system.Kernel().CreateEvent(Kernel::ResetType::OneShot, "NFC::tag_out_range_event");
+    device = std::make_shared<NfcDevice>(system);
 }
 
 Module::~Module() = default;
diff --git a/src/core/hle/service/nfc/nfc.h b/src/core/hle/service/nfc/nfc.h
index 51fe20b76..8117a9dce 100644
--- a/src/core/hle/service/nfc/nfc.h
+++ b/src/core/hle/service/nfc/nfc.h
@@ -8,6 +8,8 @@
 #include <memory>
 #include <boost/serialization/binary_object.hpp>
 #include "common/common_types.h"
+#include "core/hle/service/nfc/nfc_device.h"
+#include "core/hle/service/nfc/nfc_types.h"
 #include "core/hle/service/service.h"
 
 namespace Core {
@@ -20,45 +22,11 @@ class Event;
 
 namespace Service::NFC {
 
-namespace ErrCodes {
-enum {
-    CommandInvalidForState = 512,
-};
-} // namespace ErrCodes
-
-// TODO(FearlessTobi): Add more members to this struct
-struct AmiiboData {
-    std::array<u8, 7> uuid;
-    INSERT_PADDING_BYTES(0x4D);
-    u16_le char_id;
-    u8 char_variant;
-    u8 figure_type;
-    u16_be model_number;
-    u8 series;
-    INSERT_PADDING_BYTES(0x1C1);
-
-private:
-    template <class Archive>
-    void serialize(Archive& ar, const unsigned int) {
-        ar& boost::serialization::make_binary_object(this, sizeof(AmiiboData));
-    }
-    friend class boost::serialization::access;
-};
-static_assert(sizeof(AmiiboData) == 0x21C, "AmiiboData is an invalid size");
-
-enum class TagState : u8 {
+enum class CommunicationMode : u8 {
     NotInitialized = 0,
-    NotScanning = 1,
-    Scanning = 2,
-    TagInRange = 3,
-    TagOutOfRange = 4,
-    TagDataLoaded = 5,
-    Unknown6 = 6,
-};
-
-enum class CommunicationStatus : u8 {
-    AttemptInitialize = 1,
-    NfcInitialized = 2,
+    Ntag = 1,
+    Amiibo = 2,
+    TrainTag = 3,
 };
 
 class Module final {
@@ -73,7 +41,11 @@ public:
 
         std::shared_ptr<Module> GetModule() const;
 
-        void LoadAmiibo(const AmiiboData& amiibo_data);
+        bool IsSearchingForAmiibos();
+
+        bool IsTagActive();
+
+        bool LoadAmiibo(const std::string& fullpath);
 
         void RemoveAmiibo();
 
@@ -82,7 +54,7 @@ public:
          * NFC::Initialize service function
          *  Inputs:
          *      0 : Header code [0x00010040]
-         *      1 : (u8) unknown parameter. Can be either value 0x1 or 0x2
+         *      1 : (u8) CommunicationMode. Can be either value 0x1, 0x2 or 0x3
          *  Outputs:
          *      1 : Result of function, 0 on success, otherwise error code
          */
@@ -92,7 +64,7 @@ public:
          * NFC::Shutdown service function
          *  Inputs:
          *      0 : Header code [0x00020040]
-         *      1 : (u8) unknown parameter
+         *      1 : (u8) CommunicationMode.
          *  Outputs:
          *      1 : Result of function, 0 on success, otherwise error code
          */
@@ -153,6 +125,15 @@ public:
          */
         void ResetTagScanState(Kernel::HLERequestContext& ctx);
 
+        /**
+         * NFC::UpdateStoredAmiiboData service function
+         *  Inputs:
+         *      0 : Header code [0x00090002]
+         *  Outputs:
+         *      1 : Result of function, 0 on success, otherwise error code
+         */
+        void UpdateStoredAmiiboData(Kernel::HLERequestContext& ctx);
+
         /**
          * NFC::GetTagInRangeEvent service function
          *  Inputs:
@@ -195,6 +176,16 @@ public:
          */
         void CommunicationGetStatus(Kernel::HLERequestContext& ctx);
 
+        /**
+         * NFC::GetTagInfo2 service function
+         *  Inputs:
+         *      0 : Header code [0x00100000]
+         *  Outputs:
+         *      1 : Result of function, 0 on success, otherwise error code
+         *   2-26 : 0x60-byte struct
+         */
+        void GetTagInfo2(Kernel::HLERequestContext& ctx);
+
         /**
          * NFC::GetTagInfo service function
          *  Inputs:
@@ -206,23 +197,102 @@ public:
         void GetTagInfo(Kernel::HLERequestContext& ctx);
 
         /**
-         * NFC::GetAmiiboConfig service function
+         * NFC::GetTagInfo service function
+         *  Inputs:
+         *      0 : Header code [0x00120000]
+         *  Outputs:
+         *      1 : Result of function, 0 on success, otherwise error code
+         *      2 : Output NFC-adapter result-code
+         */
+        void CommunicationGetResult(Kernel::HLERequestContext& ctx);
+
+        /**
+         * NFC::OpenAppData service function
+         *  Inputs:
+         *      0 : Header code [0x00130040]
+         *      1 : (u32) App ID
+         *  Outputs:
+         *      1 : Result of function, 0 on success, otherwise error code
+         */
+        void OpenAppData(Kernel::HLERequestContext& ctx);
+
+        /**
+         * NFC::InitializeWriteAppData service function
+         *  Inputs:
+         *      0 : Header code [0x00140384]
+         *      1 : (u32) App ID
+         *      2 : Size
+         *   3-14 : 0x30-byte zeroed-out struct
+         *     15 : 0x20, PID translate-header for kernel
+         *     16 : PID written by kernel
+         *     17 : (Size << 14) | 2
+         *     18 : Pointer to input buffer
+         *  Outputs:
+         *      1 : Result of function, 0 on success, otherwise error code
+         */
+        void InitializeWriteAppData(Kernel::HLERequestContext& ctx);
+
+        /**
+         * NFC::ReadAppData service function
+         *  Inputs:
+         *      0 : Header code [0x00150040]
+         *      1 : Size (unused? Hard-coded to be 0xD8)
+         *  Outputs:
+         *      1 : Result of function, 0 on success, otherwise error code
+         */
+        void ReadAppData(Kernel::HLERequestContext& ctx);
+
+        /**
+         * NFC::WriteAppData service function
+         *  Inputs:
+         *      0 : Header code [0x00160242]
+         *      1 : Size
+         *    2-9 : AmiiboWriteRequest struct (see above)
+         *     10 : (Size << 14) | 2
+         *     11 : Pointer to input appdata buffer
+         *  Outputs:
+         *      1 : Result of function, 0 on success, otherwise error code
+         */
+        void WriteAppData(Kernel::HLERequestContext& ctx);
+
+        /**
+         * NFC::GetRegisterInfo service function
+         *  Inputs:
+         *      0 : Header code [0x00170000]
+         *  Outputs:
+         *      1 : Result of function, 0 on success, otherwise error code
+         *   2-43 : AmiiboSettings struct (see above)
+         */
+        void GetRegisterInfo(Kernel::HLERequestContext& ctx);
+
+        /**
+         * NFC::GetCommonInfo service function
          *  Inputs:
          *      0 : Header code [0x00180000]
          *  Outputs:
          *      1 : Result of function, 0 on success, otherwise error code
          *   2-17 : 0x40-byte config struct
          */
-        void GetAmiiboConfig(Kernel::HLERequestContext& ctx);
+        void GetCommonInfo(Kernel::HLERequestContext& ctx);
 
         /**
-         * NFC::Unknown0x1A service function
+         * NFC::GetAppDataInitStruct service function
+         *  Inputs:
+         *      0 : Header code [0x00180000]
+         *  Outputs:
+         *      1 : Result of function, 0 on success, otherwise error code
+         *   2-16 : 0x3C-byte config struct
+         */
+        void GetAppDataInitStruct(Kernel::HLERequestContext& ctx);
+
+        /**
+         * NFC::LoadAmiiboPartially service function
          *  Inputs:
          *      0 : Header code [0x001A0000]
          *  Outputs:
          *      1 : Result of function, 0 on success, otherwise error code
          */
-        void Unknown0x1A(Kernel::HLERequestContext& ctx);
+        void LoadAmiiboPartially(Kernel::HLERequestContext& ctx);
 
         /**
          * NFC::GetIdentificationBlock service function
@@ -234,21 +304,77 @@ public:
          */
         void GetIdentificationBlock(Kernel::HLERequestContext& ctx);
 
+        /**
+         * NFC::Format service function
+         *  Inputs:
+         *      0 : Header code [0x040100C2]
+         *  Outputs:
+         *      1 : Result of function, 0 on success, otherwise error code
+         */
+        void Format(Kernel::HLERequestContext& ctx);
+
+        /**
+         * NFC::GetAdminInfo service function
+         *  Inputs:
+         *      0 : Header code [0x04020000]
+         *  Outputs:
+         *      1 : Result of function, 0 on success, otherwise error code
+         */
+        void GetAdminInfo(Kernel::HLERequestContext& ctx);
+
+        /**
+         * NFC::GetEmptyRegisterInfo service function
+         *  Inputs:
+         *      0 : Header code [0x04030000]
+         *  Outputs:
+         *      1 : Result of function, 0 on success, otherwise error code
+         */
+        void GetEmptyRegisterInfo(Kernel::HLERequestContext& ctx);
+
+        /**
+         * NFC::SetRegisterInfo service function
+         *  Inputs:
+         *      0 : Header code [0x04040A40]
+         *  Outputs:
+         *      1 : Result of function, 0 on success, otherwise error code
+         */
+        void SetRegisterInfo(Kernel::HLERequestContext& ctx);
+
+        /**
+         * NFC::DeleteRegisterInfo service function
+         *  Inputs:
+         *      0 : Header code [0x04050000]
+         *  Outputs:
+         *      1 : Result of function, 0 on success, otherwise error code
+         */
+        void DeleteRegisterInfo(Kernel::HLERequestContext& ctx);
+
+        /**
+         * NFC::DeleteApplicationArea service function
+         *  Inputs:
+         *      0 : Header code [0x04060000]
+         *  Outputs:
+         *      1 : Result of function, 0 on success, otherwise error code
+         */
+        void DeleteApplicationArea(Kernel::HLERequestContext& ctx);
+
+        /**
+         * NFC::ExistsApplicationArea service function
+         *  Inputs:
+         *      0 : Header code [0x04070000]
+         *  Outputs:
+         *      1 : Result of function, 0 on success, otherwise error code
+         */
+        void ExistsApplicationArea(Kernel::HLERequestContext& ctx);
+
     protected:
         std::shared_ptr<Module> nfc;
     };
 
 private:
-    // Sync nfc_tag_state with amiibo_in_range and signal events on state change.
-    void SyncTagState();
+    CommunicationMode nfc_mode = CommunicationMode::NotInitialized;
 
-    std::shared_ptr<Kernel::Event> tag_in_range_event;
-    std::shared_ptr<Kernel::Event> tag_out_of_range_event;
-    TagState nfc_tag_state = TagState::NotInitialized;
-    CommunicationStatus nfc_status = CommunicationStatus::NfcInitialized;
-
-    AmiiboData amiibo_data{};
-    bool amiibo_in_range = false;
+    std::shared_ptr<NfcDevice> device = nullptr;
 
     template <class Archive>
     void serialize(Archive& ar, const unsigned int);
diff --git a/src/core/hle/service/nfc/nfc_device.cpp b/src/core/hle/service/nfc/nfc_device.cpp
new file mode 100644
index 000000000..4e0266023
--- /dev/null
+++ b/src/core/hle/service/nfc/nfc_device.cpp
@@ -0,0 +1,1110 @@
+// Copyright 2022 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <array>
+#include <chrono>
+#include <boost/crc.hpp>
+#include <cryptopp/osrng.h>
+
+#include "common/logging/log.h"
+#include "common/string_util.h"
+#include "core/core.h"
+#include "core/hle/kernel/shared_page.h"
+#include "core/hle/service/nfc/amiibo_crypto.h"
+#include "core/hle/service/nfc/nfc_device.h"
+
+SERVICE_CONSTRUCT_IMPL(Service::NFC::NfcDevice)
+
+namespace Service::NFC {
+template <class Archive>
+void NfcDevice::serialize(Archive& ar, const unsigned int) {
+    ar& tag_in_range_event;
+    ar& tag_out_of_range_event;
+    ar& is_initalized;
+    ar& is_data_moddified;
+    ar& is_app_area_open;
+    ar& is_plain_amiibo;
+    ar& is_write_protected;
+    ar& is_tag_in_range;
+    ar& allowed_protocols;
+    ar& device_state;
+    ar& connection_state;
+    ar& communication_state;
+    ar& amiibo_filename;
+    ar& tag;
+    ar& encrypted_tag;
+}
+SERIALIZE_IMPL(NfcDevice)
+
+/// The interval at which the amiibo will be removed automatically 1.5s
+static constexpr u64 amiibo_removal_interval_us = 268 * 1000 * 1000;
+
+NfcDevice::NfcDevice(Core::System& system) {
+    tag_in_range_event =
+        system.Kernel().CreateEvent(Kernel::ResetType::OneShot, "NFC::tag_in_range_event");
+    tag_out_of_range_event =
+        system.Kernel().CreateEvent(Kernel::ResetType::OneShot, "NFC::tag_out_range_event");
+
+    remove_amiibo_event = system.CoreTiming().RegisterEvent(
+        "remove amiibo event",
+        [this](std::uintptr_t user_data, s64 cycles_late) { UnloadAmiibo(); });
+}
+
+NfcDevice::~NfcDevice() = default;
+
+bool NfcDevice::LoadAmiibo(std::string filename) {
+    FileUtil::IOFile amiibo_file(filename, "rb");
+
+    if (!is_initalized) {
+        LOG_ERROR(Service_NFC, "Not initialized");
+        return false;
+    }
+
+    if (device_state != DeviceState::SearchingForTag) {
+        LOG_ERROR(Service_NFC, "Game is not looking for amiibos, current state {}", device_state);
+        return false;
+    }
+
+    if (!amiibo_file.IsOpen()) {
+        LOG_ERROR(Service_NFC, "Could not open amiibo file \"{}\"", filename);
+        return false;
+    }
+
+    if (!amiibo_file.ReadBytes(&tag.file, sizeof(tag.file))) {
+        LOG_ERROR(Service_NFC, "Could not read amiibo data from file \"{}\"", filename);
+        tag.file = {};
+        return false;
+    }
+
+    // TODO: Filter by allowed_protocols here
+    is_plain_amiibo = AmiiboCrypto::IsAmiiboValid(tag.file);
+    is_write_protected = false;
+
+    amiibo_filename = filename;
+    device_state = DeviceState::TagFound;
+    is_tag_in_range = true;
+    tag_out_of_range_event->Clear();
+    tag_in_range_event->Signal();
+
+    Core::System::GetInstance().CoreTiming().ScheduleEvent(amiibo_removal_interval_us,
+                                                           remove_amiibo_event);
+
+    // Fallback for plain amiibos
+    if (is_plain_amiibo) {
+        LOG_INFO(Service_NFC, "Using plain amiibo");
+        encrypted_tag.file = AmiiboCrypto::EncodedDataToNfcData(tag.file);
+        return true;
+    }
+
+    // Fallback for encrypted amiibos without keys
+    if (!AmiiboCrypto::IsKeyAvailable()) {
+        LOG_INFO(Service_NFC, "Loading amiibo without keys");
+        memcpy(&encrypted_tag.raw, &tag.raw, sizeof(EncryptedNTAG215File));
+        tag.file = {};
+        BuildAmiiboWithoutKeys();
+        is_plain_amiibo = true;
+        is_write_protected = true;
+        return true;
+    }
+
+    LOG_INFO(Service_NFC, "Loading amiibo with keys");
+    memcpy(&encrypted_tag.raw, &tag.raw, sizeof(EncryptedNTAG215File));
+    tag.file = {};
+    return true;
+}
+
+void NfcDevice::UnloadAmiibo() {
+    is_tag_in_range = false;
+    amiibo_filename = "";
+    CloseAmiibo();
+}
+
+void NfcDevice::CloseAmiibo() {
+    if (device_state == DeviceState::NotInitialized || device_state == DeviceState::Initialized ||
+        device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) {
+        return;
+    }
+
+    LOG_INFO(Service_NFC, "Amiibo removed");
+
+    if (device_state == DeviceState::TagMounted ||
+        device_state == DeviceState::TagPartiallyMounted) {
+        ResetTagScanState();
+    }
+
+    device_state = DeviceState::TagRemoved;
+    encrypted_tag.file = {};
+    tag.file = {};
+    tag_in_range_event->Clear();
+    tag_out_of_range_event->Signal();
+}
+
+std::shared_ptr<Kernel::Event> NfcDevice::GetActivateEvent() const {
+    return tag_in_range_event;
+}
+
+std::shared_ptr<Kernel::Event> NfcDevice::GetDeactivateEvent() const {
+    return tag_out_of_range_event;
+}
+
+void NfcDevice::Initialize() {
+    device_state = DeviceState::Initialized;
+    encrypted_tag.file = {};
+    tag.file = {};
+    is_initalized = true;
+}
+
+void NfcDevice::Finalize() {
+    if (device_state == DeviceState::TagMounted) {
+        ResetTagScanState();
+    }
+    if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) {
+        StopDetection();
+    }
+    device_state = DeviceState::NotInitialized;
+    connection_state = ConnectionState::Success;
+    communication_state = CommunicationState::Idle;
+    is_initalized = false;
+}
+
+ResultCode NfcDevice::StartCommunication() {
+    const auto connection_result = CheckConnectionState();
+    if (connection_result.IsError()) {
+        return connection_result;
+    }
+
+    if (device_state != DeviceState::Initialized ||
+        communication_state != CommunicationState::Idle) {
+        return ResultCommandInvalidForState;
+    }
+
+    communication_state = CommunicationState::SearchingForAdapter;
+
+    // This is a hack. This mode needs to change when the tag reader has completed the initalization
+    communication_state = CommunicationState::Initialized;
+    return RESULT_SUCCESS;
+}
+
+ResultCode NfcDevice::StopCommunication() {
+    const auto connection_result = CheckConnectionState();
+    if (connection_result.IsError()) {
+        return connection_result;
+    }
+
+    if (communication_state == CommunicationState::Idle) {
+        return ResultCommandInvalidForState;
+    }
+
+    if (device_state == DeviceState::TagMounted ||
+        device_state == DeviceState::TagPartiallyMounted) {
+        ResetTagScanState();
+    }
+
+    device_state = DeviceState::Initialized;
+    communication_state = CommunicationState::Idle;
+    return RESULT_SUCCESS;
+}
+
+ResultCode NfcDevice::StartDetection(TagProtocol allowed_protocol) {
+    const auto connection_result = CheckConnectionState();
+    if (connection_result.IsError()) {
+        return connection_result;
+    }
+
+    if (device_state != DeviceState::Initialized && device_state != DeviceState::TagRemoved &&
+        communication_state != CommunicationState::Initialized) {
+        return ResultCommandInvalidForState;
+    }
+
+    // TODO: Set console in search mode here
+
+    device_state = DeviceState::SearchingForTag;
+    allowed_protocols = allowed_protocol;
+
+    if (is_tag_in_range) {
+        LoadAmiibo(amiibo_filename);
+    }
+
+    return RESULT_SUCCESS;
+}
+
+ResultCode NfcDevice::StopDetection() {
+    const auto connection_result = CheckConnectionState();
+    if (connection_result.IsError()) {
+        return connection_result;
+    }
+
+    if (device_state == DeviceState::TagMounted ||
+        device_state == DeviceState::TagPartiallyMounted) {
+        ResetTagScanState();
+    }
+
+    if (device_state == DeviceState::TagFound || device_state == DeviceState::SearchingForTag) {
+        CloseAmiibo();
+    }
+
+    if (device_state == DeviceState::TagFound || device_state == DeviceState::SearchingForTag ||
+        device_state == DeviceState::TagRemoved) {
+        // TODO: Stop console search mode here
+        device_state = DeviceState::Initialized;
+        connection_state = ConnectionState::Success;
+        return RESULT_SUCCESS;
+    }
+
+    LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
+    return ResultCommandInvalidForState;
+}
+
+ResultCode NfcDevice::Flush() {
+    if (device_state != DeviceState::TagMounted) {
+        LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
+        const auto connection_result = CheckConnectionState();
+        if (connection_result.IsSuccess()) {
+            return ResultCommandInvalidForState;
+        }
+        return connection_result;
+    }
+
+    auto& settings = tag.file.settings;
+
+    const auto& current_date = GetAmiiboDate();
+    if (settings.write_date.raw_date != current_date.raw_date) {
+        settings.write_date = current_date;
+        UpdateSettingsCrc();
+    }
+
+    tag.file.write_counter++;
+
+    if (is_write_protected) {
+        LOG_ERROR(Service_NFC, "No keys available skipping write request");
+        return RESULT_SUCCESS;
+    }
+
+    if (!is_plain_amiibo) {
+        if (!AmiiboCrypto::EncodeAmiibo(tag.file, encrypted_tag.file)) {
+            LOG_ERROR(Service_NFC, "Failed to encode data");
+            return ResultWriteAmiiboFailed;
+        }
+
+        if (amiibo_filename.empty()) {
+            LOG_ERROR(Service_NFC, "Tried to use UpdateStoredAmiiboData on a nonexistant file.");
+            return ResultWriteAmiiboFailed;
+        }
+    }
+
+    FileUtil::IOFile amiibo_file(amiibo_filename, "wb");
+    bool write_failed = false;
+
+    if (!amiibo_file.IsOpen()) {
+        LOG_ERROR(Service_NFC, "Could not open amiibo file \"{}\"", amiibo_filename);
+        write_failed = true;
+    }
+    if (!is_plain_amiibo) {
+        if (!write_failed &&
+            !amiibo_file.WriteBytes(&encrypted_tag.file, sizeof(EncryptedNTAG215File))) {
+            LOG_ERROR(Service_NFC, "Could not write to amiibo file \"{}\"", amiibo_filename);
+            write_failed = true;
+        }
+    } else {
+        if (!write_failed && !amiibo_file.WriteBytes(&tag.file, sizeof(EncryptedNTAG215File))) {
+            LOG_ERROR(Service_NFC, "Could not write to amiibo file \"{}\"", amiibo_filename);
+            write_failed = true;
+        }
+    }
+    amiibo_file.Close();
+
+    if (write_failed) {
+        return ResultWriteAmiiboFailed;
+    }
+
+    is_data_moddified = false;
+
+    return RESULT_SUCCESS;
+}
+
+ResultCode NfcDevice::Mount() {
+    if (device_state != DeviceState::TagFound) {
+        LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
+        const auto connection_result = CheckConnectionState();
+        if (connection_result.IsSuccess()) {
+            return ResultCommandInvalidForState;
+        }
+        return connection_result;
+    }
+
+    if (!AmiiboCrypto::IsAmiiboValid(encrypted_tag.file)) {
+        LOG_ERROR(Service_NFC, "Not an amiibo");
+        return ResultNotAnAmiibo;
+    }
+
+    // The loaded amiibo is not encrypted
+    if (is_plain_amiibo) {
+        device_state = DeviceState::TagMounted;
+        return RESULT_SUCCESS;
+    }
+
+    if (!AmiiboCrypto::DecodeAmiibo(encrypted_tag.file, tag.file)) {
+        LOG_ERROR(Service_NFC, "Can't decode amiibo {}", device_state);
+        return ResultCorruptedData;
+    }
+
+    device_state = DeviceState::TagMounted;
+    return RESULT_SUCCESS;
+}
+
+ResultCode NfcDevice::MountAmiibo() {
+    TagInfo tag_info{};
+    const auto result = GetTagInfo(tag_info);
+
+    if (result.IsError()) {
+        return result;
+    }
+
+    if (tag_info.tag_type != PackedTagType::Type2) {
+        return ResultNotAnAmiibo;
+    }
+
+    return Mount();
+}
+
+ResultCode NfcDevice::PartiallyMount() {
+    if (device_state != DeviceState::TagFound) {
+        LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
+        const auto connection_result = CheckConnectionState();
+        if (connection_result.IsSuccess()) {
+            return ResultCommandInvalidForState;
+        }
+        return connection_result;
+    }
+
+    if (!AmiiboCrypto::IsAmiiboValid(encrypted_tag.file)) {
+        LOG_ERROR(Service_NFC, "Not an amiibo");
+        return ResultNotAnAmiibo;
+    }
+
+    // The loaded amiibo is not encrypted
+    if (is_plain_amiibo) {
+        device_state = DeviceState::TagPartiallyMounted;
+        return RESULT_SUCCESS;
+    }
+
+    if (!AmiiboCrypto::DecodeAmiibo(encrypted_tag.file, tag.file)) {
+        LOG_ERROR(Service_NFC, "Can't decode amiibo {}", device_state);
+        return ResultCorruptedData;
+    }
+
+    device_state = DeviceState::TagPartiallyMounted;
+    return RESULT_SUCCESS;
+}
+
+ResultCode NfcDevice::PartiallyMountAmiibo() {
+    TagInfo tag_info{};
+    const auto result = GetTagInfo(tag_info);
+
+    if (result.IsError()) {
+        return result;
+    }
+
+    if (tag_info.tag_type != PackedTagType::Type2) {
+        return ResultNotAnAmiibo;
+    }
+
+    return PartiallyMount();
+}
+ResultCode NfcDevice::ResetTagScanState() {
+    if (device_state != DeviceState::TagMounted &&
+        device_state != DeviceState::TagPartiallyMounted) {
+        LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
+        const auto connection_result = CheckConnectionState();
+        if (connection_result.IsSuccess()) {
+            return ResultCommandInvalidForState;
+        }
+        return connection_result;
+    }
+
+    device_state = DeviceState::TagFound;
+    is_app_area_open = false;
+
+    return RESULT_SUCCESS;
+}
+
+ResultCode NfcDevice::GetTagInfo2(TagInfo2& tag_info) const {
+    tag_info = {
+        .uuid_length = static_cast<u16>(encrypted_tag.file.uuid.uid.size()),
+        .tag_type = PackedTagType::Type2,
+        .uuid = encrypted_tag.file.uuid.uid,
+        .extra_data = {}, // Used on non amiibo tags
+        .protocol = TagProtocol::None,
+        .extra_data2 = {}, // Used on non amiibo tags
+    };
+
+    return RESULT_SUCCESS;
+}
+
+ResultCode NfcDevice::GetTagInfo(TagInfo& tag_info) const {
+    if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted &&
+        device_state != DeviceState::TagPartiallyMounted) {
+        LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
+        const auto connection_result = CheckConnectionState();
+        if (connection_result.IsSuccess()) {
+            return ResultCommandInvalidForState;
+        }
+        return connection_result;
+    }
+
+    tag_info = {
+        .uuid_length = static_cast<u16>(encrypted_tag.file.uuid.uid.size()),
+        .protocol = PackedTagProtocol::None,
+        .tag_type = PackedTagType::Type2,
+        .uuid = encrypted_tag.file.uuid.uid,
+        .extra_data = {}, // Used on non amiibo tags
+    };
+
+    return RESULT_SUCCESS;
+}
+
+ResultCode NfcDevice::GetCommonInfo(CommonInfo& common_info) const {
+    if (device_state != DeviceState::TagMounted) {
+        LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
+        const auto connection_result = CheckConnectionState();
+        if (connection_result.IsSuccess()) {
+            return ResultCommandInvalidForState;
+        }
+        return connection_result;
+    }
+
+    const auto& settings = tag.file.settings;
+    const auto& model_info_data = tag.file.model_info;
+
+    // TODO: Validate this data
+    common_info = {
+        .last_write_date = settings.write_date.GetWriteDate(),
+        .application_write_counter = tag.file.application_write_counter,
+        .character_id = model_info_data.character_id,
+        .character_variant = model_info_data.character_variant,
+        .series = model_info_data.series,
+        .model_number = model_info_data.model_number,
+        .amiibo_type = model_info_data.amiibo_type,
+        .version = tag.file.amiibo_version,
+        .application_area_size = sizeof(ApplicationArea),
+    };
+
+    return RESULT_SUCCESS;
+}
+
+ResultCode NfcDevice::GetModelInfo(ModelInfo& model_info) const {
+    if (device_state != DeviceState::TagMounted &&
+        device_state != DeviceState::TagPartiallyMounted) {
+        LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
+        const auto connection_result = CheckConnectionState();
+        if (connection_result.IsSuccess()) {
+            return ResultCommandInvalidForState;
+        }
+        return connection_result;
+    }
+
+    const auto& model_info_data = encrypted_tag.file.user_memory.model_info;
+    model_info = {
+        .character_id = model_info_data.character_id,
+        .character_variant = model_info_data.character_variant,
+        .series = model_info_data.series,
+        .model_number = model_info_data.model_number,
+        .amiibo_type = model_info_data.amiibo_type,
+    };
+
+    return RESULT_SUCCESS;
+}
+
+ResultCode NfcDevice::GetRegisterInfo(RegisterInfo& register_info) const {
+    if (device_state != DeviceState::TagMounted) {
+        LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
+        const auto connection_result = CheckConnectionState();
+        if (connection_result.IsSuccess()) {
+            return ResultCommandInvalidForState;
+        }
+        return connection_result;
+    }
+
+    if (tag.file.settings.settings.amiibo_initialized == 0) {
+        return ResultRegistrationIsNotInitialized;
+    }
+
+    const auto& settings = tag.file.settings;
+
+    // TODO: Validate this data
+    register_info = {
+        .mii_data = tag.file.owner_mii,
+        .owner_mii_aes_ccm = tag.file.owner_mii_aes_ccm,
+        .amiibo_name = settings.amiibo_name,
+        .flags = static_cast<u8>(settings.settings.raw & 0xf),
+        .font_region = settings.country_code_id,
+        .creation_date = settings.init_date.GetWriteDate(),
+    };
+
+    return RESULT_SUCCESS;
+}
+
+ResultCode NfcDevice::GetAdminInfo(AdminInfo& admin_info) const {
+    if (device_state != DeviceState::TagMounted) {
+        LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
+        const auto connection_result = CheckConnectionState();
+        if (connection_result.IsSuccess()) {
+            return ResultCommandInvalidForState;
+        }
+        return connection_result;
+    }
+
+    u8 flags = static_cast<u8>(tag.file.settings.settings.raw >> 0x4);
+    if (tag.file.settings.settings.amiibo_initialized == 0) {
+        flags = flags & 0xfe;
+    }
+
+    u64 application_id = 0;
+    u32 application_area_id = 0;
+    AppAreaVersion app_area_version = AppAreaVersion::NotSet;
+    if (tag.file.settings.settings.appdata_initialized != 0) {
+        application_id = tag.file.application_id;
+        app_area_version =
+            static_cast<AppAreaVersion>(application_id >> application_id_version_offset & 0xf);
+
+        switch (app_area_version) {
+        case AppAreaVersion::Nintendo3DS:
+        case AppAreaVersion::Nintendo3DSv2:
+            app_area_version = AppAreaVersion::Nintendo3DS;
+            break;
+        case AppAreaVersion::NintendoWiiU:
+            app_area_version = AppAreaVersion::NintendoWiiU;
+            break;
+        default:
+            app_area_version = AppAreaVersion::NotSet;
+            break;
+        }
+
+        // Restore application id to original value
+        if (application_id >> 0x38 != 0) {
+            const u8 application_byte = tag.file.application_id_byte & 0xf;
+            application_id = RemoveVersionByte(application_id) |
+                             (static_cast<u64>(application_byte) << application_id_version_offset);
+        }
+
+        application_area_id = tag.file.application_area_id;
+    }
+
+    // TODO: Validate this data
+    admin_info = {
+        .application_id = application_id,
+        .application_area_id = application_area_id,
+        .crc_counter = tag.file.settings.crc_counter,
+        .flags = flags,
+        .tag_type = PackedTagType::Type2,
+        .app_area_version = app_area_version,
+    };
+
+    return RESULT_SUCCESS;
+}
+
+ResultCode NfcDevice::DeleteRegisterInfo() {
+    // This is a hack to get around a HW issue
+    if (device_state == DeviceState::TagFound) {
+        Mount();
+    }
+
+    if (device_state != DeviceState::TagMounted) {
+        LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
+        const auto connection_result = CheckConnectionState();
+        if (connection_result.IsSuccess()) {
+            return ResultCommandInvalidForState;
+        }
+        return connection_result;
+    }
+
+    if (tag.file.settings.settings.amiibo_initialized == 0) {
+        return ResultRegistrationIsNotInitialized;
+    }
+
+    CryptoPP::AutoSeededRandomPool rng;
+    const std::size_t mii_data_size =
+        sizeof(tag.file.owner_mii) + sizeof(tag.file.padding) + sizeof(tag.file.owner_mii_aes_ccm);
+    std::array<CryptoPP::byte, mii_data_size> buffer{};
+    rng.GenerateBlock(buffer.data(), mii_data_size);
+
+    memcpy(&tag.file.owner_mii, buffer.data(), mii_data_size);
+    memcpy(&tag.file.settings.amiibo_name, buffer.data(), sizeof(tag.file.settings.amiibo_name));
+    tag.file.unknown = rng.GenerateByte();
+    tag.file.unknown2[0] = rng.GenerateWord32();
+    tag.file.unknown2[1] = rng.GenerateWord32();
+    tag.file.register_info_crc = rng.GenerateWord32();
+    tag.file.settings.init_date.raw_date = static_cast<u32>(rng.GenerateWord32());
+    tag.file.settings.settings.font_region.Assign(0);
+    tag.file.settings.settings.amiibo_initialized.Assign(0);
+    tag.file.settings.country_code_id = 0;
+
+    return Flush();
+}
+
+ResultCode NfcDevice::SetRegisterInfoPrivate(const RegisterInfoPrivate& register_info) {
+    if (device_state != DeviceState::TagMounted) {
+        LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
+        const auto connection_result = CheckConnectionState();
+        if (connection_result.IsSuccess()) {
+            return ResultCommandInvalidForState;
+        }
+        return connection_result;
+    }
+
+    auto& settings = tag.file.settings;
+
+    if (tag.file.settings.settings.amiibo_initialized == 0) {
+        settings.init_date = GetAmiiboDate();
+        settings.write_date = GetAmiiboDate();
+    }
+
+    // Calculate mii CRC with the padding
+    tag.file.owner_mii_aes_ccm = boost::crc<16, 0x1021, 0, 0, false, false>(
+        &register_info.mii_data, sizeof(HLE::Applets::MiiData) + sizeof(u16));
+
+    settings.amiibo_name = register_info.amiibo_name;
+    tag.file.owner_mii = register_info.mii_data;
+    tag.file.mii_extension = {};
+    tag.file.unknown = 0;
+    tag.file.unknown2 = {};
+    settings.country_code_id = 0;
+    settings.settings.font_region.Assign(0);
+    settings.settings.amiibo_initialized.Assign(1);
+
+    UpdateRegisterInfoCrc();
+
+    return Flush();
+}
+
+ResultCode NfcDevice::RestoreAmiibo() {
+    if (device_state != DeviceState::TagMounted) {
+        LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
+        const auto connection_result = CheckConnectionState();
+        if (connection_result.IsSuccess()) {
+            return ResultCommandInvalidForState;
+        }
+        return connection_result;
+    }
+
+    // TODO: Load amiibo from backup on system
+    LOG_ERROR(Service_NFC, "Not Implemented");
+    return RESULT_SUCCESS;
+}
+
+ResultCode NfcDevice::Format() {
+    auto ResultCode = DeleteApplicationArea();
+    auto ResultCode2 = DeleteRegisterInfo();
+
+    if (ResultCode.IsError()) {
+        return ResultCode;
+    }
+
+    if (ResultCode2.IsError()) {
+        return ResultCode2;
+    }
+
+    return RESULT_SUCCESS;
+}
+
+ResultCode NfcDevice::OpenApplicationArea(u32 access_id) {
+    if (device_state != DeviceState::TagMounted) {
+        LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
+        const auto connection_result = CheckConnectionState();
+        if (connection_result.IsSuccess()) {
+            return ResultCommandInvalidForState;
+        }
+        return connection_result;
+    }
+
+    if (tag.file.settings.settings.appdata_initialized.Value() == 0) {
+        LOG_WARNING(Service_NFC, "Application area is not initialized");
+        return ResultApplicationAreaIsNotInitialized;
+    }
+
+    if (tag.file.application_area_id != access_id) {
+        LOG_WARNING(Service_NFC, "Wrong application area id");
+        return ResultWrongApplicationAreaId;
+    }
+
+    is_app_area_open = true;
+
+    return RESULT_SUCCESS;
+}
+
+ResultCode NfcDevice::GetApplicationAreaId(u32& application_area_id) const {
+    application_area_id = {};
+
+    if (device_state != DeviceState::TagMounted) {
+        LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
+        const auto connection_result = CheckConnectionState();
+        if (connection_result.IsSuccess()) {
+            return ResultCommandInvalidForState;
+        }
+        return connection_result;
+    }
+
+    if (tag.file.settings.settings.appdata_initialized.Value() == 0) {
+        LOG_WARNING(Service_NFC, "Application area is not initialized");
+        return ResultApplicationAreaIsNotInitialized;
+    }
+
+    application_area_id = tag.file.application_area_id;
+
+    return RESULT_SUCCESS;
+}
+
+ResultCode NfcDevice::GetApplicationArea(std::vector<u8>& data) const {
+    if (device_state != DeviceState::TagMounted) {
+        LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
+        const auto connection_result = CheckConnectionState();
+        if (connection_result.IsSuccess()) {
+            return ResultCommandInvalidForState;
+        }
+        return connection_result;
+    }
+
+    if (!is_app_area_open) {
+        LOG_ERROR(Service_NFC, "Application area is not open");
+        return ResultCommandInvalidForState;
+    }
+
+    if (tag.file.settings.settings.appdata_initialized.Value() == 0) {
+        LOG_ERROR(Service_NFC, "Application area is not initialized");
+        return ResultApplicationAreaIsNotInitialized;
+    }
+
+    if (data.size() > sizeof(ApplicationArea)) {
+        data.resize(sizeof(ApplicationArea));
+    }
+
+    memcpy(data.data(), tag.file.application_area.data(), data.size());
+
+    return RESULT_SUCCESS;
+}
+
+ResultCode NfcDevice::SetApplicationArea(std::span<const u8> data) {
+    if (device_state != DeviceState::TagMounted) {
+        LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
+        const auto connection_result = CheckConnectionState();
+        if (connection_result.IsSuccess()) {
+            return ResultCommandInvalidForState;
+        }
+        return connection_result;
+    }
+
+    if (data.size() == 0) {
+        LOG_ERROR(Service_NFC, "Data buffer is null");
+        return ResultInvalidArgument;
+    }
+
+    if (data.size() > sizeof(ApplicationArea)) {
+        LOG_ERROR(Service_NFC, "Wrong data size {}", data.size());
+        return ResultInvalidArgumentValue;
+    }
+
+    if (!is_app_area_open) {
+        LOG_ERROR(Service_NFC, "Application area is not open");
+        return ResultCommandInvalidForState;
+    }
+
+    if (tag.file.settings.settings.appdata_initialized.Value() == 0) {
+        LOG_ERROR(Service_NFC, "Application area is not initialized");
+        return ResultApplicationAreaIsNotInitialized;
+    }
+
+    std::memcpy(tag.file.application_area.data(), data.data(), data.size());
+
+    // Fill remaining data with random numbers
+    CryptoPP::AutoSeededRandomPool rng;
+    const std::size_t data_size = sizeof(ApplicationArea) - data.size();
+    std::vector<CryptoPP::byte> buffer(data_size);
+    rng.GenerateBlock(buffer.data(), data_size);
+    memcpy(tag.file.application_area.data() + data.size(), buffer.data(), data_size);
+
+    if (tag.file.application_write_counter != counter_limit) {
+        tag.file.application_write_counter++;
+    }
+
+    is_data_moddified = true;
+
+    return RESULT_SUCCESS;
+}
+
+ResultCode NfcDevice::CreateApplicationArea(u32 access_id, std::span<const u8> data) {
+    if (device_state != DeviceState::TagMounted) {
+        LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
+        const auto connection_result = CheckConnectionState();
+        if (connection_result.IsSuccess()) {
+            return ResultCommandInvalidForState;
+        }
+        return connection_result;
+    }
+
+    if (tag.file.settings.settings.appdata_initialized.Value() != 0) {
+        LOG_ERROR(Service_NFC, "Application area already exist");
+        return ResultApplicationAreaExist;
+    }
+
+    return RecreateApplicationArea(access_id, data);
+}
+
+ResultCode NfcDevice::RecreateApplicationArea(u32 access_id, std::span<const u8> data) {
+    if (device_state != DeviceState::TagMounted) {
+        LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
+        const auto connection_result = CheckConnectionState();
+        if (connection_result.IsSuccess()) {
+            return ResultCommandInvalidForState;
+        }
+        return connection_result;
+    }
+
+    if (data.size() > sizeof(ApplicationArea)) {
+        LOG_ERROR(Service_NFC, "Wrong data size {}", data.size());
+        return ResultInvalidArgumentValue;
+    }
+
+    if (is_app_area_open) {
+        LOG_ERROR(Service_NFC, "Application area is open");
+        return ResultCommandInvalidForState;
+    }
+
+    std::memcpy(tag.file.application_area.data(), data.data(), data.size());
+
+    // Fill remaining data with random numbers
+    CryptoPP::AutoSeededRandomPool rng;
+    const std::size_t data_size = sizeof(ApplicationArea) - data.size();
+    std::vector<CryptoPP::byte> buffer(data_size);
+    rng.GenerateBlock(buffer.data(), data_size);
+    memcpy(tag.file.application_area.data() + data.size(), buffer.data(), data_size);
+
+    if (tag.file.application_write_counter != counter_limit) {
+        tag.file.application_write_counter++;
+    }
+
+    u64 application_id{};
+    if (Core::System::GetInstance().GetAppLoader().ReadProgramId(application_id) ==
+        Loader::ResultStatus::Success) {
+        tag.file.application_id_byte =
+            static_cast<u8>(application_id >> application_id_version_offset & 0xf);
+        tag.file.application_id =
+            RemoveVersionByte(application_id) |
+            (static_cast<u64>(AppAreaVersion::Nintendo3DSv2) << application_id_version_offset);
+    }
+    tag.file.settings.settings.appdata_initialized.Assign(1);
+    tag.file.application_area_id = access_id;
+    tag.file.unknown = {};
+    tag.file.unknown2 = {};
+
+    UpdateRegisterInfoCrc();
+
+    return Flush();
+}
+
+ResultCode NfcDevice::DeleteApplicationArea() {
+    // This is a hack to get around a HW issue
+    if (device_state == DeviceState::TagFound) {
+        Mount();
+    }
+
+    if (device_state != DeviceState::TagMounted) {
+        LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
+        const auto connection_result = CheckConnectionState();
+        if (connection_result.IsSuccess()) {
+            return ResultCommandInvalidForState;
+        }
+        return connection_result;
+    }
+
+    if (tag.file.settings.settings.appdata_initialized == 0) {
+        return ResultApplicationAreaIsNotInitialized;
+    }
+
+    CryptoPP::AutoSeededRandomPool rng;
+    constexpr std::size_t data_size = sizeof(ApplicationArea);
+    std::array<CryptoPP::byte, data_size> buffer{};
+    rng.GenerateBlock(buffer.data(), data_size);
+
+    if (tag.file.application_write_counter != counter_limit) {
+        tag.file.application_write_counter++;
+    }
+
+    // Reset data with random bytes
+    memcpy(tag.file.application_area.data(), buffer.data(), data_size); //
+    memcpy(&tag.file.application_id, buffer.data(), sizeof(u64));
+    tag.file.application_area_id = rng.GenerateWord32();
+    tag.file.application_id_byte = rng.GenerateByte();
+    tag.file.settings.settings.appdata_initialized.Assign(0);
+    tag.file.unknown = {};
+    tag.file.unknown2 = {};
+    is_app_area_open = false;
+
+    UpdateRegisterInfoCrc();
+
+    return Flush();
+}
+
+ResultCode NfcDevice::ApplicationAreaExist(bool& has_application_area) {
+    if (device_state != DeviceState::TagMounted) {
+        LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
+        const auto connection_result = CheckConnectionState();
+        if (connection_result.IsSuccess()) {
+            return ResultCommandInvalidForState;
+        }
+        return connection_result;
+    }
+
+    has_application_area = tag.file.settings.settings.appdata_initialized.Value() != 0;
+
+    return RESULT_SUCCESS;
+}
+
+constexpr u32 NfcDevice::GetApplicationAreaSize() const {
+    return sizeof(ApplicationArea);
+}
+
+DeviceState NfcDevice::GetCurrentState() const {
+    return device_state;
+}
+
+ResultCode NfcDevice::GetCommunicationStatus(CommunicationState& status) const {
+    if (communication_state == CommunicationState::Idle ||
+        communication_state == CommunicationState::SearchingForAdapter) {
+        status = communication_state;
+        return RESULT_SUCCESS;
+    }
+
+    if (communication_state == CommunicationState::Initialized ||
+        communication_state == CommunicationState::Active) {
+        status = CommunicationState::Initialized;
+        return RESULT_SUCCESS;
+    }
+
+    return ResultCommandInvalidForState;
+}
+
+ResultCode NfcDevice::CheckConnectionState() const {
+    if (connection_state == ConnectionState::Lost) {
+        return ResultCommunicationLost;
+    }
+
+    if (connection_state == ConnectionState::NoAdapter) {
+        return ResultNoAdapterDetected;
+    }
+
+    return RESULT_SUCCESS;
+}
+
+void NfcDevice::SetAmiiboName(AmiiboSettings& settings, const AmiiboName& amiibo_name) {
+    // Convert from little endian to big endian
+    for (std::size_t i = 0; i < amiibo_name_length; i++) {
+        settings.amiibo_name[i] = static_cast<u16_be>(amiibo_name[i]);
+    }
+}
+
+time_t NfcDevice::GetCurrentTime() const {
+    auto& share_page = Core::System::GetInstance().Kernel().GetSharedPageHandler();
+    const auto console_time = share_page.GetSharedPage().date_time_1.date_time / 1000;
+
+    // 3DS console time uses Jan 1 1900 as internal epoch,
+    // so we use the seconds between 1900 and 2000 as base console time
+    constexpr u64 START_DATE = 3155673600ULL;
+
+    // 3DS system does't allow user to set a time before Jan 1 2000,
+    // so we use it as an auxiliary epoch to calculate the console time.
+    std::tm epoch_tm;
+    epoch_tm.tm_sec = 0;
+    epoch_tm.tm_min = 0;
+    epoch_tm.tm_hour = 0;
+    epoch_tm.tm_mday = 1;
+    epoch_tm.tm_mon = 0;
+    epoch_tm.tm_year = 100;
+    epoch_tm.tm_isdst = 0;
+    s64 epoch = std::mktime(&epoch_tm);
+    return static_cast<time_t>(console_time - START_DATE + epoch);
+}
+
+AmiiboDate NfcDevice::GetAmiiboDate() const {
+    auto now = GetCurrentTime();
+    tm local_tm = *localtime(&now);
+    AmiiboDate amiibo_date{};
+
+    amiibo_date.SetYear(static_cast<u16>(local_tm.tm_year + 1900));
+    amiibo_date.SetMonth(static_cast<u8>(local_tm.tm_mon + 1));
+    amiibo_date.SetDay(static_cast<u8>(local_tm.tm_mday));
+
+    return amiibo_date;
+}
+
+u64 NfcDevice::RemoveVersionByte(u64 application_id) const {
+    return application_id & ~(0xfULL << application_id_version_offset);
+}
+
+void NfcDevice::UpdateSettingsCrc() {
+    auto& settings = tag.file.settings;
+
+    if (settings.crc_counter != counter_limit) {
+        settings.crc_counter++;
+    }
+
+    // TODO: this reads data from a global, find what it is
+    std::array<u8, 8> unknown_input{};
+    boost::crc_32_type crc;
+    crc.process_bytes(&unknown_input, sizeof(unknown_input));
+    settings.crc = crc.checksum();
+}
+
+void NfcDevice::UpdateRegisterInfoCrc() {
+#pragma pack(push, 1)
+    struct CrcData {
+        HLE::Applets::MiiData mii;
+        INSERT_PADDING_BYTES(0x2);
+        u16 mii_crc;
+        u8 application_id_byte;
+        u8 unknown;
+        u64 mii_extension;
+        std::array<u32, 0x5> unknown2;
+    };
+    static_assert(sizeof(CrcData) == 0x7e, "CrcData is an invalid size");
+#pragma pack(pop)
+
+    const CrcData crc_data{
+        .mii = tag.file.owner_mii,
+        .mii_crc = tag.file.owner_mii_aes_ccm,
+        .application_id_byte = tag.file.application_id_byte,
+        .unknown = tag.file.unknown,
+        .mii_extension = tag.file.mii_extension,
+        .unknown2 = tag.file.unknown2,
+    };
+
+    boost::crc_32_type crc;
+    crc.process_bytes(&crc_data, sizeof(CrcData));
+    tag.file.register_info_crc = crc.checksum();
+}
+
+void NfcDevice::BuildAmiiboWithoutKeys() {
+    auto& settings = tag.file.settings;
+    const auto default_mii = HLE::Applets::MiiSelector::GetStandardMiiResult();
+
+    tag.file = AmiiboCrypto::NfcDataToEncodedData(encrypted_tag.file);
+
+    // Common info
+    tag.file.write_counter = 0;
+    tag.file.amiibo_version = 0;
+    settings.write_date = GetAmiiboDate();
+
+    // Register info
+    SetAmiiboName(settings, {'c', 'i', 't', 'r', 'A', 'm', 'i', 'i', 'b', 'o'});
+    settings.settings.font_region.Assign(0);
+    settings.init_date = GetAmiiboDate();
+    tag.file.owner_mii = default_mii.selected_mii_data;
+    tag.file.padding = default_mii.unknown1;
+    tag.file.owner_mii_aes_ccm = default_mii.mii_data_checksum;
+
+    // Admin info
+    settings.settings.amiibo_initialized.Assign(1);
+    settings.settings.appdata_initialized.Assign(0);
+}
+
+} // namespace Service::NFC
diff --git a/src/core/hle/service/nfc/nfc_device.h b/src/core/hle/service/nfc/nfc_device.h
new file mode 100644
index 000000000..dc6d28361
--- /dev/null
+++ b/src/core/hle/service/nfc/nfc_device.h
@@ -0,0 +1,111 @@
+// Copyright 2022 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <span>
+#include <vector>
+#include <boost/serialization/binary_object.hpp>
+
+#include "common/common_types.h"
+#include "core/hle/service/nfc/nfc_results.h"
+#include "core/hle/service/nfc/nfc_types.h"
+#include "core/hle/service/service.h"
+
+namespace Kernel {
+class KEvent;
+class KReadableEvent;
+} // namespace Kernel
+
+namespace Service::NFC {
+class NfcDevice {
+public:
+    NfcDevice(Core::System& system);
+    ~NfcDevice();
+
+    bool LoadAmiibo(std::string filename);
+    void UnloadAmiibo();
+    void CloseAmiibo();
+
+    void Initialize();
+    void Finalize();
+
+    ResultCode StartCommunication();
+    ResultCode StopCommunication();
+    ResultCode StartDetection(TagProtocol allowed_protocol);
+    ResultCode StopDetection();
+    ResultCode Mount();
+    ResultCode MountAmiibo();
+    ResultCode PartiallyMount();
+    ResultCode PartiallyMountAmiibo();
+    ResultCode ResetTagScanState();
+    ResultCode Flush();
+
+    ResultCode GetTagInfo2(TagInfo2& tag_info) const;
+    ResultCode GetTagInfo(TagInfo& tag_info) const;
+    ResultCode GetCommonInfo(CommonInfo& common_info) const;
+    ResultCode GetModelInfo(ModelInfo& model_info) const;
+    ResultCode GetRegisterInfo(RegisterInfo& register_info) const;
+    ResultCode GetAdminInfo(AdminInfo& admin_info) const;
+
+    ResultCode DeleteRegisterInfo();
+    ResultCode SetRegisterInfoPrivate(const RegisterInfoPrivate& register_info);
+    ResultCode RestoreAmiibo();
+    ResultCode Format();
+
+    ResultCode OpenApplicationArea(u32 access_id);
+    ResultCode GetApplicationAreaId(u32& application_area_id) const;
+    ResultCode GetApplicationArea(std::vector<u8>& data) const;
+    ResultCode SetApplicationArea(std::span<const u8> data);
+    ResultCode CreateApplicationArea(u32 access_id, std::span<const u8> data);
+    ResultCode RecreateApplicationArea(u32 access_id, std::span<const u8> data);
+    ResultCode DeleteApplicationArea();
+    ResultCode ApplicationAreaExist(bool& has_application_area);
+
+    constexpr u32 GetApplicationAreaSize() const;
+    DeviceState GetCurrentState() const;
+    ResultCode GetCommunicationStatus(CommunicationState& status) const;
+    ResultCode CheckConnectionState() const;
+
+    std::shared_ptr<Kernel::Event> GetActivateEvent() const;
+    std::shared_ptr<Kernel::Event> GetDeactivateEvent() const;
+
+private:
+    time_t GetCurrentTime() const;
+    void SetAmiiboName(AmiiboSettings& settings, const AmiiboName& amiibo_name);
+    AmiiboDate GetAmiiboDate() const;
+    u64 RemoveVersionByte(u64 application_id) const;
+    void UpdateSettingsCrc();
+    void UpdateRegisterInfoCrc();
+
+    void BuildAmiiboWithoutKeys();
+
+    std::shared_ptr<Kernel::Event> tag_in_range_event = nullptr;
+    std::shared_ptr<Kernel::Event> tag_out_of_range_event = nullptr;
+    Core::TimingEventType* remove_amiibo_event = nullptr;
+
+    bool is_initalized{};
+    bool is_data_moddified{};
+    bool is_app_area_open{};
+    bool is_plain_amiibo{};
+    bool is_write_protected{};
+    bool is_tag_in_range{};
+    TagProtocol allowed_protocols{};
+    DeviceState device_state{DeviceState::NotInitialized};
+    ConnectionState connection_state = ConnectionState::Success;
+    CommunicationState communication_state = CommunicationState::Idle;
+
+    std::string amiibo_filename = "";
+
+    SerializableAmiiboFile tag{};
+    SerializableEncryptedAmiiboFile encrypted_tag{};
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int);
+    friend class boost::serialization::access;
+};
+
+} // namespace Service::NFC
+
+SERVICE_CONSTRUCT(Service::NFC::NfcDevice)
diff --git a/src/core/hle/service/nfc/nfc_m.cpp b/src/core/hle/service/nfc/nfc_m.cpp
index aa6a67dbc..e96dcabc4 100644
--- a/src/core/hle/service/nfc/nfc_m.cpp
+++ b/src/core/hle/service/nfc/nfc_m.cpp
@@ -21,25 +21,33 @@ NFC_M::NFC_M(std::shared_ptr<Module> nfc) : Module::Interface(std::move(nfc), "n
         {IPC::MakeHeader(0x0006, 0, 0), &NFC_M::StopTagScanning, "StopTagScanning"},
         {IPC::MakeHeader(0x0007, 0, 0), &NFC_M::LoadAmiiboData, "LoadAmiiboData"},
         {IPC::MakeHeader(0x0008, 0, 0), &NFC_M::ResetTagScanState, "ResetTagScanState"},
-        {IPC::MakeHeader(0x0009, 0, 2), nullptr, "UpdateStoredAmiiboData"},
+        {IPC::MakeHeader(0x0009, 0, 2), &NFC_M::UpdateStoredAmiiboData, "UpdateStoredAmiiboData"},
+        {IPC::MakeHeader(0x000A, 0, 0), nullptr, "Unknown0x0A"},
         {IPC::MakeHeader(0x000B, 0, 0), &NFC_M::GetTagInRangeEvent, "GetTagInRangeEvent"},
         {IPC::MakeHeader(0x000C, 0, 0), &NFC_M::GetTagOutOfRangeEvent, "GetTagOutOfRangeEvent"},
         {IPC::MakeHeader(0x000D, 0, 0), &NFC_M::GetTagState, "GetTagState"},
+        {IPC::MakeHeader(0x000E, 0, 0), nullptr, "Unknown0x0E"},
         {IPC::MakeHeader(0x000F, 0, 0), &NFC_M::CommunicationGetStatus, "CommunicationGetStatus"},
-        {IPC::MakeHeader(0x0010, 0, 0), nullptr, "GetTagInfo2"},
+        {IPC::MakeHeader(0x0010, 0, 0), &NFC_M::GetTagInfo2, "GetTagInfo2"},
         {IPC::MakeHeader(0x0011, 0, 0), &NFC_M::GetTagInfo, "GetTagInfo"},
-        {IPC::MakeHeader(0x0012, 0, 0), nullptr, "CommunicationGetResult"},
-        {IPC::MakeHeader(0x0013, 1, 0), nullptr, "OpenAppData"},
-        {IPC::MakeHeader(0x0014, 14, 4), nullptr, "InitializeWriteAppData"},
-        {IPC::MakeHeader(0x0015, 1, 0), nullptr, "ReadAppData"},
-        {IPC::MakeHeader(0x0016, 9, 2), nullptr, "WriteAppData"},
-        {IPC::MakeHeader(0x0017, 0, 0), nullptr, "GetAmiiboSettings"},
-        {IPC::MakeHeader(0x0018, 0, 0), &NFC_M::GetAmiiboConfig, "GetAmiiboConfig"},
-        {IPC::MakeHeader(0x0019, 0, 0), nullptr, "GetAppDataInitStruct"},
-        {IPC::MakeHeader(0x001A, 0, 0), &NFC_M::Unknown0x1A, "Unknown0x1A"},
+        {IPC::MakeHeader(0x0012, 0, 0), &NFC_M::CommunicationGetResult, "CommunicationGetResult"},
+        {IPC::MakeHeader(0x0013, 1, 0), &NFC_M::OpenAppData, "OpenAppData"},
+        {IPC::MakeHeader(0x0014, 14, 4), &NFC_M::InitializeWriteAppData, "InitializeWriteAppData"},
+        {IPC::MakeHeader(0x0015, 1, 0), &NFC_M::ReadAppData, "ReadAppData"},
+        {IPC::MakeHeader(0x0016, 9, 2), &NFC_M::WriteAppData, "WriteAppData"},
+        {IPC::MakeHeader(0x0017, 0, 0), &NFC_M::GetRegisterInfo, "GetRegisterInfo"},
+        {IPC::MakeHeader(0x0018, 0, 0), &NFC_M::GetCommonInfo, "GetCommonInfo"},
+        {IPC::MakeHeader(0x0019, 0, 0), &NFC_M::GetAppDataInitStruct, "GetAppDataInitStruct"},
+        {IPC::MakeHeader(0x001A, 0, 0), &NFC_M::LoadAmiiboPartially, "LoadAmiiboPartially"},
         {IPC::MakeHeader(0x001B, 0, 0), &NFC_M::GetIdentificationBlock, "GetIdentificationBlock"},
         // nfc:m
-        {IPC::MakeHeader(0x0404, 41, 0), nullptr, "SetAmiiboSettings"}
+        {IPC::MakeHeader(0x0401, 3, 2), &NFC_M::Format, "Format"},
+        {IPC::MakeHeader(0x0402, 0, 0), &NFC_M::GetAdminInfo, "GetAdminInfo"},
+        {IPC::MakeHeader(0x0403, 0, 0), &NFC_M::GetEmptyRegisterInfo, "GetEmptyRegisterInfo"},
+        {IPC::MakeHeader(0x0404, 41, 0), &NFC_M::SetRegisterInfo, "SetRegisterInfo"},
+        {IPC::MakeHeader(0x0405, 0, 0), &NFC_M::DeleteRegisterInfo, "DeleteRegisterInfo"},
+        {IPC::MakeHeader(0x0406, 0, 0), &NFC_M::DeleteApplicationArea, "DeleteApplicationArea"},
+        {IPC::MakeHeader(0x0407, 0, 0), &NFC_M::ExistsApplicationArea, "ExistsApplicationArea"}
         // clang-format on
     };
     RegisterHandlers(functions);
diff --git a/src/core/hle/service/nfc/nfc_results.h b/src/core/hle/service/nfc/nfc_results.h
new file mode 100644
index 000000000..ee45ba2d9
--- /dev/null
+++ b/src/core/hle/service/nfc/nfc_results.h
@@ -0,0 +1,62 @@
+// Copyright 2023 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/result.h"
+
+namespace Service::NFC {
+
+namespace ErrCodes {
+enum {
+    InvalidArgumentValue = 80,
+    InvalidArgument = 81,
+
+    InvalidChecksum = 200,
+    WriteFailed = 328,
+
+    CommandInvalidForState = 512,
+    NotAnAmiibo = 522,
+    CorruptedData = 536,
+    AppDataUninitialized = 544,
+    RegistrationUnitialized = 552,
+    ApplicationAreaExist = 560,
+    AppIdMismatch = 568,
+
+    CommunicationLost = 608,
+    NoAdapterDetected = 616,
+};
+} // namespace ErrCodes
+
+constexpr ResultCode ResultInvalidArgumentValue(ErrCodes::InvalidArgumentValue, ErrorModule::NFC,
+                                                ErrorSummary::InvalidArgument, ErrorLevel::Status);
+constexpr ResultCode ResultInvalidArgument(ErrCodes::InvalidArgument, ErrorModule::NFC,
+                                           ErrorSummary::InvalidArgument, ErrorLevel::Status);
+constexpr ResultCode ResultCommandInvalidForState(ErrCodes::CommandInvalidForState,
+                                                  ErrorModule::NFC, ErrorSummary::InvalidState,
+                                                  ErrorLevel::Status);
+constexpr ResultCode ResultNotAnAmiibo(ErrCodes::NotAnAmiibo, ErrorModule::NFC,
+                                       ErrorSummary::InvalidState, ErrorLevel::Status);
+constexpr ResultCode ResultCorruptedData(ErrCodes::CorruptedData, ErrorModule::NFC,
+                                         ErrorSummary::InvalidState, ErrorLevel::Status);
+constexpr ResultCode ResultWriteAmiiboFailed(ErrCodes::WriteFailed, ErrorModule::NFC,
+                                             ErrorSummary::InvalidState, ErrorLevel::Status);
+constexpr ResultCode ResultApplicationAreaIsNotInitialized(ErrCodes::AppDataUninitialized,
+                                                           ErrorModule::NFC,
+                                                           ErrorSummary::InvalidState,
+                                                           ErrorLevel::Status);
+constexpr ResultCode ResultRegistrationIsNotInitialized(ErrCodes::RegistrationUnitialized,
+                                                        ErrorModule::NFC,
+                                                        ErrorSummary::InvalidState,
+                                                        ErrorLevel::Status);
+constexpr ResultCode ResultApplicationAreaExist(ErrCodes::ApplicationAreaExist, ErrorModule::NFC,
+                                                ErrorSummary::InvalidState, ErrorLevel::Status);
+constexpr ResultCode ResultWrongApplicationAreaId(ErrCodes::AppIdMismatch, ErrorModule::NFC,
+                                                  ErrorSummary::InvalidState, ErrorLevel::Status);
+constexpr ResultCode ResultCommunicationLost(ErrCodes::CommunicationLost, ErrorModule::NFC,
+                                             ErrorSummary::InvalidState, ErrorLevel::Status);
+constexpr ResultCode ResultNoAdapterDetected(ErrCodes::NoAdapterDetected, ErrorModule::NFC,
+                                             ErrorSummary::InvalidState, ErrorLevel::Status);
+
+} // namespace Service::NFC
diff --git a/src/core/hle/service/nfc/nfc_types.h b/src/core/hle/service/nfc/nfc_types.h
new file mode 100644
index 000000000..2a31a16dc
--- /dev/null
+++ b/src/core/hle/service/nfc/nfc_types.h
@@ -0,0 +1,460 @@
+// Copyright 2022 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+
+#include "common/bit_field.h"
+#include "common/common_types.h"
+#include "core/hle/applets/mii_selector.h"
+
+namespace Service::NFC {
+static constexpr std::size_t amiibo_name_length = 0xA;
+static constexpr std::size_t application_id_version_offset = 0x1c;
+static constexpr std::size_t counter_limit = 0xffff;
+
+enum class ServiceType : u32 {
+    User,
+    Debug,
+    System,
+};
+
+enum class CommunicationState : u8 {
+    Idle = 0,
+    SearchingForAdapter = 1,
+    Initialized = 2,
+    Active = 3,
+};
+
+enum class ConnectionState : u8 {
+    Success = 0,
+    NoAdapter = 1,
+    Lost = 2,
+};
+
+enum class DeviceState : u32 {
+    NotInitialized = 0,
+    Initialized = 1,
+    SearchingForTag = 2,
+    TagFound = 3,
+    TagRemoved = 4,
+    TagMounted = 5,
+    TagPartiallyMounted = 6, // Validate this one seems to have other name
+};
+
+enum class ModelType : u32 {
+    Amiibo,
+};
+
+enum class MountTarget : u32 {
+    None,
+    Rom,
+    Ram,
+    All,
+};
+
+enum class AmiiboType : u8 {
+    Figure,
+    Card,
+    Yarn,
+};
+
+enum class AmiiboSeries : u8 {
+    SuperSmashBros,
+    SuperMario,
+    ChibiRobo,
+    YoshiWoollyWorld,
+    Splatoon,
+    AnimalCrossing,
+    EightBitMario,
+    Skylanders,
+    Unknown8,
+    TheLegendOfZelda,
+    ShovelKnight,
+    Unknown11,
+    Kiby,
+    Pokemon,
+    MarioSportsSuperstars,
+    MonsterHunter,
+    BoxBoy,
+    Pikmin,
+    FireEmblem,
+    Metroid,
+    Others,
+    MegaMan,
+    Diablo,
+};
+
+enum class TagType : u32 {
+    None,
+    Type1, // ISO14443A RW 96-2k bytes 106kbit/s
+    Type2, // ISO14443A RW/RO 540 bytes 106kbit/s
+    Type3, // Sony Felica RW/RO 2k bytes 212kbit/s
+    Type4, // ISO14443A RW/RO 4k-32k bytes 424kbit/s
+    Type5, // ISO15693 RW/RO 540 bytes 106kbit/s
+};
+
+enum class PackedTagType : u8 {
+    None,
+    Type1, // ISO14443A RW 96-2k bytes 106kbit/s
+    Type2, // ISO14443A RW/RO 540 bytes 106kbit/s
+    Type3, // Sony Felica RW/RO 2k bytes 212kbit/s
+    Type4, // ISO14443A RW/RO 4k-32k bytes 424kbit/s
+    Type5, // ISO15693 RW/RO 540 bytes 106kbit/s
+};
+
+// Verify this enum. It might be completely wrong default protocol is 0x0
+enum class TagProtocol : u32 {
+    None,
+    TypeA = 1U << 0, // ISO14443A
+    TypeB = 1U << 1, // ISO14443B
+    TypeF = 1U << 2, // Sony Felica
+    Unknown1 = 1U << 3,
+    Unknown2 = 1U << 5,
+    All = 0xFFFFFFFFU,
+};
+
+// Verify this enum. It might be completely wrong default protocol is 0x0
+enum class PackedTagProtocol : u8 {
+    None,
+    TypeA = 1U << 0, // ISO14443A
+    TypeB = 1U << 1, // ISO14443B
+    TypeF = 1U << 2, // Sony Felica
+    Unknown1 = 1U << 3,
+    Unknown2 = 1U << 5,
+    All = 0xFF,
+};
+
+enum class AppAreaVersion : u8 {
+    Nintendo3DS = 0,
+    NintendoWiiU = 1,
+    Nintendo3DSv2 = 2,
+    NintendoSwitch = 3,
+    NotSet = 0xFF,
+};
+
+using UniqueSerialNumber = std::array<u8, 7>;
+using LockBytes = std::array<u8, 2>;
+using HashData = std::array<u8, 0x20>;
+using ApplicationArea = std::array<u8, 0xD8>;
+using AmiiboName = std::array<u16_be, amiibo_name_length>;
+using DataBlock = std::array<u8, 0x10>;
+using KeyData = std::array<u8, 0x6>;
+
+struct TagUuid {
+    UniqueSerialNumber uid;
+    u8 nintendo_id;
+    LockBytes lock_bytes;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& uid;
+        ar& nintendo_id;
+        ar& lock_bytes;
+    }
+    friend class boost::serialization::access;
+};
+static_assert(sizeof(TagUuid) == 10, "TagUuid is an invalid size");
+
+struct WriteDate {
+    u16 year;
+    u8 month;
+    u8 day;
+};
+static_assert(sizeof(WriteDate) == 0x4, "WriteDate is an invalid size");
+
+struct AmiiboDate {
+    u16 raw_date{};
+
+    u16 GetValue() const {
+        return Common::swap16(raw_date);
+    }
+
+    u16 GetYear() const {
+        return static_cast<u16>(((GetValue() & 0xFE00) >> 9) + 2000);
+    }
+    u8 GetMonth() const {
+        return static_cast<u8>((GetValue() & 0x01E0) >> 5);
+    }
+    u8 GetDay() const {
+        return static_cast<u8>(GetValue() & 0x001F);
+    }
+
+    WriteDate GetWriteDate() const {
+        if (!IsValidDate()) {
+            return {
+                .year = 2000,
+                .month = 1,
+                .day = 1,
+            };
+        }
+        return {
+            .year = GetYear(),
+            .month = GetMonth(),
+            .day = GetDay(),
+        };
+    }
+
+    void SetYear(u16 year) {
+        const u16 year_converted = static_cast<u16>((year - 2000) << 9);
+        raw_date = Common::swap16((GetValue() & ~0xFE00) | year_converted);
+    }
+    void SetMonth(u8 month) {
+        const u16 month_converted = static_cast<u16>(month << 5);
+        raw_date = Common::swap16((GetValue() & ~0x01E0) | month_converted);
+    }
+    void SetDay(u8 day) {
+        const u16 day_converted = static_cast<u16>(day);
+        raw_date = Common::swap16((GetValue() & ~0x001F) | day_converted);
+    }
+
+    bool IsValidDate() const {
+        const bool is_day_valid = GetDay() > 0 && GetDay() < 32;
+        const bool is_month_valid = GetMonth() > 0 && GetMonth() < 13;
+        const bool is_year_valid = GetYear() >= 2000;
+        return is_year_valid && is_month_valid && is_day_valid;
+    }
+};
+static_assert(sizeof(AmiiboDate) == 2, "AmiiboDate is an invalid size");
+
+struct Settings {
+    union {
+        u8 raw{};
+
+        BitField<0, 4, u8> font_region;
+        BitField<4, 1, u8> amiibo_initialized;
+        BitField<5, 1, u8> appdata_initialized;
+    };
+};
+static_assert(sizeof(Settings) == 1, "AmiiboDate is an invalid size");
+
+struct AmiiboSettings {
+    Settings settings;
+    u8 country_code_id;
+    u16_be crc_counter; // Incremented each time crc is changed
+    AmiiboDate init_date;
+    AmiiboDate write_date;
+    u32_be crc;
+    AmiiboName amiibo_name; // UTF-16 text
+};
+static_assert(sizeof(AmiiboSettings) == 0x20, "AmiiboSettings is an invalid size");
+
+struct AmiiboModelInfo {
+    u16 character_id;
+    u8 character_variant;
+    AmiiboType amiibo_type;
+    u16_be model_number;
+    AmiiboSeries series;
+    PackedTagType tag_type;
+    INSERT_PADDING_BYTES(0x4); // Unknown
+};
+static_assert(sizeof(AmiiboModelInfo) == 0xC, "AmiiboModelInfo is an invalid size");
+
+struct NTAG215Password {
+    u32 PWD;  // Password to allow write access
+    u16 PACK; // Password acknowledge reply
+    u16 RFUI; // Reserved for future use
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& PWD;
+        ar& PACK;
+        ar& RFUI;
+    }
+    friend class boost::serialization::access;
+};
+static_assert(sizeof(NTAG215Password) == 0x8, "NTAG215Password is an invalid size");
+
+#pragma pack(1)
+struct EncryptedAmiiboFile {
+    u8 constant_value;                // Must be A5
+    u16_be write_counter;             // Number of times the amiibo has been written?
+    u8 amiibo_version;                // Amiibo file version
+    AmiiboSettings settings;          // Encrypted amiibo settings
+    HashData hmac_tag;                // Hash
+    AmiiboModelInfo model_info;       // Encrypted amiibo model info
+    HashData keygen_salt;             // Salt
+    HashData hmac_data;               // Hash
+    HLE::Applets::MiiData owner_mii;  // Encrypted Mii data
+    u16 padding;                      // Mii Padding
+    u16_be owner_mii_aes_ccm;         // Mii data AES-CCM MAC
+    u64_be application_id;            // Encrypted Game id
+    u16_be application_write_counter; // Encrypted Counter
+    u32_be application_area_id;       // Encrypted Game id
+    u8 application_id_byte;
+    u8 unknown;
+    u64 mii_extension;
+    std::array<u32, 0x5> unknown2;
+    u32_be register_info_crc;
+    ApplicationArea application_area; // Encrypted Game data
+};
+static_assert(sizeof(EncryptedAmiiboFile) == 0x1F8, "AmiiboFile is an invalid size");
+
+struct NTAG215File {
+    LockBytes lock_bytes;      // Tag UUID
+    u16 static_lock;           // Set defined pages as read only
+    u32 compability_container; // Defines available memory
+    HashData hmac_data;        // Hash
+    u8 constant_value;         // Must be A5
+    u16_be write_counter;      // Number of times the amiibo has been written?
+    u8 amiibo_version;         // Amiibo file version
+    AmiiboSettings settings;
+    HLE::Applets::MiiData owner_mii;  // Mii data
+    u16 padding;                      // Mii Padding
+    u16_be owner_mii_aes_ccm;         // Mii data AES-CCM MAC
+    u64_be application_id;            // Game id
+    u16_be application_write_counter; // Counter
+    u32_be application_area_id;
+    u8 application_id_byte;
+    u8 unknown;
+    u64 mii_extension;
+    std::array<u32, 0x5> unknown2;
+    u32_be register_info_crc;
+    ApplicationArea application_area; // Game data
+    HashData hmac_tag;                // Hash
+    UniqueSerialNumber uid;           // Unique serial number
+    u8 nintendo_id;                   // Tag UUID
+    AmiiboModelInfo model_info;
+    HashData keygen_salt;     // Salt
+    u32 dynamic_lock;         // Dynamic lock
+    u32 CFG0;                 // Defines memory protected by password
+    u32 CFG1;                 // Defines number of verification attempts
+    NTAG215Password password; // Password data
+};
+static_assert(sizeof(NTAG215File) == 0x21C, "NTAG215File is an invalid size");
+static_assert(std::is_trivially_copyable_v<NTAG215File>, "NTAG215File must be trivially copyable.");
+#pragma pack()
+
+struct EncryptedNTAG215File {
+    TagUuid uuid;                    // Unique serial number
+    u16 static_lock;                 // Set defined pages as read only
+    u32 compability_container;       // Defines available memory
+    EncryptedAmiiboFile user_memory; // Writable data
+    u32 dynamic_lock;                // Dynamic lock
+    u32 CFG0;                        // Defines memory protected by password
+    u32 CFG1;                        // Defines number of verification attempts
+    NTAG215Password password;        // Password data
+};
+static_assert(sizeof(EncryptedNTAG215File) == 0x21C, "EncryptedNTAG215File is an invalid size");
+static_assert(std::is_trivially_copyable_v<EncryptedNTAG215File>,
+              "EncryptedNTAG215File must be trivially copyable.");
+
+struct SerializableAmiiboFile {
+    union {
+        std::array<u8, 0x21C> raw;
+        NTAG215File file;
+    };
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& raw;
+    }
+    friend class boost::serialization::access;
+};
+static_assert(sizeof(SerializableAmiiboFile) == 0x21C, "SerializableAmiiboFile is an invalid size");
+static_assert(std::is_trivially_copyable_v<SerializableAmiiboFile>,
+              "SerializableAmiiboFile must be trivially copyable.");
+
+struct SerializableEncryptedAmiiboFile {
+    union {
+        std::array<u8, 0x21C> raw;
+        EncryptedNTAG215File file;
+    };
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& raw;
+    }
+    friend class boost::serialization::access;
+};
+static_assert(sizeof(SerializableEncryptedAmiiboFile) == 0x21C,
+              "SerializableEncryptedAmiiboFile is an invalid size");
+static_assert(std::is_trivially_copyable_v<SerializableEncryptedAmiiboFile>,
+              "SerializableEncryptedAmiiboFile must be trivially copyable.");
+
+struct TagInfo {
+    u16 uuid_length;
+    PackedTagProtocol protocol;
+    PackedTagType tag_type;
+    UniqueSerialNumber uuid;
+    std::array<u8, 0x21> extra_data;
+};
+static_assert(sizeof(TagInfo) == 0x2C, "TagInfo is an invalid size");
+
+struct TagInfo2 {
+    u16 uuid_length;
+    INSERT_PADDING_BYTES(0x1);
+    PackedTagType tag_type;
+    UniqueSerialNumber uuid;
+    std::array<u8, 0x21> extra_data;
+    TagProtocol protocol;
+    std::array<u8, 0x30> extra_data2;
+};
+static_assert(sizeof(TagInfo2) == 0x60, "TagInfo2 is an invalid size");
+
+struct CommonInfo {
+    WriteDate last_write_date;
+    u16 application_write_counter;
+    u16 character_id;
+    u8 character_variant;
+    AmiiboSeries series;
+    u16 model_number;
+    AmiiboType amiibo_type;
+    u8 version;
+    u16 application_area_size;
+    INSERT_PADDING_BYTES(0x30);
+};
+static_assert(sizeof(CommonInfo) == 0x40, "CommonInfo is an invalid size");
+
+struct ModelInfo {
+    u16 character_id;
+    u8 character_variant;
+    AmiiboSeries series;
+    u16 model_number;
+    AmiiboType amiibo_type;
+    INSERT_PADDING_BYTES(0x2F);
+};
+static_assert(sizeof(ModelInfo) == 0x36, "ModelInfo is an invalid size");
+
+struct RegisterInfo {
+    HLE::Applets::MiiData mii_data;
+    INSERT_PADDING_BYTES(0x2);
+    u16_be owner_mii_aes_ccm; // Mii data AES-CCM MAC
+    AmiiboName amiibo_name;
+    INSERT_PADDING_BYTES(0x2); // Zero string terminator
+    u8 flags;
+    u8 font_region;
+    WriteDate creation_date;
+    INSERT_PADDING_BYTES(0x2C);
+};
+static_assert(sizeof(RegisterInfo) == 0xA8, "RegisterInfo is an invalid size");
+
+struct RegisterInfoPrivate {
+    HLE::Applets::MiiData mii_data;
+    INSERT_PADDING_BYTES(0x2);
+    u16_be owner_mii_aes_ccm; // Mii data AES-CCM MAC
+    AmiiboName amiibo_name;
+    INSERT_PADDING_BYTES(0x2); // Zero string terminator
+    u8 flags;
+    u8 font_region;
+    WriteDate creation_date;
+    INSERT_PADDING_BYTES(0x28);
+};
+static_assert(sizeof(RegisterInfoPrivate) == 0xA4, "RegisterInfoPrivate is an invalid size");
+static_assert(std::is_trivial_v<RegisterInfoPrivate>, "RegisterInfoPrivate must be trivial.");
+static_assert(std::is_trivially_copyable_v<RegisterInfoPrivate>,
+              "RegisterInfoPrivate must be trivially copyable.");
+
+struct AdminInfo {
+    u64_be application_id;
+    u32_be application_area_id;
+    u16 crc_counter;
+    u8 flags;
+    PackedTagType tag_type;
+    AppAreaVersion app_area_version;
+    INSERT_PADDING_BYTES(0x7);
+    INSERT_PADDING_BYTES(0x28);
+};
+static_assert(sizeof(AdminInfo) == 0x40, "AdminInfo is an invalid size");
+
+} // namespace Service::NFC
diff --git a/src/core/hle/service/nfc/nfc_u.cpp b/src/core/hle/service/nfc/nfc_u.cpp
index 9950dc6ca..8aa36261c 100644
--- a/src/core/hle/service/nfc/nfc_u.cpp
+++ b/src/core/hle/service/nfc/nfc_u.cpp
@@ -20,23 +20,32 @@ NFC_U::NFC_U(std::shared_ptr<Module> nfc) : Module::Interface(std::move(nfc), "n
         {IPC::MakeHeader(0x0006, 0, 0), &NFC_U::StopTagScanning, "StopTagScanning"},
         {IPC::MakeHeader(0x0007, 0, 0), &NFC_U::LoadAmiiboData, "LoadAmiiboData"},
         {IPC::MakeHeader(0x0008, 0, 0), &NFC_U::ResetTagScanState, "ResetTagScanState"},
-        {IPC::MakeHeader(0x0009, 0, 2), nullptr, "UpdateStoredAmiiboData"},
+        {IPC::MakeHeader(0x0009, 0, 2), &NFC_U::UpdateStoredAmiiboData, "UpdateStoredAmiiboData"},
+        {IPC::MakeHeader(0x000A, 0, 0), nullptr, "Unknown0x0A"},
         {IPC::MakeHeader(0x000B, 0, 0), &NFC_U::GetTagInRangeEvent, "GetTagInRangeEvent"},
         {IPC::MakeHeader(0x000C, 0, 0), &NFC_U::GetTagOutOfRangeEvent, "GetTagOutOfRangeEvent"},
         {IPC::MakeHeader(0x000D, 0, 0), &NFC_U::GetTagState, "GetTagState"},
+        {IPC::MakeHeader(0x000E, 0, 0), nullptr, "Unknown0x0E"},
         {IPC::MakeHeader(0x000F, 0, 0), &NFC_U::CommunicationGetStatus, "CommunicationGetStatus"},
-        {IPC::MakeHeader(0x0010, 0, 0), nullptr, "GetTagInfo2"},
+        {IPC::MakeHeader(0x0010, 0, 0), &NFC_U::GetTagInfo2, "GetTagInfo2"},
         {IPC::MakeHeader(0x0011, 0, 0), &NFC_U::GetTagInfo, "GetTagInfo"},
-        {IPC::MakeHeader(0x0012, 0, 0), nullptr, "CommunicationGetResult"},
-        {IPC::MakeHeader(0x0013, 1, 0), nullptr, "OpenAppData"},
-        {IPC::MakeHeader(0x0014, 14, 4), nullptr, "InitializeWriteAppData"},
-        {IPC::MakeHeader(0x0015, 1, 0), nullptr, "ReadAppData"},
-        {IPC::MakeHeader(0x0016, 9, 2), nullptr, "WriteAppData"},
-        {IPC::MakeHeader(0x0017, 0, 0), nullptr, "GetAmiiboSettings"},
-        {IPC::MakeHeader(0x0018, 0, 0), &NFC_U::GetAmiiboConfig, "GetAmiiboConfig"},
-        {IPC::MakeHeader(0x0019, 0, 0), nullptr, "GetAppDataInitStruct"},
-        {IPC::MakeHeader(0x001A, 0, 0), &NFC_U::Unknown0x1A, "Unknown0x1A"},
+        {IPC::MakeHeader(0x0012, 0, 0), &NFC_U::CommunicationGetResult, "CommunicationGetResult"},
+        {IPC::MakeHeader(0x0013, 1, 0), &NFC_U::OpenAppData, "OpenAppData"},
+        {IPC::MakeHeader(0x0014, 14, 4), &NFC_U::InitializeWriteAppData, "InitializeWriteAppData"},
+        {IPC::MakeHeader(0x0015, 1, 0), &NFC_U::ReadAppData, "ReadAppData"},
+        {IPC::MakeHeader(0x0016, 9, 2), &NFC_U::WriteAppData, "WriteAppData"},
+        {IPC::MakeHeader(0x0017, 0, 0), &NFC_U::GetRegisterInfo, "GetRegisterInfo"},
+        {IPC::MakeHeader(0x0018, 0, 0), &NFC_U::GetCommonInfo, "GetCommonInfo"},
+        {IPC::MakeHeader(0x0019, 0, 0), &NFC_U::GetAppDataInitStruct, "GetAppDataInitStruct"},
+        {IPC::MakeHeader(0x001A, 0, 0), &NFC_U::LoadAmiiboPartially, "LoadAmiiboPartially"},
         {IPC::MakeHeader(0x001B, 0, 0), &NFC_U::GetIdentificationBlock, "GetIdentificationBlock"},
+        {IPC::MakeHeader(0x001C, 0, 0), nullptr, "Unknown0x1C"},
+        {IPC::MakeHeader(0x001D, 0, 0), nullptr, "Unknown0x1D"},
+        {IPC::MakeHeader(0x001E, 0, 0), nullptr, "Unknown0x1E"},
+        {IPC::MakeHeader(0x001F, 0, 0), nullptr, "Unknown0x1F"},
+        {IPC::MakeHeader(0x0020, 0, 0), nullptr, "Unknown0x20"},
+        {IPC::MakeHeader(0x0021, 0, 0), nullptr, "Unknown0x21"},
+        {IPC::MakeHeader(0x0022, 0, 0), nullptr, "Unknown0x22"},
         // clang-format on
     };
     RegisterHandlers(functions);