code:
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

// SPDX-FileCopyrightText: Copyright 2017 socram8888/amiitool
// SPDX-License-Identifier: MIT

#include <array>
#include <mbedtls/aes.h>
#include <mbedtls/hmac_drbg.h>

#include "common/fs/file.h"
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/logging/log.h"
#include "core/hle/service/nfc/common/amiibo_crypto.h"

namespace Service::NFP::AmiiboCrypto {

bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) {
    const auto& amiibo_data = ntag_file.user_memory;
    LOG_DEBUG(Service_NFP, "uuid_lock=0x{0:x}", ntag_file.static_lock);
    LOG_DEBUG(Service_NFP, "compatibility_container=0x{0:x}", ntag_file.compatibility_container);
    LOG_DEBUG(Service_NFP, "write_count={}", static_cast<u16>(amiibo_data.write_counter));

    LOG_DEBUG(Service_NFP, "character_id=0x{0:x}", amiibo_data.model_info.character_id);
    LOG_DEBUG(Service_NFP, "character_variant={}", amiibo_data.model_info.character_variant);
    LOG_DEBUG(Service_NFP, "amiibo_type={}", amiibo_data.model_info.amiibo_type);
    LOG_DEBUG(Service_NFP, "model_number=0x{0:x}",
              static_cast<u16>(amiibo_data.model_info.model_number));
    LOG_DEBUG(Service_NFP, "series={}", amiibo_data.model_info.series);
    LOG_DEBUG(Service_NFP, "tag_type=0x{0:x}", amiibo_data.model_info.tag_type);

    LOG_DEBUG(Service_NFP, "tag_dynamic_lock=0x{0:x}", ntag_file.dynamic_lock);
    LOG_DEBUG(Service_NFP, "tag_CFG0=0x{0:x}", ntag_file.CFG0);
    LOG_DEBUG(Service_NFP, "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.part1[0] ^ ntag_file.uuid.part1[1] ^ ntag_file.uuid.part1[2]) !=
        ntag_file.uuid.crc_check1) {
        return false;
    }
    if ((ntag_file.uuid.part2[0] ^ ntag_file.uuid.part2[1] ^ ntag_file.uuid.part2[2] ^
         ntag_file.uuid.nintendo_id) != ntag_file.uuid_crc_check2) {
        return false;
    }

    // Check against all know constants on an amiibo binary
    if (ntag_file.static_lock != 0xE00F) {
        return false;
    }
    if (ntag_file.compatibility_container != 0xEEFF10F1U) {
        return false;
    }
    if (amiibo_data.model_info.tag_type != NFC::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;
    encoded_data.uid_crc_check2 = nfc_data.uuid_crc_check2;
    encoded_data.internal_number = nfc_data.internal_number;
    encoded_data.static_lock = nfc_data.static_lock;
    encoded_data.compatibility_container = nfc_data.compatibility_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.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.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 = encoded_data.uid;
    nfc_data.uuid_crc_check2 = encoded_data.uid_crc_check2;
    nfc_data.internal_number = encoded_data.internal_number;
    nfc_data.static_lock = encoded_data.static_lock;
    nfc_data.compatibility_container = encoded_data.compatibility_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.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,
        .uid_2 = data.uid,
        .keygen_salt = data.keygen_salt,
    };

    return seed;
}

std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed) {
    const std::size_t seedPart1Len = sizeof(key.magic_bytes) - key.magic_length;
    const std::size_t string_size = key.type_string.size();
    std::vector<u8> output(string_size + seedPart1Len);

    // 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, seedPart1Len);

    // Append all bytes from magicBytes
    output.insert(output.end(), key.magic_bytes.begin(),
                  key.magic_bytes.begin() + key.magic_length);

    std::array<u8, sizeof(NFP::TagUuid)> seed_uuid{};
    memcpy(seed_uuid.data(), &seed.uid_1, sizeof(NFP::TagUuid));
    output.insert(output.end(), seed_uuid.begin(), seed_uuid.end());
    memcpy(seed_uuid.data(), &seed.uid_2, sizeof(NFP::TagUuid));
    output.insert(output.end(), seed_uuid.begin(), seed_uuid.end());

    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, mbedtls_md_context_t& hmac_ctx, const HmacKey& hmac_key,
                std::span<const 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
    mbedtls_md_init(&hmac_ctx);
    mbedtls_md_setup(&hmac_ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1);
    mbedtls_md_hmac_starts(&hmac_ctx, hmac_key.data(), hmac_key.size());
}

void CryptoStep(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, DrgbOutput& output) {
    // If used at least once, reinitialize the HMAC
    if (ctx.used) {
        mbedtls_md_hmac_reset(&hmac_ctx);
    }

    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
    mbedtls_md_hmac_update(&hmac_ctx, reinterpret_cast<const unsigned char*>(ctx.buffer.data()),
                           ctx.buffer_size);
    mbedtls_md_hmac_finish(&hmac_ctx, output.data());
}

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{};
    mbedtls_md_context_t 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));

    // Cleanup context
    mbedtls_md_free(&hmac_ctx);

    return derived_keys;
}

void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& out_data) {
    mbedtls_aes_context aes;
    std::size_t nc_off = 0;
    std::array<u8, sizeof(keys.aes_iv)> nonce_counter{};
    std::array<u8, sizeof(keys.aes_iv)> stream_block{};

    const auto aes_key_size = static_cast<u32>(keys.aes_key.size() * 8);
    mbedtls_aes_setkey_enc(&aes, keys.aes_key.data(), aes_key_size);
    memcpy(nonce_counter.data(), keys.aes_iv.data(), sizeof(keys.aes_iv));

    constexpr std::size_t encrypted_data_size = HMAC_TAG_START - SETTINGS_START;
    mbedtls_aes_crypt_ctr(&aes, encrypted_data_size, &nc_off, nonce_counter.data(),
                          stream_block.data(),
                          reinterpret_cast<const unsigned char*>(&in_data.settings),
                          reinterpret_cast<unsigned char*>(&out_data.settings));

    // Copy the rest of the data directly
    out_data.uid = in_data.uid;
    out_data.uid_crc_check2 = in_data.uid_crc_check2;
    out_data.internal_number = in_data.internal_number;
    out_data.static_lock = in_data.static_lock;
    out_data.compatibility_container = in_data.compatibility_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 yuzu_keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir);

    const Common::FS::IOFile keys_file{yuzu_keys_dir / "key_retail.bin",
                                       Common::FS::FileAccessMode::Read,
                                       Common::FS::FileType::BinaryFile};

    if (!keys_file.IsOpen()) {
        LOG_ERROR(Service_NFP, "Failed to open key file");
        return false;
    }

    if (keys_file.Read(unfixed_info) != 1) {
        LOG_ERROR(Service_NFP, "Failed to read unfixed_info");
        return false;
    }
    if (keys_file.Read(locked_secret) != 1) {
        LOG_ERROR(Service_NFP, "Failed to read locked-secret");
        return false;
    }

    return true;
}

bool IsKeyAvailable() {
    const auto yuzu_keys_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::KeysDir);
    return Common::FS::Exists(yuzu_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;
    mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), tag_keys.hmac_key.data(),
                    sizeof(HmacKey), reinterpret_cast<const unsigned char*>(&tag_data.uid),
                    input_length, reinterpret_cast<unsigned char*>(&tag_data.hmac_tag));

    // Regenerate data HMAC
    constexpr std::size_t input_length2 = DYNAMIC_LOCK_START - WRITE_COUNTER_START;
    mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), data_keys.hmac_key.data(),
                    sizeof(HmacKey),
                    reinterpret_cast<const unsigned char*>(&tag_data.write_counter), input_length2,
                    reinterpret_cast<unsigned char*>(&tag_data.hmac_data));

    if (tag_data.hmac_data != encrypted_tag_data.user_memory.hmac_data) {
        LOG_ERROR(Service_NFP, "hmac_data doesn't match");
        return false;
    }

    if (tag_data.hmac_tag != encrypted_tag_data.user_memory.hmac_tag) {
        LOG_ERROR(Service_NFP, "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;
    mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), tag_keys.hmac_key.data(),
                    sizeof(HmacKey), reinterpret_cast<const unsigned char*>(&tag_data.uid),
                    input_length, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag));

    // Init mbedtls HMAC context
    mbedtls_md_context_t ctx;
    mbedtls_md_init(&ctx);
    mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1);

    // Generate data HMAC
    mbedtls_md_hmac_starts(&ctx, data_keys.hmac_key.data(), sizeof(HmacKey));
    mbedtls_md_hmac_update(&ctx, reinterpret_cast<const unsigned char*>(&tag_data.write_counter),
                           input_length2); // Data
    mbedtls_md_hmac_update(&ctx, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag),
                           sizeof(HashData)); // Tag HMAC
    mbedtls_md_hmac_update(&ctx, reinterpret_cast<const unsigned char*>(&tag_data.uid),
                           input_length);
    mbedtls_md_hmac_finish(&ctx, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_data));

    // HMAC cleanup
    mbedtls_md_free(&ctx);

    // Encrypt
    Cipher(data_keys, tag_data, encoded_tag_data);

    // Convert back to hardware
    encrypted_tag_data = EncodedDataToNfcData(encoded_tag_data);

    return true;
}

} // namespace Service::NFP::AmiiboCrypto


code:
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once

#include <array>

#include "core/hle/service/nfp/nfp_types.h"

struct mbedtls_md_context_t;

namespace Service::NFP::AmiiboCrypto {
// Byte locations in Service::NFP::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;
    TagUuid uid_1;
    TagUuid uid_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, mbedtls_md_context_t& hmac_ctx, const HmacKey& hmac_key,
                std::span<const u8> seed);

// Feeds data to mbedtls context to generate the derived key
void CryptoStep(CryptoCtx& ctx, mbedtls_md_context_t& 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 encrypted 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::NFP::AmiiboCrypto

repo_name: litucks/torzu

license: mit


code:
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "core/hle/service/glue/time/static.h"
#include "core/hle/service/psc/time/steady_clock.h"
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4701) // Potentially uninitialized local variable 'result' used
#endif

#include <boost/crc.hpp>

#ifdef _MSC_VER
#pragma warning(pop)
#endif

#include <fmt/format.h>

#include "common/fs/file.h"
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/input.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "common/tiny_mt.h"
#include "core/core.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/mii/mii_manager.h"
#include "core/hle/service/nfc/common/amiibo_crypto.h"
#include "core/hle/service/nfc/common/device.h"
#include "core/hle/service/nfc/mifare_result.h"
#include "core/hle/service/nfc/nfc_result.h"
#include "core/hle/service/service.h"
#include "core/hle/service/sm/sm.h"
#include "hid_core/frontend/emulated_controller.h"
#include "hid_core/hid_core.h"
#include "hid_core/hid_types.h"

namespace Service::NFC {
NfcDevice::NfcDevice(Core::HID::NpadIdType npad_id_, Core::System& system_,
                     KernelHelpers::ServiceContext& service_context_,
                     Kernel::KEvent* availability_change_event_)
    : npad_id{npad_id_}, system{system_}, service_context{service_context_},
      availability_change_event{availability_change_event_} {
    activate_event = service_context.CreateEvent("NFC:ActivateEvent");
    deactivate_event = service_context.CreateEvent("NFC:DeactivateEvent");
    npad_device = system.HIDCore().GetEmulatedController(npad_id);

    Core::HID::ControllerUpdateCallback engine_callback{
        .on_change = [this](Core::HID::ControllerTriggerType type) { NpadUpdate(type); },
        .is_npad_service = false,
    };
    is_controller_set = true;
    callback_key = npad_device->SetCallback(engine_callback);
}

NfcDevice::~NfcDevice() {
    service_context.CloseEvent(activate_event);
    service_context.CloseEvent(deactivate_event);
    if (!is_controller_set) {
        return;
    }
    npad_device->DeleteCallback(callback_key);
    is_controller_set = false;
};

void NfcDevice::NpadUpdate(Core::HID::ControllerTriggerType type) {
    if (type == Core::HID::ControllerTriggerType::Connected) {
        Initialize();
        availability_change_event->Signal();
        return;
    }

    if (type == Core::HID::ControllerTriggerType::Disconnected) {
        Finalize();
        availability_change_event->Signal();
        return;
    }

    if (!is_initialized) {
        return;
    }

    if (!npad_device->IsConnected()) {
        return;
    }

    // Ensure nfc mode is always active
    if (npad_device->GetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex) ==
        Common::Input::PollingMode::Active) {
        npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
                                    Common::Input::PollingMode::NFC);
    }

    if (type != Core::HID::ControllerTriggerType::Nfc) {
        return;
    }

    const auto nfc_status = npad_device->GetNfc();
    switch (nfc_status.state) {
    case Common::Input::NfcState::NewAmiibo:
        LoadNfcTag(nfc_status.protocol, nfc_status.tag_type, nfc_status.uuid_length,
                   nfc_status.uuid);
        break;
    case Common::Input::NfcState::AmiiboRemoved:
        if (device_state == DeviceState::Initialized || device_state == DeviceState::TagRemoved) {
            break;
        }
        if (device_state != DeviceState::SearchingForTag) {
            CloseNfcTag();
        }
        break;
    default:
        break;
    }
}

bool NfcDevice::LoadNfcTag(u8 protocol, u8 tag_type, u8 uuid_length, UniqueSerialNumber uuid) {
    if (device_state != DeviceState::SearchingForTag) {
        LOG_ERROR(Service_NFC, "Game is not looking for nfc tag, current state {}", device_state);
        return false;
    }

    if ((protocol & static_cast<u8>(allowed_protocols)) == 0) {
        LOG_ERROR(Service_NFC, "Protocol not supported {}", protocol);
        return false;
    }

    real_tag_info = {
        .uuid = uuid,
        .uuid_length = uuid_length,
        .protocol = static_cast<NfcProtocol>(protocol),
        .tag_type = static_cast<TagType>(tag_type),
    };

    device_state = DeviceState::TagFound;
    deactivate_event->GetReadableEvent().Clear();
    activate_event->Signal();
    return true;
}

bool NfcDevice::LoadAmiiboData() {
    std::vector<u8> data{};

    if (!npad_device->ReadAmiiboData(data)) {
        return false;
    }

    if (data.size() < sizeof(NFP::EncryptedNTAG215File)) {
        LOG_ERROR(Service_NFC, "Not an amiibo, size={}", data.size());
        return false;
    }

    memcpy(&tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File));
    is_plain_amiibo = NFP::AmiiboCrypto::IsAmiiboValid(tag_data);
    is_write_protected = false;

    // Fallback for plain amiibos
    if (is_plain_amiibo) {
        LOG_INFO(Service_NFP, "Using plain amiibo");
        encrypted_tag_data = NFP::AmiiboCrypto::EncodedDataToNfcData(tag_data);
        return true;
    }

    // Fallback for encrypted amiibos without keys
    if (!NFP::AmiiboCrypto::IsKeyAvailable()) {
        LOG_INFO(Service_NFC, "Loading amiibo without keys");
        memcpy(&encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File));
        BuildAmiiboWithoutKeys(tag_data, encrypted_tag_data);
        is_plain_amiibo = true;
        is_write_protected = true;
        return true;
    }

    LOG_INFO(Service_NFP, "Using encrypted amiibo");
    tag_data = {};
    memcpy(&encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File));
    return true;
}

void NfcDevice::CloseNfcTag() {
    LOG_INFO(Service_NFC, "Remove nfc tag");

    if (device_state == DeviceState::TagMounted) {
        Unmount();
    }

    device_state = DeviceState::TagRemoved;
    encrypted_tag_data = {};
    tag_data = {};
    activate_event->GetReadableEvent().Clear();
    deactivate_event->Signal();
}

Kernel::KReadableEvent& NfcDevice::GetActivateEvent() const {
    return activate_event->GetReadableEvent();
}

Kernel::KReadableEvent& NfcDevice::GetDeactivateEvent() const {
    return deactivate_event->GetReadableEvent();
}

void NfcDevice::Initialize() {
    device_state = npad_device->HasNfc() ? DeviceState::Initialized : DeviceState::Unavailable;
    encrypted_tag_data = {};
    tag_data = {};

    if (device_state != DeviceState::Initialized) {
        return;
    }

    is_initialized = npad_device->AddNfcHandle();
}

void NfcDevice::Finalize() {
    if (npad_device->IsConnected()) {
        if (device_state == DeviceState::TagMounted) {
            Unmount();
        }
        if (device_state == DeviceState::SearchingForTag ||
            device_state == DeviceState::TagRemoved) {
            StopDetection();
        }
    }

    if (device_state != DeviceState::Unavailable) {
        npad_device->RemoveNfcHandle();
    }

    device_state = DeviceState::Unavailable;
    is_initialized = false;
}

Result NfcDevice::StartDetection(NfcProtocol allowed_protocol) {
    if (device_state != DeviceState::Initialized && device_state != DeviceState::TagRemoved) {
        LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
        return ResultWrongDeviceState;
    }

    if (!npad_device->StartNfcPolling()) {
        LOG_ERROR(Service_NFC, "Nfc polling not supported");
        return ResultNfcDisabled;
    }

    device_state = DeviceState::SearchingForTag;
    allowed_protocols = allowed_protocol;
    return ResultSuccess;
}

Result NfcDevice::StopDetection() {
    if (device_state == DeviceState::Initialized) {
        return ResultSuccess;
    }

    if (device_state == DeviceState::TagFound || device_state == DeviceState::TagMounted) {
        CloseNfcTag();
    }

    if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) {
        npad_device->StopNfcPolling();
        device_state = DeviceState::Initialized;
        return ResultSuccess;
    }

    LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
    return ResultWrongDeviceState;
}

Result NfcDevice::GetTagInfo(NFP::TagInfo& tag_info) const {
    if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) {
        LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
        if (device_state == DeviceState::TagRemoved) {
            return ResultTagRemoved;
        }
        return ResultWrongDeviceState;
    }

    tag_info = real_tag_info;

    // Generate random UUID to bypass amiibo load limits
    if (real_tag_info.tag_type == TagType::Type2 && Settings::values.random_amiibo_id) {
        Common::TinyMT rng{};
        rng.Initialize(static_cast<u32>(GetCurrentPosixTime()));
        rng.GenerateRandomBytes(tag_info.uuid.data(), tag_info.uuid_length);
    }

    return ResultSuccess;
}

Result NfcDevice::ReadMifare(std::span<const MifareReadBlockParameter> parameters,
                             std::span<MifareReadBlockData> read_block_data) const {
    if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) {
        LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
        if (device_state == DeviceState::TagRemoved) {
            return ResultTagRemoved;
        }
        return ResultWrongDeviceState;
    }

    Result result = ResultSuccess;

    TagInfo tag_info{};
    result = GetTagInfo(tag_info);

    if (result.IsError()) {
        return result;
    }

    if (tag_info.protocol != NfcProtocol::TypeA || tag_info.tag_type != TagType::Mifare) {
        return ResultInvalidTagType;
    }

    if (parameters.size() == 0) {
        return ResultInvalidArgument;
    }

    Common::Input::MifareRequest request{};
    Common::Input::MifareRequest out_data{};
    const auto unknown = parameters[0].sector_key.unknown;
    for (std::size_t i = 0; i < parameters.size(); i++) {
        if (unknown != parameters[i].sector_key.unknown) {
            return ResultInvalidArgument;
        }
    }

    for (std::size_t i = 0; i < parameters.size(); i++) {
        if (parameters[i].sector_key.command == MifareCmd::None) {
            continue;
        }
        request.data[i].command = static_cast<u8>(parameters[i].sector_key.command);
        request.data[i].sector = parameters[i].sector_number;
        memcpy(request.data[i].key.data(), parameters[i].sector_key.sector_key.data(),
               sizeof(KeyData));
    }

    if (!npad_device->ReadMifareData(request, out_data)) {
        return ResultMifareError288;
    }

    for (std::size_t i = 0; i < read_block_data.size(); i++) {
        if (static_cast<MifareCmd>(out_data.data[i].command) == MifareCmd::None) {
            continue;
        }

        read_block_data[i] = {
            .data = out_data.data[i].data,
            .sector_number = out_data.data[i].sector,
        };
    }

    return ResultSuccess;
}

Result NfcDevice::WriteMifare(std::span<const MifareWriteBlockParameter> parameters) {
    Result result = ResultSuccess;

    TagInfo tag_info{};
    result = GetTagInfo(tag_info);

    if (result.IsError()) {
        return result;
    }

    if (tag_info.protocol != NfcProtocol::TypeA || tag_info.tag_type != TagType::Mifare) {
        return ResultInvalidTagType;
    }

    if (parameters.size() == 0) {
        return ResultInvalidArgument;
    }

    const auto unknown = parameters[0].sector_key.unknown;
    for (std::size_t i = 0; i < parameters.size(); i++) {
        if (unknown != parameters[i].sector_key.unknown) {
            return ResultInvalidArgument;
        }
    }

    Common::Input::MifareRequest request{};
    for (std::size_t i = 0; i < parameters.size(); i++) {
        if (parameters[i].sector_key.command == MifareCmd::None) {
            continue;
        }
        request.data[i].command = static_cast<u8>(parameters[i].sector_key.command);
        request.data[i].sector = parameters[i].sector_number;
        memcpy(request.data[i].key.data(), parameters[i].sector_key.sector_key.data(),
               sizeof(KeyData));
        memcpy(request.data[i].data.data(), parameters[i].data.data(), sizeof(KeyData));
    }

    if (!npad_device->WriteMifareData(request)) {
        return ResultMifareError288;
    }

    return result;
}

Result NfcDevice::SendCommandByPassThrough(const s64& timeout, std::span<const u8> command_data,
                                           std::span<u8> out_data) {
    // Not implemented
    return ResultSuccess;
}

Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target_) {
    bool is_corrupted = false;

    if (model_type != NFP::ModelType::Amiibo) {
        return ResultInvalidArgument;
    }

    if (device_state != DeviceState::TagFound) {
        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
        return ResultWrongDeviceState;
    }

    if (!LoadAmiiboData()) {
        LOG_ERROR(Service_NFP, "Not an amiibo");
        return ResultInvalidTagType;
    }

    if (!NFP::AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) {
        LOG_ERROR(Service_NFP, "Not an amiibo");
        return ResultInvalidTagType;
    }

    // The loaded amiibo is not encrypted
    if (is_plain_amiibo) {
        std::vector<u8> data(sizeof(NFP::NTAG215File));
        memcpy(data.data(), &tag_data, sizeof(tag_data));
    }

    if (!is_plain_amiibo && !NFP::AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) {
        LOG_ERROR(Service_NFP, "Can't decode amiibo");
        is_corrupted = true;
    }

    if (tag_data.settings.settings.amiibo_initialized && !tag_data.owner_mii.IsValid()) {
        LOG_ERROR(Service_NFP, "Invalid mii data");
        is_corrupted = true;
    }

    device_state = DeviceState::TagMounted;
    mount_target = mount_target_;

    const bool create_backup =
        mount_target == NFP::MountTarget::All || mount_target == NFP::MountTarget::Ram ||
        (mount_target == NFP::MountTarget::Rom && HasBackup(encrypted_tag_data.uuid).IsError());
    if (!is_corrupted && create_backup) {
        std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File));
        memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data));
        WriteBackupData(encrypted_tag_data.uuid, data);
    }

    if (is_corrupted && mount_target != NFP::MountTarget::Rom) {
        bool has_backup = HasBackup(encrypted_tag_data.uuid).IsSuccess();
        return has_backup ? ResultCorruptedDataWithBackup : ResultCorruptedData;
    }

    return ResultSuccess;
}

Result NfcDevice::Unmount() {
    if (device_state != DeviceState::TagMounted) {
        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
        if (device_state == DeviceState::TagRemoved) {
            return ResultTagRemoved;
        }
        return ResultWrongDeviceState;
    }

    // Save data before unloading the amiibo
    if (is_data_moddified) {
        Flush();
    }

    device_state = DeviceState::TagFound;
    mount_target = NFP::MountTarget::None;
    is_app_area_open = false;

    return ResultSuccess;
}

Result NfcDevice::Flush() {
    if (device_state != DeviceState::TagMounted) {
        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
        if (device_state == DeviceState::TagRemoved) {
            return ResultTagRemoved;
        }
        return ResultWrongDeviceState;
    }

    if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
        LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
        return ResultWrongDeviceState;
    }

    auto& settings = tag_data.settings;

    const auto& current_date = GetAmiiboDate(GetCurrentPosixTime());
    if (settings.write_date.raw_date != current_date.raw_date) {
        settings.write_date = current_date;
        UpdateSettingsCrc();
    }

    tag_data.write_counter++;

    const auto result = FlushWithBreak(NFP::BreakType::Normal);

    is_data_moddified = false;

    return result;
}

Result NfcDevice::FlushDebug() {
    if (device_state != DeviceState::TagMounted) {
        LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
        if (device_state == DeviceState::TagRemoved) {
            return ResultTagRemoved;
        }
        return ResultWrongDeviceState;
    }

    if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
        LOG_ERROR(Service_NFC, "Amiibo is read only", device_state);
        return ResultWrongDeviceState;
    }

    tag_data.write_counter++;

    const auto result = FlushWithBreak(NFP::BreakType::Normal);

    is_data_moddified = false;

    return result;
}

Result NfcDevice::FlushWithBreak(NFP::BreakType break_type) {
    if (break_type != NFP::BreakType::Normal) {
        LOG_ERROR(Service_NFC, "Break type not implemented {}", break_type);
        return ResultWrongDeviceState;
    }

    if (is_write_protected) {
        LOG_ERROR(Service_NFP, "No keys available skipping write request");
        return ResultSuccess;
    }

    std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File));
    if (is_plain_amiibo) {
        memcpy(data.data(), &tag_data, sizeof(tag_data));
        WriteBackupData(tag_data.uid, data);
    } else {
        if (!NFP::AmiiboCrypto::EncodeAmiibo(tag_data, encrypted_tag_data)) {
            LOG_ERROR(Service_NFP, "Failed to encode data");
            return ResultWriteAmiiboFailed;
        }

        memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data));
        WriteBackupData(encrypted_tag_data.uuid, data);
    }

    if (!npad_device->WriteNfc(data)) {
        LOG_ERROR(Service_NFP, "Error writing to file");
        return ResultWriteAmiiboFailed;
    }

    return ResultSuccess;
}

Result NfcDevice::Restore() {
    if (device_state != DeviceState::TagFound) {
        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
        if (device_state == DeviceState::TagRemoved) {
            return ResultTagRemoved;
        }
        return ResultWrongDeviceState;
    }

    NFC::TagInfo tag_info{};
    std::array<u8, sizeof(NFP::EncryptedNTAG215File)> data{};
    Result result = GetTagInfo(tag_info);

    if (result.IsError()) {
        return result;
    }

    result = ReadBackupData(tag_info.uuid, tag_info.uuid_length, data);

    if (result.IsError()) {
        return result;
    }

    NFP::NTAG215File temporary_tag_data{};
    NFP::EncryptedNTAG215File temporary_encrypted_tag_data{};

    // Fallback for encrypted amiibos without keys
    if (is_write_protected) {
        return ResultWriteAmiiboFailed;
    }

    // Fallback for plain amiibos
    if (is_plain_amiibo) {
        LOG_INFO(Service_NFP, "Restoring backup of plain amiibo");
        memcpy(&temporary_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File));
        temporary_encrypted_tag_data = NFP::AmiiboCrypto::EncodedDataToNfcData(temporary_tag_data);
    }

    if (!is_plain_amiibo) {
        LOG_INFO(Service_NFP, "Restoring backup of encrypted amiibo");
        temporary_tag_data = {};
        memcpy(&temporary_encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File));
    }

    if (!NFP::AmiiboCrypto::IsAmiiboValid(temporary_encrypted_tag_data)) {
        return ResultInvalidTagType;
    }

    if (!is_plain_amiibo) {
        if (!NFP::AmiiboCrypto::DecodeAmiibo(temporary_encrypted_tag_data, temporary_tag_data)) {
            LOG_ERROR(Service_NFP, "Can't decode amiibo");
            return ResultCorruptedData;
        }
    }

    // Restore mii data in case is corrupted by previous instances of yuzu
    if (tag_data.settings.settings.amiibo_initialized && !tag_data.owner_mii.IsValid()) {
        LOG_ERROR(Service_NFP, "Regenerating mii data");
        Mii::StoreData new_mii{};
        new_mii.BuildRandom(Mii::Age::All, Mii::Gender::All, Mii::Race::All);
        new_mii.SetNickname({u'y', u'u', u'z', u'u', u'\0'});

        tag_data.owner_mii.BuildFromStoreData(new_mii);
        tag_data.mii_extension.SetFromStoreData(new_mii);
    }

    // Overwrite tag contents with backup and mount the tag
    tag_data = temporary_tag_data;
    encrypted_tag_data = temporary_encrypted_tag_data;
    device_state = DeviceState::TagMounted;
    mount_target = NFP::MountTarget::All;
    is_data_moddified = true;

    return ResultSuccess;
}

Result NfcDevice::GetCommonInfo(NFP::CommonInfo& common_info) const {
    if (device_state != DeviceState::TagMounted) {
        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
        if (device_state == DeviceState::TagRemoved) {
            return ResultTagRemoved;
        }
        return ResultWrongDeviceState;
    }

    if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
        LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
        return ResultWrongDeviceState;
    }

    const auto& settings = tag_data.settings;

    // TODO: Validate this data
    common_info = {
        .last_write_date = settings.write_date.GetWriteDate(),
        .write_counter = tag_data.application_write_counter,
        .version = tag_data.amiibo_version,
        .application_area_size = sizeof(NFP::ApplicationArea),
    };
    return ResultSuccess;
}

Result NfcDevice::GetModelInfo(NFP::ModelInfo& model_info) const {
    if (device_state != DeviceState::TagMounted) {
        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
        if (device_state == DeviceState::TagRemoved) {
            return ResultTagRemoved;
        }
        return ResultWrongDeviceState;
    }

    const auto& model_info_data = encrypted_tag_data.user_memory.model_info;

    model_info = {
        .character_id = model_info_data.character_id,
        .character_variant = model_info_data.character_variant,
        .amiibo_type = model_info_data.amiibo_type,
        .model_number = model_info_data.model_number,
        .series = model_info_data.series,
    };
    return ResultSuccess;
}

Result NfcDevice::GetRegisterInfo(NFP::RegisterInfo& register_info) const {
    if (device_state != DeviceState::TagMounted) {
        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
        if (device_state == DeviceState::TagRemoved) {
            return ResultTagRemoved;
        }
        return ResultWrongDeviceState;
    }

    if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
        LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
        return ResultWrongDeviceState;
    }

    if (tag_data.settings.settings.amiibo_initialized == 0) {
        return ResultRegistrationIsNotInitialized;
    }

    Mii::CharInfo char_info{};
    Mii::StoreData store_data{};
    tag_data.owner_mii.BuildToStoreData(store_data);
    char_info.SetFromStoreData(store_data);

    const auto& settings = tag_data.settings;

    // TODO: Validate this data
    register_info = {
        .mii_char_info = char_info,
        .creation_date = settings.init_date.GetWriteDate(),
        .amiibo_name = GetAmiiboName(settings),
        .font_region = settings.settings.font_region,
    };

    return ResultSuccess;
}

Result NfcDevice::GetRegisterInfoPrivate(NFP::RegisterInfoPrivate& register_info) const {
    if (device_state != DeviceState::TagMounted) {
        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
        if (device_state == DeviceState::TagRemoved) {
            return ResultTagRemoved;
        }
        return ResultWrongDeviceState;
    }

    if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
        LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
        return ResultWrongDeviceState;
    }

    if (tag_data.settings.settings.amiibo_initialized == 0) {
        return ResultRegistrationIsNotInitialized;
    }

    Mii::StoreData store_data{};
    const auto& settings = tag_data.settings;
    tag_data.owner_mii.BuildToStoreData(store_data);

    // TODO: Validate and complete this data
    register_info = {
        .mii_store_data = store_data,
        .creation_date = settings.init_date.GetWriteDate(),
        .amiibo_name = GetAmiiboName(settings),
        .font_region = settings.settings.font_region,
    };

    return ResultSuccess;
}

Result NfcDevice::GetAdminInfo(NFP::AdminInfo& admin_info) const {
    if (device_state != DeviceState::TagMounted) {
        LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
        if (device_state == DeviceState::TagRemoved) {
            return ResultTagRemoved;
        }
        return ResultWrongDeviceState;
    }

    if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
        LOG_ERROR(Service_NFC, "Amiibo is read only", device_state);
        return ResultWrongDeviceState;
    }

    u8 flags = static_cast<u8>(tag_data.settings.settings.raw >> 0x4);
    if (tag_data.settings.settings.amiibo_initialized == 0) {
        flags = flags & 0xfe;
    }

    u64 application_id = 0;
    u32 application_area_id = 0;
    NFP::AppAreaVersion app_area_version = NFP::AppAreaVersion::NotSet;
    if (tag_data.settings.settings.appdata_initialized != 0) {
        application_id = tag_data.application_id;
        app_area_version = static_cast<NFP::AppAreaVersion>(
            application_id >> NFP::application_id_version_offset & 0xf);

        // Restore application id to original value
        if (application_id >> 0x38 != 0) {
            const u8 application_byte = tag_data.application_id_byte & 0xf;
            application_id =
                RemoveVersionByte(application_id) |
                (static_cast<u64>(application_byte) << NFP::application_id_version_offset);
        }

        application_area_id = tag_data.application_area_id;
    }

    // TODO: Validate this data
    admin_info = {
        .application_id = application_id,
        .application_area_id = application_area_id,
        .crc_change_counter = tag_data.settings.crc_counter,
        .flags = flags,
        .tag_type = PackedTagType::Type2,
        .app_area_version = app_area_version,
    };

    return ResultSuccess;
}

Result NfcDevice::DeleteRegisterInfo() {
    if (device_state != DeviceState::TagMounted) {
        LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
        if (device_state == DeviceState::TagRemoved) {
            return ResultTagRemoved;
        }
        return ResultWrongDeviceState;
    }

    if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
        LOG_ERROR(Service_NFC, "Amiibo is read only", device_state);
        return ResultWrongDeviceState;
    }

    if (tag_data.settings.settings.amiibo_initialized == 0) {
        return ResultRegistrationIsNotInitialized;
    }

    Common::TinyMT rng{};
    rng.Initialize(static_cast<u32>(GetCurrentPosixTime()));
    rng.GenerateRandomBytes(&tag_data.owner_mii, sizeof(tag_data.owner_mii));
    rng.GenerateRandomBytes(&tag_data.settings.amiibo_name, sizeof(tag_data.settings.amiibo_name));
    rng.GenerateRandomBytes(&tag_data.unknown, sizeof(u8));
    rng.GenerateRandomBytes(&tag_data.unknown2[0], sizeof(u32));
    rng.GenerateRandomBytes(&tag_data.unknown2[1], sizeof(u32));
    rng.GenerateRandomBytes(&tag_data.register_info_crc, sizeof(u32));
    rng.GenerateRandomBytes(&tag_data.settings.init_date, sizeof(u32));
    tag_data.settings.settings.font_region.Assign(0);
    tag_data.settings.settings.amiibo_initialized.Assign(0);

    return Flush();
}

Result NfcDevice::SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& register_info) {
    if (device_state != DeviceState::TagMounted) {
        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
        if (device_state == DeviceState::TagRemoved) {
            return ResultTagRemoved;
        }
        return ResultWrongDeviceState;
    }

    if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
        LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
        return ResultWrongDeviceState;
    }

    auto& settings = tag_data.settings;

    if (tag_data.settings.settings.amiibo_initialized == 0) {
        settings.init_date = GetAmiiboDate(GetCurrentPosixTime());
        settings.write_date.raw_date = 0;
    }

    SetAmiiboName(settings, register_info.amiibo_name);
    tag_data.owner_mii.BuildFromStoreData(register_info.mii_store_data);
    tag_data.mii_extension.SetFromStoreData(register_info.mii_store_data);
    tag_data.unknown = 0;
    tag_data.unknown2 = {};
    settings.country_code_id = 0;
    settings.settings.font_region.Assign(0);
    settings.settings.amiibo_initialized.Assign(1);

    UpdateRegisterInfoCrc();

    return Flush();
}

Result NfcDevice::Format() {
    Result result = ResultSuccess;

    if (device_state == DeviceState::TagFound) {
        result = Mount(NFP::ModelType::Amiibo, NFP::MountTarget::All);
    }

    // We are formatting all data. Corruption is not an issue.
    if (result.IsError() &&
        (result != ResultCorruptedData && result != ResultCorruptedDataWithBackup)) {
        return result;
    }

    DeleteApplicationArea();
    DeleteRegisterInfo();

    return Flush();
}

Result NfcDevice::OpenApplicationArea(u32 access_id) {
    if (device_state != DeviceState::TagMounted) {
        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
        if (device_state == DeviceState::TagRemoved) {
            return ResultTagRemoved;
        }
        return ResultWrongDeviceState;
    }

    if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
        LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
        return ResultWrongDeviceState;
    }

    if (tag_data.settings.settings.appdata_initialized.Value() == 0) {
        LOG_WARNING(Service_NFP, "Application area is not initialized");
        return ResultApplicationAreaIsNotInitialized;
    }

    if (tag_data.application_area_id != access_id) {
        LOG_WARNING(Service_NFP, "Wrong application area id");
        return ResultWrongApplicationAreaId;
    }

    is_app_area_open = true;

    return ResultSuccess;
}

Result NfcDevice::GetApplicationAreaId(u32& application_area_id) const {
    application_area_id = {};

    if (device_state != DeviceState::TagMounted) {
        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
        if (device_state == DeviceState::TagRemoved) {
            return ResultTagRemoved;
        }
        return ResultWrongDeviceState;
    }

    if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
        LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
        return ResultWrongDeviceState;
    }

    if (tag_data.settings.settings.appdata_initialized.Value() == 0) {
        LOG_WARNING(Service_NFP, "Application area is not initialized");
        return ResultApplicationAreaIsNotInitialized;
    }

    application_area_id = tag_data.application_area_id;

    return ResultSuccess;
}

Result NfcDevice::GetApplicationArea(std::span<u8> data) const {
    if (device_state != DeviceState::TagMounted) {
        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
        if (device_state == DeviceState::TagRemoved) {
            return ResultTagRemoved;
        }
        return ResultWrongDeviceState;
    }

    if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
        LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
        return ResultWrongDeviceState;
    }

    if (!is_app_area_open) {
        LOG_ERROR(Service_NFP, "Application area is not open");
        return ResultWrongDeviceState;
    }

    if (tag_data.settings.settings.appdata_initialized.Value() == 0) {
        LOG_ERROR(Service_NFP, "Application area is not initialized");
        return ResultApplicationAreaIsNotInitialized;
    }

    memcpy(data.data(), tag_data.application_area.data(),
           std::min(data.size(), sizeof(NFP::ApplicationArea)));

    return ResultSuccess;
}

Result NfcDevice::SetApplicationArea(std::span<const u8> data) {
    if (device_state != DeviceState::TagMounted) {
        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
        if (device_state == DeviceState::TagRemoved) {
            return ResultTagRemoved;
        }
        return ResultWrongDeviceState;
    }

    if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
        LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
        return ResultWrongDeviceState;
    }

    if (!is_app_area_open) {
        LOG_ERROR(Service_NFP, "Application area is not open");
        return ResultWrongDeviceState;
    }

    if (tag_data.settings.settings.appdata_initialized.Value() == 0) {
        LOG_ERROR(Service_NFP, "Application area is not initialized");
        return ResultApplicationAreaIsNotInitialized;
    }

    if (data.size() > sizeof(NFP::ApplicationArea)) {
        LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
        return ResultUnknown;
    }

    Common::TinyMT rng{};
    rng.Initialize(static_cast<u32>(GetCurrentPosixTime()));
    std::memcpy(tag_data.application_area.data(), data.data(), data.size());
    // Fill remaining data with random numbers
    rng.GenerateRandomBytes(tag_data.application_area.data() + data.size(),
                            sizeof(NFP::ApplicationArea) - data.size());

    if (tag_data.application_write_counter != NFP::counter_limit) {
        tag_data.application_write_counter++;
    }

    is_data_moddified = true;

    return ResultSuccess;
}

Result NfcDevice::CreateApplicationArea(u32 access_id, std::span<const u8> data) {
    if (device_state != DeviceState::TagMounted) {
        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
        if (device_state == DeviceState::TagRemoved) {
            return ResultTagRemoved;
        }
        return ResultWrongDeviceState;
    }

    if (tag_data.settings.settings.appdata_initialized.Value() != 0) {
        LOG_ERROR(Service_NFP, "Application area already exist");
        return ResultApplicationAreaExist;
    }

    return RecreateApplicationArea(access_id, data);
}

Result NfcDevice::RecreateApplicationArea(u32 access_id, std::span<const u8> data) {
    if (device_state != DeviceState::TagMounted) {
        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
        if (device_state == DeviceState::TagRemoved) {
            return ResultTagRemoved;
        }
        return ResultWrongDeviceState;
    }

    if (is_app_area_open) {
        LOG_ERROR(Service_NFP, "Application area is open");
        return ResultWrongDeviceState;
    }

    if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
        LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
        return ResultWrongDeviceState;
    }

    if (data.size() > sizeof(NFP::ApplicationArea)) {
        LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
        return ResultWrongApplicationAreaSize;
    }

    Common::TinyMT rng{};
    rng.Initialize(static_cast<u32>(GetCurrentPosixTime()));
    std::memcpy(tag_data.application_area.data(), data.data(), data.size());
    // Fill remaining data with random numbers
    rng.GenerateRandomBytes(tag_data.application_area.data() + data.size(),
                            sizeof(NFP::ApplicationArea) - data.size());

    if (tag_data.application_write_counter != NFP::counter_limit) {
        tag_data.application_write_counter++;
    }

    const u64 application_id = system.GetApplicationProcessProgramID();

    tag_data.application_id_byte =
        static_cast<u8>(application_id >> NFP::application_id_version_offset & 0xf);
    tag_data.application_id =
        RemoveVersionByte(application_id) | (static_cast<u64>(NFP::AppAreaVersion::NintendoSwitch)
                                             << NFP::application_id_version_offset);
    tag_data.settings.settings.appdata_initialized.Assign(1);
    tag_data.application_area_id = access_id;
    tag_data.unknown = {};
    tag_data.unknown2 = {};

    UpdateRegisterInfoCrc();

    return Flush();
}

Result NfcDevice::DeleteApplicationArea() {
    if (device_state != DeviceState::TagMounted) {
        LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
        if (device_state == DeviceState::TagRemoved) {
            return ResultTagRemoved;
        }
        return ResultWrongDeviceState;
    }

    if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
        LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
        return ResultWrongDeviceState;
    }

    if (tag_data.settings.settings.appdata_initialized == 0) {
        return ResultApplicationAreaIsNotInitialized;
    }

    if (tag_data.application_write_counter != NFP::counter_limit) {
        tag_data.application_write_counter++;
    }

    Common::TinyMT rng{};
    rng.Initialize(static_cast<u32>(GetCurrentPosixTime()));
    rng.GenerateRandomBytes(tag_data.application_area.data(), sizeof(NFP::ApplicationArea));
    rng.GenerateRandomBytes(&tag_data.application_id, sizeof(u64));
    rng.GenerateRandomBytes(&tag_data.application_area_id, sizeof(u32));
    rng.GenerateRandomBytes(&tag_data.application_id_byte, sizeof(u8));
    tag_data.settings.settings.appdata_initialized.Assign(0);
    tag_data.unknown = {};
    tag_data.unknown2 = {};
    is_app_area_open = false;

    UpdateRegisterInfoCrc();

    return Flush();
}

Result NfcDevice::ExistsApplicationArea(bool& has_application_area) const {
    if (device_state != DeviceState::TagMounted) {
        LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
        if (device_state == DeviceState::TagRemoved) {
            return ResultTagRemoved;
        }
        return ResultWrongDeviceState;
    }

    if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
        LOG_ERROR(Service_NFC, "Amiibo is read only", device_state);
        return ResultWrongDeviceState;
    }

    has_application_area = tag_data.settings.settings.appdata_initialized.Value() != 0;

    return ResultSuccess;
}

Result NfcDevice::GetAll(NFP::NfpData& data) const {
    if (device_state != DeviceState::TagMounted) {
        LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
        if (device_state == DeviceState::TagRemoved) {
            return ResultTagRemoved;
        }
        return ResultWrongDeviceState;
    }

    if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
        LOG_ERROR(Service_NFC, "Amiibo is read only", device_state);
        return ResultWrongDeviceState;
    }

    NFP::CommonInfo common_info{};
    const u64 application_id = tag_data.application_id;

    GetCommonInfo(common_info);

    data = {
        .magic = tag_data.constant_value,
        .write_counter = tag_data.write_counter,
        .settings_crc = tag_data.settings.crc,
        .common_info = common_info,
        .mii_char_info = tag_data.owner_mii,
        .mii_store_data_extension = tag_data.mii_extension,
        .creation_date = tag_data.settings.init_date.GetWriteDate(),
        .amiibo_name = tag_data.settings.amiibo_name,
        .amiibo_name_null_terminated = 0,
        .settings = tag_data.settings.settings,
        .unknown1 = tag_data.unknown,
        .register_info_crc = tag_data.register_info_crc,
        .unknown2 = tag_data.unknown2,
        .application_id = application_id,
        .access_id = tag_data.application_area_id,
        .settings_crc_counter = tag_data.settings.crc_counter,
        .font_region = tag_data.settings.settings.font_region,
        .tag_type = PackedTagType::Type2,
        .console_type = static_cast<NFP::AppAreaVersion>(
            application_id >> NFP::application_id_version_offset & 0xf),
        .application_id_byte = tag_data.application_id_byte,
        .application_area = tag_data.application_area,
    };

    return ResultSuccess;
}

Result NfcDevice::SetAll(const NFP::NfpData& data) {
    if (device_state != DeviceState::TagMounted) {
        LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
        if (device_state == DeviceState::TagRemoved) {
            return ResultTagRemoved;
        }
        return ResultWrongDeviceState;
    }

    if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
        LOG_ERROR(Service_NFC, "Amiibo is read only", device_state);
        return ResultWrongDeviceState;
    }

    tag_data.constant_value = data.magic;
    tag_data.write_counter = data.write_counter;
    tag_data.settings.crc = data.settings_crc;
    tag_data.settings.write_date.SetWriteDate(data.common_info.last_write_date);
    tag_data.write_counter = data.common_info.write_counter;
    tag_data.amiibo_version = data.common_info.version;
    tag_data.owner_mii = data.mii_char_info;
    tag_data.mii_extension = data.mii_store_data_extension;
    tag_data.settings.init_date.SetWriteDate(data.creation_date);
    tag_data.settings.amiibo_name = data.amiibo_name;
    tag_data.settings.settings = data.settings;
    tag_data.unknown = data.unknown1;
    tag_data.register_info_crc = data.register_info_crc;
    tag_data.unknown2 = data.unknown2;
    tag_data.application_id = data.application_id;
    tag_data.application_area_id = data.access_id;
    tag_data.settings.crc_counter = data.settings_crc_counter;
    tag_data.settings.settings.font_region.Assign(data.font_region);
    tag_data.application_id_byte = data.application_id_byte;
    tag_data.application_area = data.application_area;

    return ResultSuccess;
}

Result NfcDevice::BreakTag(NFP::BreakType break_type) {
    if (device_state != DeviceState::TagMounted) {
        LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
        if (device_state == DeviceState::TagRemoved) {
            return ResultTagRemoved;
        }
        return ResultWrongDeviceState;
    }

    if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
        LOG_ERROR(Service_NFC, "Amiibo is read only", device_state);
        return ResultWrongDeviceState;
    }

    // TODO: Complete this implementation

    return FlushWithBreak(break_type);
}

Result NfcDevice::HasBackup(const UniqueSerialNumber& uid, std::size_t uuid_size) const {
    ASSERT_MSG(uuid_size < sizeof(UniqueSerialNumber), "Invalid UUID size");
    constexpr auto backup_dir = "backup";
    const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir);
    const auto file_name =
        fmt::format("{0:02x}.bin", fmt::join(uid.begin(), uid.begin() + uuid_size, ""));

    if (!Common::FS::Exists(yuzu_amiibo_dir / backup_dir / file_name)) {
        return ResultUnableToAccessBackupFile;
    }

    return ResultSuccess;
}

Result NfcDevice::HasBackup(const NFP::TagUuid& tag_uid) const {
    UniqueSerialNumber uuid{};
    memcpy(uuid.data(), &tag_uid, sizeof(NFP::TagUuid));
    return HasBackup(uuid, sizeof(NFP::TagUuid));
}

Result NfcDevice::ReadBackupData(const UniqueSerialNumber& uid, std::size_t uuid_size,
                                 std::span<u8> data) const {
    ASSERT_MSG(uuid_size < sizeof(UniqueSerialNumber), "Invalid UUID size");
    constexpr auto backup_dir = "backup";
    const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir);
    const auto file_name =
        fmt::format("{0:02x}.bin", fmt::join(uid.begin(), uid.begin() + uuid_size, ""));

    const Common::FS::IOFile keys_file{yuzu_amiibo_dir / backup_dir / file_name,
                                       Common::FS::FileAccessMode::Read,
                                       Common::FS::FileType::BinaryFile};

    if (!keys_file.IsOpen()) {
        LOG_ERROR(Service_NFP, "Failed to open amiibo backup");
        return ResultUnableToAccessBackupFile;
    }

    if (keys_file.Read(data) != data.size()) {
        LOG_ERROR(Service_NFP, "Failed to read amiibo backup");
        return ResultUnableToAccessBackupFile;
    }

    return ResultSuccess;
}

Result NfcDevice::ReadBackupData(const NFP::TagUuid& tag_uid, std::span<u8> data) const {
    UniqueSerialNumber uuid{};
    memcpy(uuid.data(), &tag_uid, sizeof(NFP::TagUuid));
    return ReadBackupData(uuid, sizeof(NFP::TagUuid), data);
}

Result NfcDevice::WriteBackupData(const UniqueSerialNumber& uid, std::size_t uuid_size,
                                  std::span<const u8> data) {
    ASSERT_MSG(uuid_size < sizeof(UniqueSerialNumber), "Invalid UUID size");
    constexpr auto backup_dir = "backup";
    const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir);
    const auto file_name =
        fmt::format("{0:02x}.bin", fmt::join(uid.begin(), uid.begin() + uuid_size, ""));

    if (HasBackup(uid, uuid_size).IsError()) {
        if (!Common::FS::CreateDir(yuzu_amiibo_dir / backup_dir)) {
            return ResultBackupPathAlreadyExist;
        }

        if (!Common::FS::NewFile(yuzu_amiibo_dir / backup_dir / file_name)) {
            return ResultBackupPathAlreadyExist;
        }
    }

    const Common::FS::IOFile keys_file{yuzu_amiibo_dir / backup_dir / file_name,
                                       Common::FS::FileAccessMode::ReadWrite,
                                       Common::FS::FileType::BinaryFile};

    if (!keys_file.IsOpen()) {
        LOG_ERROR(Service_NFP, "Failed to open amiibo backup");
        return ResultUnableToAccessBackupFile;
    }

    if (keys_file.Write(data) != data.size()) {
        LOG_ERROR(Service_NFP, "Failed to write amiibo backup");
        return ResultUnableToAccessBackupFile;
    }

    return ResultSuccess;
}

Result NfcDevice::WriteBackupData(const NFP::TagUuid& tag_uid, std::span<const u8> data) {
    UniqueSerialNumber uuid{};
    memcpy(uuid.data(), &tag_uid, sizeof(NFP::TagUuid));
    return WriteBackupData(uuid, sizeof(NFP::TagUuid), data);
}

Result NfcDevice::WriteNtf(std::span<const u8> data) {
    if (device_state != DeviceState::TagMounted) {
        LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
        if (device_state == DeviceState::TagRemoved) {
            return ResultTagRemoved;
        }
        return ResultWrongDeviceState;
    }

    if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
        LOG_ERROR(Service_NFC, "Amiibo is read only", device_state);
        return ResultWrongDeviceState;
    }

    // Not implemented

    return ResultSuccess;
}

NFP::AmiiboName NfcDevice::GetAmiiboName(const NFP::AmiiboSettings& settings) const {
    std::array<char16_t, NFP::amiibo_name_length> settings_amiibo_name{};
    NFP::AmiiboName amiibo_name{};

    // Convert from big endian to little endian
    for (std::size_t i = 0; i < NFP::amiibo_name_length; i++) {
        settings_amiibo_name[i] = static_cast<u16>(settings.amiibo_name[i]);
    }

    // Convert from utf16 to utf8
    const auto amiibo_name_utf8 = Common::UTF16ToUTF8(settings_amiibo_name.data());
    memcpy(amiibo_name.data(), amiibo_name_utf8.data(), amiibo_name_utf8.size());

    return amiibo_name;
}

void NfcDevice::SetAmiiboName(NFP::AmiiboSettings& settings,
                              const NFP::AmiiboName& amiibo_name) const {
    std::array<char16_t, NFP::amiibo_name_length> settings_amiibo_name{};

    // Convert from utf8 to utf16
    const auto amiibo_name_utf16 = Common::UTF8ToUTF16(amiibo_name.data());
    memcpy(settings_amiibo_name.data(), amiibo_name_utf16.data(),
           amiibo_name_utf16.size() * sizeof(char16_t));

    // Convert from little endian to big endian
    for (std::size_t i = 0; i < NFP::amiibo_name_length; i++) {
        settings.amiibo_name[i] = static_cast<u16_be>(settings_amiibo_name[i]);
    }
}

NFP::AmiiboDate NfcDevice::GetAmiiboDate(s64 posix_time) const {
    auto static_service =
        system.ServiceManager().GetService<Service::Glue::Time::StaticService>("time:u", true);

    std::shared_ptr<Service::Glue::Time::TimeZoneService> timezone_service{};
    static_service->GetTimeZoneService(&timezone_service);

    Service::PSC::Time::CalendarTime calendar_time{};
    Service::PSC::Time::CalendarAdditionalInfo additional_info{};

    NFP::AmiiboDate amiibo_date{};

    amiibo_date.SetYear(2000);
    amiibo_date.SetMonth(1);
    amiibo_date.SetDay(1);

    if (timezone_service->ToCalendarTimeWithMyRule(&calendar_time, &additional_info, posix_time) ==
        ResultSuccess) {
        amiibo_date.SetYear(calendar_time.year);
        amiibo_date.SetMonth(calendar_time.month);
        amiibo_date.SetDay(calendar_time.day);
    }

    return amiibo_date;
}

s64 NfcDevice::GetCurrentPosixTime() const {
    auto static_service =
        system.ServiceManager().GetService<Service::Glue::Time::StaticService>("time:u", true);

    std::shared_ptr<Service::PSC::Time::SteadyClock> steady_clock{};
    static_service->GetStandardSteadyClock(&steady_clock);

    Service::PSC::Time::SteadyClockTimePoint time_point{};
    R_ASSERT(steady_clock->GetCurrentTimePoint(&time_point));
    return time_point.time_point;
}

u64 NfcDevice::RemoveVersionByte(u64 application_id) const {
    return application_id & ~(0xfULL << NFP::application_id_version_offset);
}

void NfcDevice::UpdateSettingsCrc() {
    auto& settings = tag_data.settings;

    if (settings.crc_counter != NFP::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 {
        Mii::Ver3StoreData mii;
        u8 application_id_byte;
        u8 unknown;
        Mii::NfpStoreDataExtension 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_data.owner_mii,
        .application_id_byte = tag_data.application_id_byte,
        .unknown = tag_data.unknown,
        .mii_extension = tag_data.mii_extension,
        .unknown2 = tag_data.unknown2,
    };

    boost::crc_32_type crc;
    crc.process_bytes(&crc_data, sizeof(CrcData));
    tag_data.register_info_crc = crc.checksum();
}

void NfcDevice::BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data,
                                       const NFP::EncryptedNTAG215File& encrypted_file) const {
    Service::Mii::StoreData store_data{};
    auto& settings = stubbed_tag_data.settings;

    stubbed_tag_data = NFP::AmiiboCrypto::NfcDataToEncodedData(encrypted_file);

    // Common info
    stubbed_tag_data.write_counter = 0;
    stubbed_tag_data.amiibo_version = 0;
    settings.write_date = GetAmiiboDate(GetCurrentPosixTime());

    // Register info
    SetAmiiboName(settings, {'y', 'u', 'z', 'u', 'A', 'm', 'i', 'i', 'b', 'o'});
    settings.settings.font_region.Assign(0);
    settings.init_date = GetAmiiboDate(GetCurrentPosixTime());
    store_data.BuildBase(Mii::Gender::Male);
    stubbed_tag_data.owner_mii.BuildFromStoreData(store_data);

    // Admin info
    settings.settings.amiibo_initialized.Assign(1);
    settings.settings.appdata_initialized.Assign(0);
}

u64 NfcDevice::GetHandle() const {
    // Generate a handle based of the npad id
    return static_cast<u64>(npad_id);
}

DeviceState NfcDevice::GetCurrentState() const {
    return device_state;
}

Result NfcDevice::GetNpadId(Core::HID::NpadIdType& out_npad_id) const {
    // TODO: This should get the npad id from nn::hid::system::GetXcdHandleForNpadWithNfc
    out_npad_id = npad_id;
    return ResultSuccess;
}

} // namespace Service::NFC


code:
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <span>

#include "common/common_types.h"
#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/nfc/mifare_types.h"
#include "core/hle/service/nfc/nfc_types.h"
#include "core/hle/service/nfp/nfp_types.h"
#include "core/hle/service/service.h"

namespace Kernel {
class KEvent;
class KReadableEvent;
} // namespace Kernel

namespace Core {
class System;
} // namespace Core

namespace Core::HID {
class EmulatedController;
enum class ControllerTriggerType;
enum class NpadIdType : u32;
} // namespace Core::HID

namespace Service::NFC {
class NfcDevice {
public:
    NfcDevice(Core::HID::NpadIdType npad_id_, Core::System& system_,
              KernelHelpers::ServiceContext& service_context_,
              Kernel::KEvent* availability_change_event_);
    ~NfcDevice();

    void Initialize();
    void Finalize();

    Result StartDetection(NfcProtocol allowed_protocol);
    Result StopDetection();

    Result GetTagInfo(TagInfo& tag_info) const;

    Result ReadMifare(std::span<const MifareReadBlockParameter> parameters,
                      std::span<MifareReadBlockData> read_block_data) const;

    Result WriteMifare(std::span<const MifareWriteBlockParameter> parameters);

    Result SendCommandByPassThrough(const s64& timeout, std::span<const u8> command_data,
                                    std::span<u8> out_data);

    Result Mount(NFP::ModelType model_type, NFP::MountTarget mount_target);
    Result Unmount();

    Result Flush();
    Result FlushDebug();
    Result FlushWithBreak(NFP::BreakType break_type);
    Result Restore();

    Result GetCommonInfo(NFP::CommonInfo& common_info) const;
    Result GetModelInfo(NFP::ModelInfo& model_info) const;
    Result GetRegisterInfo(NFP::RegisterInfo& register_info) const;
    Result GetRegisterInfoPrivate(NFP::RegisterInfoPrivate& register_info) const;
    Result GetAdminInfo(NFP::AdminInfo& admin_info) const;

    Result DeleteRegisterInfo();
    Result SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& register_info);
    Result Format();

    Result OpenApplicationArea(u32 access_id);
    Result GetApplicationAreaId(u32& application_area_id) const;
    Result GetApplicationArea(std::span<u8> data) const;
    Result SetApplicationArea(std::span<const u8> data);
    Result CreateApplicationArea(u32 access_id, std::span<const u8> data);
    Result RecreateApplicationArea(u32 access_id, std::span<const u8> data);
    Result DeleteApplicationArea();
    Result ExistsApplicationArea(bool& has_application_area) const;

    Result GetAll(NFP::NfpData& data) const;
    Result SetAll(const NFP::NfpData& data);
    Result BreakTag(NFP::BreakType break_type);
    Result HasBackup(const UniqueSerialNumber& uid, std::size_t uuid_size) const;
    Result HasBackup(const NFP::TagUuid& tag_uid) const;
    Result ReadBackupData(const UniqueSerialNumber& uid, std::size_t uuid_size,
                          std::span<u8> data) const;
    Result ReadBackupData(const NFP::TagUuid& tag_uid, std::span<u8> data) const;
    Result WriteBackupData(const UniqueSerialNumber& uid, std::size_t uuid_size,
                           std::span<const u8> data);
    Result WriteBackupData(const NFP::TagUuid& tag_uid, std::span<const u8> data);
    Result WriteNtf(std::span<const u8> data);

    u64 GetHandle() const;
    DeviceState GetCurrentState() const;
    Result GetNpadId(Core::HID::NpadIdType& out_npad_id) const;

    Kernel::KReadableEvent& GetActivateEvent() const;
    Kernel::KReadableEvent& GetDeactivateEvent() const;

private:
    void NpadUpdate(Core::HID::ControllerTriggerType type);
    bool LoadNfcTag(u8 protocol, u8 tag_type, u8 uuid_length, UniqueSerialNumber uuid);
    bool LoadAmiiboData();
    void CloseNfcTag();

    NFP::AmiiboName GetAmiiboName(const NFP::AmiiboSettings& settings) const;
    void SetAmiiboName(NFP::AmiiboSettings& settings, const NFP::AmiiboName& amiibo_name) const;
    NFP::AmiiboDate GetAmiiboDate(s64 posix_time) const;
    s64 GetCurrentPosixTime() const;
    u64 RemoveVersionByte(u64 application_id) const;
    void UpdateSettingsCrc();
    void UpdateRegisterInfoCrc();

    void BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data,
                                const NFP::EncryptedNTAG215File& encrypted_file) const;

    bool is_controller_set{};
    int callback_key;
    const Core::HID::NpadIdType npad_id;
    Core::System& system;
    Core::HID::EmulatedController* npad_device = nullptr;
    KernelHelpers::ServiceContext& service_context;
    Kernel::KEvent* activate_event = nullptr;
    Kernel::KEvent* deactivate_event = nullptr;
    Kernel::KEvent* availability_change_event = nullptr;

    bool is_initialized{};
    NfcProtocol allowed_protocols{};
    DeviceState device_state{DeviceState::Unavailable};

    // NFP data
    bool is_data_moddified{};
    bool is_app_area_open{};
    bool is_plain_amiibo{};
    bool is_write_protected{};
    NFP::MountTarget mount_target{NFP::MountTarget::None};

    TagInfo real_tag_info{};
    NFP::NTAG215File tag_data{};
    NFP::EncryptedNTAG215File encrypted_tag_data{};
};

} // namespace Service::NFC


code:
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

#include <algorithm>

#include "common/logging/log.h"
#include "core/core.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/service/glue/time/static.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/nfc/common/device.h"
#include "core/hle/service/nfc/common/device_manager.h"
#include "core/hle/service/nfc/nfc_result.h"
#include "core/hle/service/psc/time/steady_clock.h"
#include "core/hle/service/service.h"
#include "core/hle/service/set/system_settings_server.h"
#include "core/hle/service/sm/sm.h"
#include "hid_core/hid_types.h"
#include "hid_core/hid_util.h"

namespace Service::NFC {

DeviceManager::DeviceManager(Core::System& system_, KernelHelpers::ServiceContext& service_context_)
    : system{system_}, service_context{service_context_} {

    availability_change_event =
        service_context.CreateEvent("Nfc:DeviceManager:AvailabilityChangeEvent");

    for (u32 device_index = 0; device_index < devices.size(); device_index++) {
        devices[device_index] =
            std::make_shared<NfcDevice>(HID::IndexToNpadIdType(device_index), system,
                                        service_context, availability_change_event);
    }

    is_initialized = false;

    m_set_sys =
        system.ServiceManager().GetService<Service::Set::ISystemSettingsServer>("set:sys", true);
}

DeviceManager ::~DeviceManager() {
    if (is_initialized) {
        Finalize();
    }
    service_context.CloseEvent(availability_change_event);
}

Result DeviceManager::Initialize() {
    for (auto& device : devices) {
        device->Initialize();
    }
    is_initialized = true;
    return ResultSuccess;
}

Result DeviceManager::Finalize() {
    for (auto& device : devices) {
        device->Finalize();
    }
    is_initialized = false;
    return ResultSuccess;
}

Result DeviceManager::ListDevices(std::vector<u64>& nfp_devices, std::size_t max_allowed_devices,
                                  bool skip_fatal_errors) const {
    std::scoped_lock lock{mutex};
    if (max_allowed_devices < 1) {
        return ResultInvalidArgument;
    }

    Result result = IsNfcParameterSet();
    if (result.IsError()) {
        return result;
    }

    result = IsNfcEnabled();
    if (result.IsError()) {
        return result;
    }

    result = IsNfcInitialized();
    if (result.IsError()) {
        return result;
    }

    for (auto& device : devices) {
        if (nfp_devices.size() >= max_allowed_devices) {
            continue;
        }
        if (skip_fatal_errors) {
            constexpr s64 MinimumRecoveryTime = 60;

            auto static_service =
                system.ServiceManager().GetService<Service::Glue::Time::StaticService>("time:u",
                                                                                true);

            std::shared_ptr<Service::PSC::Time::SteadyClock> steady_clock{};
            static_service->GetStandardSteadyClock(&steady_clock);

            Service::PSC::Time::SteadyClockTimePoint time_point{};
            R_ASSERT(steady_clock->GetCurrentTimePoint(&time_point));

            const s64 elapsed_time = time_point.time_point - time_since_last_error;
            if (time_since_last_error != 0 && elapsed_time < MinimumRecoveryTime) {
                continue;
            }
        }
        if (device->GetCurrentState() == DeviceState::Unavailable) {
            continue;
        }
        nfp_devices.push_back(device->GetHandle());
    }

    if (nfp_devices.empty()) {
        return ResultDeviceNotFound;
    }

    return result;
}

DeviceState DeviceManager::GetDeviceState(u64 device_handle) const {
    std::scoped_lock lock{mutex};

    std::shared_ptr<NfcDevice> device = nullptr;
    const auto result = GetDeviceFromHandle(device_handle, device, false);

    if (result.IsSuccess()) {
        return device->GetCurrentState();
    }

    return DeviceState::Finalized;
}

Result DeviceManager::GetNpadId(u64 device_handle, Core::HID::NpadIdType& npad_id) {
    std::scoped_lock lock{mutex};

    std::shared_ptr<NfcDevice> device = nullptr;
    auto result = GetDeviceHandle(device_handle, device);

    if (result.IsSuccess()) {
        result = device->GetNpadId(npad_id);
        result = VerifyDeviceResult(device, result);
    }

    return result;
}

Kernel::KReadableEvent& DeviceManager::AttachAvailabilityChangeEvent() const {
    return availability_change_event->GetReadableEvent();
}

Result DeviceManager::StartDetection(u64 device_handle, NfcProtocol tag_protocol) {
    std::scoped_lock lock{mutex};

    std::shared_ptr<NfcDevice> device = nullptr;
    auto result = GetDeviceHandle(device_handle, device);

    if (result.IsSuccess()) {
        result = device->StartDetection(tag_protocol);
        result = VerifyDeviceResult(device, result);
    }

    return result;
}

Result DeviceManager::StopDetection(u64 device_handle) {
    std::scoped_lock lock{mutex};

    std::shared_ptr<NfcDevice> device = nullptr;
    auto result = GetDeviceHandle(device_handle, device);

    if (result.IsSuccess()) {
        result = device->StopDetection();
        result = VerifyDeviceResult(device, result);
    }

    return result;
}

Result DeviceManager::GetTagInfo(u64 device_handle, TagInfo& tag_info) {
    std::scoped_lock lock{mutex};

    std::shared_ptr<NfcDevice> device = nullptr;
    auto result = GetDeviceHandle(device_handle, device);

    if (result.IsSuccess()) {
        result = device->GetTagInfo(tag_info);
        result = VerifyDeviceResult(device, result);
    }

    return result;
}

Result DeviceManager::AttachActivateEvent(Kernel::KReadableEvent** out_event,
                                          u64 device_handle) const {
    std::vector<u64> nfp_devices;
    std::shared_ptr<NfcDevice> device = nullptr;
    Result result = ListDevices(nfp_devices, 9, false);

    if (result.IsSuccess()) {
        result = CheckHandleOnList(device_handle, nfp_devices);
    }

    if (result.IsSuccess()) {
        result = GetDeviceFromHandle(device_handle, device, false);
    }

    if (result.IsSuccess()) {
        *out_event = &device->GetActivateEvent();
    }

    return result;
}

Result DeviceManager::AttachDeactivateEvent(Kernel::KReadableEvent** out_event,
                                            u64 device_handle) const {
    std::vector<u64> nfp_devices;
    std::shared_ptr<NfcDevice> device = nullptr;
    Result result = ListDevices(nfp_devices, 9, false);

    if (result.IsSuccess()) {
        result = CheckHandleOnList(device_handle, nfp_devices);
    }

    if (result.IsSuccess()) {
        result = GetDeviceFromHandle(device_handle, device, false);
    }

    if (result.IsSuccess()) {
        *out_event = &device->GetDeactivateEvent();
    }

    return result;
}

Result DeviceManager::ReadMifare(u64 device_handle,
                                 std::span<const MifareReadBlockParameter> read_parameters,
                                 std::span<MifareReadBlockData> read_data) {
    std::scoped_lock lock{mutex};

    std::shared_ptr<NfcDevice> device = nullptr;
    auto result = GetDeviceHandle(device_handle, device);

    if (result.IsSuccess()) {
        result = device->ReadMifare(read_parameters, read_data);
        result = VerifyDeviceResult(device, result);
    }

    return result;
}

Result DeviceManager::WriteMifare(u64 device_handle,
                                  std::span<const MifareWriteBlockParameter> write_parameters) {
    std::scoped_lock lock{mutex};

    std::shared_ptr<NfcDevice> device = nullptr;
    auto result = GetDeviceHandle(device_handle, device);

    if (result.IsSuccess()) {
        result = device->WriteMifare(write_parameters);
        result = VerifyDeviceResult(device, result);
    }

    return result;
}

Result DeviceManager::SendCommandByPassThrough(u64 device_handle, const s64& timeout,
                                               std::span<const u8> command_data,
                                               std::span<u8> out_data) {
    std::scoped_lock lock{mutex};

    std::shared_ptr<NfcDevice> device = nullptr;
    auto result = GetDeviceHandle(device_handle, device);

    if (result.IsSuccess()) {
        result = device->SendCommandByPassThrough(timeout, command_data, out_data);
        result = VerifyDeviceResult(device, result);
    }

    return result;
}

Result DeviceManager::Mount(u64 device_handle, NFP::ModelType model_type,
                            NFP::MountTarget mount_target) {
    std::scoped_lock lock{mutex};

    std::shared_ptr<NfcDevice> device = nullptr;
    auto result = GetDeviceHandle(device_handle, device);

    if (result.IsSuccess()) {
        result = device->Mount(model_type, mount_target);
        result = VerifyDeviceResult(device, result);
    }

    return result;
}

Result DeviceManager::Unmount(u64 device_handle) {
    std::scoped_lock lock{mutex};

    std::shared_ptr<NfcDevice> device = nullptr;
    auto result = GetDeviceHandle(device_handle, device);

    if (result.IsSuccess()) {
        result = device->Unmount();
        result = VerifyDeviceResult(device, result);
    }

    return result;
}

Result DeviceManager::OpenApplicationArea(u64 device_handle, u32 access_id) {
    std::scoped_lock lock{mutex};

    std::shared_ptr<NfcDevice> device = nullptr;
    auto result = GetDeviceHandle(device_handle, device);

    if (result.IsSuccess()) {
        result = device->OpenApplicationArea(access_id);
        result = VerifyDeviceResult(device, result);
    }

    return result;
}

Result DeviceManager::GetApplicationArea(u64 device_handle, std::span<u8> data) {
    std::scoped_lock lock{mutex};

    std::shared_ptr<NfcDevice> device = nullptr;
    auto result = GetDeviceHandle(device_handle, device);

    if (result.IsSuccess()) {
        result = device->GetApplicationArea(data);
        result = VerifyDeviceResult(device, result);
    }

    return result;
}

Result DeviceManager::SetApplicationArea(u64 device_handle, std::span<const u8> data) {
    std::scoped_lock lock{mutex};

    std::shared_ptr<NfcDevice> device = nullptr;
    auto result = GetDeviceHandle(device_handle, device);

    if (result.IsSuccess()) {
        result = device->SetApplicationArea(data);
        result = VerifyDeviceResult(device, result);
    }

    return result;
}

Result DeviceManager::Flush(u64 device_handle) {
    std::scoped_lock lock{mutex};

    std::shared_ptr<NfcDevice> device = nullptr;
    auto result = GetDeviceHandle(device_handle, device);

    if (result.IsSuccess()) {
        result = device->Flush();
        result = VerifyDeviceResult(device, result);
    }

    return result;
}

Result DeviceManager::Restore(u64 device_handle) {
    std::scoped_lock lock{mutex};

    std::shared_ptr<NfcDevice> device = nullptr;
    auto result = GetDeviceHandle(device_handle, device);

    if (result.IsSuccess()) {
        result = device->Restore();
        result = VerifyDeviceResult(device, result);
    }

    return result;
}

Result DeviceManager::CreateApplicationArea(u64 device_handle, u32 access_id,
                                            std::span<const u8> data) {
    std::scoped_lock lock{mutex};

    std::shared_ptr<NfcDevice> device = nullptr;
    auto result = GetDeviceHandle(device_handle, device);

    if (result.IsSuccess()) {
        result = device->CreateApplicationArea(access_id, data);
        result = VerifyDeviceResult(device, result);
    }

    return result;
}

Result DeviceManager::GetRegisterInfo(u64 device_handle, NFP::RegisterInfo& register_info) {
    std::scoped_lock lock{mutex};

    std::shared_ptr<NfcDevice> device = nullptr;
    auto result = GetDeviceHandle(device_handle, device);

    if (result.IsSuccess()) {
        result = device->GetRegisterInfo(register_info);
        result = VerifyDeviceResult(device, result);
    }

    return result;
}

Result DeviceManager::GetCommonInfo(u64 device_handle, NFP::CommonInfo& common_info) {
    std::scoped_lock lock{mutex};

    std::shared_ptr<NfcDevice> device = nullptr;
    auto result = GetDeviceHandle(device_handle, device);

    if (result.IsSuccess()) {
        result = device->GetCommonInfo(common_info);
        result = VerifyDeviceResult(device, result);
    }

    return result;
}

Result DeviceManager::GetModelInfo(u64 device_handle, NFP::ModelInfo& model_info) {
    std::scoped_lock lock{mutex};

    std::shared_ptr<NfcDevice> device = nullptr;
    auto result = GetDeviceHandle(device_handle, device);

    if (result.IsSuccess()) {
        result = device->GetModelInfo(model_info);
        result = VerifyDeviceResult(device, result);
    }

    return result;
}

u32 DeviceManager::GetApplicationAreaSize() const {
    return sizeof(NFP::ApplicationArea);
}

Result DeviceManager::RecreateApplicationArea(u64 device_handle, u32 access_id,
                                              std::span<const u8> data) {
    std::scoped_lock lock{mutex};

    std::shared_ptr<NfcDevice> device = nullptr;
    auto result = GetDeviceHandle(device_handle, device);

    if (result.IsSuccess()) {
        result = device->RecreateApplicationArea(access_id, data);
        result = VerifyDeviceResult(device, result);
    }

    return result;
}

Result DeviceManager::Format(u64 device_handle) {
    std::scoped_lock lock{mutex};

    std::shared_ptr<NfcDevice> device = nullptr;
    auto result = GetDeviceHandle(device_handle, device);

    if (result.IsSuccess()) {
        result = device->Format();
        result = VerifyDeviceResult(device, result);
    }

    return result;
}

Result DeviceManager::GetAdminInfo(u64 device_handle, NFP::AdminInfo& admin_info) {
    std::scoped_lock lock{mutex};

    std::shared_ptr<NfcDevice> device = nullptr;
    auto result = GetDeviceHandle(device_handle, device);

    if (result.IsSuccess()) {
        result = device->GetAdminInfo(admin_info);
        result = VerifyDeviceResult(device, result);
    }

    return result;
}

Result DeviceManager::GetRegisterInfoPrivate(u64 device_handle,
                                             NFP::RegisterInfoPrivate& register_info) {
    std::scoped_lock lock{mutex};

    std::shared_ptr<NfcDevice> device = nullptr;
    auto result = GetDeviceHandle(device_handle, device);

    if (result.IsSuccess()) {
        result = device->GetRegisterInfoPrivate(register_info);
        result = VerifyDeviceResult(device, result);
    }

    return result;
}

Result DeviceManager::SetRegisterInfoPrivate(u64 device_handle,
                                             const NFP::RegisterInfoPrivate& register_info) {
    std::scoped_lock lock{mutex};

    std::shared_ptr<NfcDevice> device = nullptr;
    auto result = GetDeviceHandle(device_handle, device);

    if (result.IsSuccess()) {
        result = device->SetRegisterInfoPrivate(register_info);
        result = VerifyDeviceResult(device, result);
    }

    return result;
}

Result DeviceManager::DeleteRegisterInfo(u64 device_handle) {
    std::scoped_lock lock{mutex};

    std::shared_ptr<NfcDevice> device = nullptr;
    auto result = GetDeviceHandle(device_handle, device);

    if (result.IsSuccess()) {
        result = device->DeleteRegisterInfo();
        result = VerifyDeviceResult(device, result);
    }

    return result;
}

Result DeviceManager::DeleteApplicationArea(u64 device_handle) {
    std::scoped_lock lock{mutex};

    std::shared_ptr<NfcDevice> device = nullptr;
    auto result = GetDeviceHandle(device_handle, device);

    if (result.IsSuccess()) {
        result = device->DeleteApplicationArea();
        result = VerifyDeviceResult(device, result);
    }

    return result;
}

Result DeviceManager::ExistsApplicationArea(u64 device_handle, bool& has_application_area) {
    std::scoped_lock lock{mutex};

    std::shared_ptr<NfcDevice> device = nullptr;
    auto result = GetDeviceHandle(device_handle, device);

    if (result.IsSuccess()) {
        result = device->ExistsApplicationArea(has_application_area);
        result = VerifyDeviceResult(device, result);
    }

    return result;
}

Result DeviceManager::GetAll(u64 device_handle, NFP::NfpData& nfp_data) {
    std::scoped_lock lock{mutex};

    std::shared_ptr<NfcDevice> device = nullptr;
    auto result = GetDeviceHandle(device_handle, device);

    if (result.IsSuccess()) {
        result = device->GetAll(nfp_data);
        result = VerifyDeviceResult(device, result);
    }

    return result;
}

Result DeviceManager::SetAll(u64 device_handle, const NFP::NfpData& nfp_data) {
    std::scoped_lock lock{mutex};

    std::shared_ptr<NfcDevice> device = nullptr;
    auto result = GetDeviceHandle(device_handle, device);

    if (result.IsSuccess()) {
        result = device->SetAll(nfp_data);
        result = VerifyDeviceResult(device, result);
    }

    return result;
}

Result DeviceManager::FlushDebug(u64 device_handle) {
    std::scoped_lock lock{mutex};

    std::shared_ptr<NfcDevice> device = nullptr;
    auto result = GetDeviceHandle(device_handle, device);

    if (result.IsSuccess()) {
        result = device->FlushDebug();
        result = VerifyDeviceResult(device, result);
    }

    return result;
}

Result DeviceManager::BreakTag(u64 device_handle, NFP::BreakType break_type) {
    std::scoped_lock lock{mutex};

    std::shared_ptr<NfcDevice> device = nullptr;
    auto result = GetDeviceHandle(device_handle, device);

    if (result.IsSuccess()) {
        result = device->BreakTag(break_type);
        result = VerifyDeviceResult(device, result);
    }

    return result;
}

Result DeviceManager::ReadBackupData(u64 device_handle, std::span<u8> data) {
    std::scoped_lock lock{mutex};

    std::shared_ptr<NfcDevice> device = nullptr;
    auto result = GetDeviceHandle(device_handle, device);
    NFC::TagInfo tag_info{};

    if (result.IsSuccess()) {
        result = device->GetTagInfo(tag_info);
    }

    if (result.IsSuccess()) {
        result = device->ReadBackupData(tag_info.uuid, tag_info.uuid_length, data);
        result = VerifyDeviceResult(device, result);
    }

    return result;
}

Result DeviceManager::WriteBackupData(u64 device_handle, std::span<const u8> data) {
    std::scoped_lock lock{mutex};

    std::shared_ptr<NfcDevice> device = nullptr;
    auto result = GetDeviceHandle(device_handle, device);
    NFC::TagInfo tag_info{};

    if (result.IsSuccess()) {
        result = device->GetTagInfo(tag_info);
    }

    if (result.IsSuccess()) {
        result = device->WriteBackupData(tag_info.uuid, tag_info.uuid_length, data);
        result = VerifyDeviceResult(device, result);
    }

    return result;
}

Result DeviceManager::WriteNtf(u64 device_handle, NFP::WriteType, std::span<const u8> data) {
    std::scoped_lock lock{mutex};

    std::shared_ptr<NfcDevice> device = nullptr;
    auto result = GetDeviceHandle(device_handle, device);

    if (result.IsSuccess()) {
        result = device->WriteNtf(data);
        result = VerifyDeviceResult(device, result);
    }

    return result;
}

Result DeviceManager::CheckHandleOnList(u64 device_handle,
                                        const std::span<const u64> device_list) const {
    if (device_list.size() < 1) {
        return ResultDeviceNotFound;
    }

    if (std::find(device_list.begin(), device_list.end(), device_handle) != device_list.end()) {
        return ResultSuccess;
    }

    return ResultDeviceNotFound;
}

Result DeviceManager::GetDeviceFromHandle(u64 handle, std::shared_ptr<NfcDevice>& nfc_device,
                                          bool check_state) const {
    if (check_state) {
        const Result is_parameter_set = IsNfcParameterSet();
        if (is_parameter_set.IsError()) {
            return is_parameter_set;
        }
        const Result is_enabled = IsNfcEnabled();
        if (is_enabled.IsError()) {
            return is_enabled;
        }
        const Result is_nfc_initialized = IsNfcInitialized();
        if (is_nfc_initialized.IsError()) {
            return is_nfc_initialized;
        }
    }

    for (auto& device : devices) {
        if (device->GetHandle() == handle) {
            nfc_device = device;
            return ResultSuccess;
        }
    }

    return ResultDeviceNotFound;
}

std::optional<std::shared_ptr<NfcDevice>> DeviceManager::GetNfcDevice(u64 handle) {
    for (auto& device : devices) {
        if (device->GetHandle() == handle) {
            return device;
        }
    }
    return std::nullopt;
}

const std::optional<std::shared_ptr<NfcDevice>> DeviceManager::GetNfcDevice(u64 handle) const {
    for (auto& device : devices) {
        if (device->GetHandle() == handle) {
            return device;
        }
    }
    return std::nullopt;
}

Result DeviceManager::GetDeviceHandle(u64 handle, std::shared_ptr<NfcDevice>& device) const {
    const auto result = GetDeviceFromHandle(handle, device, true);
    if (result.IsError()) {
        return result;
    }
    return CheckDeviceState(device);
}

Result DeviceManager::VerifyDeviceResult(std::shared_ptr<NfcDevice> device,
                                         Result operation_result) {
    if (operation_result.IsSuccess()) {
        return operation_result;
    }

    const Result is_parameter_set = IsNfcParameterSet();
    if (is_parameter_set.IsError()) {
        return is_parameter_set;
    }
    const Result is_enabled = IsNfcEnabled();
    if (is_enabled.IsError()) {
        return is_enabled;
    }
    const Result is_nfc_initialized = IsNfcInitialized();
    if (is_nfc_initialized.IsError()) {
        return is_nfc_initialized;
    }
    const Result device_state = CheckDeviceState(device);
    if (device_state.IsError()) {
        return device_state;
    }

    if (operation_result == ResultUnknown112 || operation_result == ResultUnknown114 ||
        operation_result == ResultUnknown115) {
        auto static_service =
            system.ServiceManager().GetService<Service::Glue::Time::StaticService>("time:u", true);

        std::shared_ptr<Service::PSC::Time::SteadyClock> steady_clock{};
        static_service->GetStandardSteadyClock(&steady_clock);

        Service::PSC::Time::SteadyClockTimePoint time_point{};
        R_ASSERT(steady_clock->GetCurrentTimePoint(&time_point));

        time_since_last_error = time_point.time_point;
    }

    return operation_result;
}

Result DeviceManager::CheckDeviceState(std::shared_ptr<NfcDevice> device) const {
    if (device == nullptr) {
        return ResultInvalidArgument;
    }

    return ResultSuccess;
}

Result DeviceManager::IsNfcEnabled() const {
    bool is_enabled{};
    R_TRY(m_set_sys->GetNfcEnableFlag(&is_enabled));
    if (!is_enabled) {
        return ResultNfcDisabled;
    }
    return ResultSuccess;
}

Result DeviceManager::IsNfcParameterSet() const {
    // TODO: This calls checks against a bool on offset 0x450
    const bool is_set = true;
    if (!is_set) {
        return ResultUnknown76;
    }
    return ResultSuccess;
}

Result DeviceManager::IsNfcInitialized() const {
    if (!is_initialized) {
        return ResultNfcNotInitialized;
    }
    return ResultSuccess;
}

} // namespace Service::NFC