summaryrefslogtreecommitdiffstats
path: root/src/core/hle/service/nfc/common
diff options
context:
space:
mode:
authorNarr the Reg <juangerman-13@hotmail.com>2023-04-20 03:01:23 +0200
committergerman77 <juangerman-13@hotmail.com>2023-05-06 06:02:59 +0200
commit94151097b9abadf35c55ea06a31925c9848f4c62 (patch)
tree28a00c878f90492ffd2bb95521c2cc3990cc4323 /src/core/hle/service/nfc/common
parentcore: service: Add FunctionInfoTyped to allow expanding existing interfaces (diff)
downloadyuzu-94151097b9abadf35c55ea06a31925c9848f4c62.tar
yuzu-94151097b9abadf35c55ea06a31925c9848f4c62.tar.gz
yuzu-94151097b9abadf35c55ea06a31925c9848f4c62.tar.bz2
yuzu-94151097b9abadf35c55ea06a31925c9848f4c62.tar.lz
yuzu-94151097b9abadf35c55ea06a31925c9848f4c62.tar.xz
yuzu-94151097b9abadf35c55ea06a31925c9848f4c62.tar.zst
yuzu-94151097b9abadf35c55ea06a31925c9848f4c62.zip
Diffstat (limited to 'src/core/hle/service/nfc/common')
-rw-r--r--src/core/hle/service/nfc/common/amiibo_crypto.cpp405
-rw-r--r--src/core/hle/service/nfc/common/amiibo_crypto.h106
-rw-r--r--src/core/hle/service/nfc/common/device.cpp1249
-rw-r--r--src/core/hle/service/nfc/common/device.h138
-rw-r--r--src/core/hle/service/nfc/common/device_manager.cpp695
-rw-r--r--src/core/hle/service/nfc/common/device_manager.h100
6 files changed, 2693 insertions, 0 deletions
diff --git a/src/core/hle/service/nfc/common/amiibo_crypto.cpp b/src/core/hle/service/nfc/common/amiibo_crypto.cpp
new file mode 100644
index 000000000..f3901ee8d
--- /dev/null
+++ b/src/core/hle/service/nfc/common/amiibo_crypto.cpp
@@ -0,0 +1,405 @@
+// 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, "compability_container=0x{0:x}", ntag_file.compability_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.uid[0] ^ ntag_file.uuid.uid[1] ^ ntag_file.uuid.uid[2]) !=
+ ntag_file.uuid.uid[3]) {
+ return false;
+ }
+ if ((ntag_file.uuid.uid[4] ^ ntag_file.uuid.uid[5] ^ ntag_file.uuid.uid[6] ^
+ ntag_file.uuid.nintendo_id) != ntag_file.uuid.lock_bytes[0]) {
+ return false;
+ }
+
+ // Check against all know constants on an amiibo binary
+ if (ntag_file.static_lock != 0xE00F) {
+ return false;
+ }
+ if (ntag_file.compability_container != 0xEEFF10F1U) {
+ return false;
+ }
+ if (amiibo_data.constant_value != 0xA5) {
+ 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.uid;
+ encoded_data.nintendo_id = nfc_data.uuid.nintendo_id;
+ encoded_data.static_lock = nfc_data.static_lock;
+ encoded_data.compability_container = nfc_data.compability_container;
+ encoded_data.hmac_data = nfc_data.user_memory.hmac_data;
+ encoded_data.constant_value = nfc_data.user_memory.constant_value;
+ encoded_data.write_counter = nfc_data.user_memory.write_counter;
+ encoded_data.amiibo_version = nfc_data.user_memory.amiibo_version;
+ encoded_data.settings = nfc_data.user_memory.settings;
+ encoded_data.owner_mii = nfc_data.user_memory.owner_mii;
+ encoded_data.application_id = nfc_data.user_memory.application_id;
+ encoded_data.application_write_counter = nfc_data.user_memory.application_write_counter;
+ encoded_data.application_area_id = nfc_data.user_memory.application_area_id;
+ encoded_data.application_id_byte = nfc_data.user_memory.application_id_byte;
+ encoded_data.unknown = nfc_data.user_memory.unknown;
+ encoded_data.mii_extension = nfc_data.user_memory.mii_extension;
+ encoded_data.unknown2 = nfc_data.user_memory.unknown2;
+ encoded_data.register_info_crc = nfc_data.user_memory.register_info_crc;
+ encoded_data.application_area = nfc_data.user_memory.application_area;
+ encoded_data.hmac_tag = nfc_data.user_memory.hmac_tag;
+ encoded_data.lock_bytes = nfc_data.uuid.lock_bytes;
+ encoded_data.model_info = nfc_data.user_memory.model_info;
+ encoded_data.keygen_salt = nfc_data.user_memory.keygen_salt;
+ encoded_data.dynamic_lock = nfc_data.dynamic_lock;
+ encoded_data.CFG0 = nfc_data.CFG0;
+ encoded_data.CFG1 = nfc_data.CFG1;
+ encoded_data.password = nfc_data.password;
+
+ return encoded_data;
+}
+
+EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) {
+ EncryptedNTAG215File nfc_data{};
+
+ nfc_data.uuid.uid = encoded_data.uid;
+ nfc_data.uuid.nintendo_id = encoded_data.nintendo_id;
+ nfc_data.uuid.lock_bytes = encoded_data.lock_bytes;
+ nfc_data.static_lock = encoded_data.static_lock;
+ nfc_data.compability_container = encoded_data.compability_container;
+ nfc_data.user_memory.hmac_data = encoded_data.hmac_data;
+ nfc_data.user_memory.constant_value = encoded_data.constant_value;
+ nfc_data.user_memory.write_counter = encoded_data.write_counter;
+ nfc_data.user_memory.amiibo_version = encoded_data.amiibo_version;
+ nfc_data.user_memory.settings = encoded_data.settings;
+ nfc_data.user_memory.owner_mii = encoded_data.owner_mii;
+ nfc_data.user_memory.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;
+}
+
+u32 GetTagPassword(const TagUuid& uuid) {
+ // Verify that the generated password is correct
+ u32 password = 0xAA ^ (uuid.uid[1] ^ uuid.uid[3]);
+ password &= (0x55 ^ (uuid.uid[2] ^ uuid.uid[4])) << 8;
+ password &= (0xAA ^ (uuid.uid[3] ^ uuid.uid[5])) << 16;
+ password &= (0x55 ^ (uuid.uid[4] ^ uuid.uid[6])) << 24;
+ return password;
+}
+
+HashSeed GetSeed(const NTAG215File& data) {
+ HashSeed seed{
+ .magic = data.write_counter,
+ .padding = {},
+ .uid_1 = data.uid,
+ .nintendo_id_1 = data.nintendo_id,
+ .uid_2 = data.uid,
+ .nintendo_id_2 = data.nintendo_id,
+ .keygen_salt = data.keygen_salt,
+ };
+
+ return seed;
+}
+
+std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed) {
+ const std::size_t 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);
+
+ output.insert(output.end(), seed.uid_1.begin(), seed.uid_1.end());
+ output.emplace_back(seed.nintendo_id_1);
+ output.insert(output.end(), seed.uid_2.begin(), seed.uid_2.end());
+ output.emplace_back(seed.nintendo_id_2);
+
+ for (std::size_t i = 0; i < sizeof(seed.keygen_salt); i++) {
+ output.emplace_back(static_cast<u8>(seed.keygen_salt[i] ^ key.xor_pad[i]));
+ }
+
+ return output;
+}
+
+void CryptoInit(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, const HmacKey& hmac_key,
+ const std::vector<u8>& seed) {
+ // Initialize context
+ ctx.used = false;
+ ctx.counter = 0;
+ ctx.buffer_size = sizeof(ctx.counter) + seed.size();
+ memcpy(ctx.buffer.data() + sizeof(u16), seed.data(), seed.size());
+
+ // Initialize HMAC context
+ 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.nintendo_id = in_data.nintendo_id;
+ out_data.lock_bytes = in_data.lock_bytes;
+ out_data.static_lock = in_data.static_lock;
+ out_data.compability_container = in_data.compability_container;
+
+ out_data.constant_value = in_data.constant_value;
+ out_data.write_counter = in_data.write_counter;
+
+ out_data.model_info = in_data.model_info;
+ out_data.keygen_salt = in_data.keygen_salt;
+ out_data.dynamic_lock = in_data.dynamic_lock;
+ out_data.CFG0 = in_data.CFG0;
+ out_data.CFG1 = in_data.CFG1;
+ out_data.password = in_data.password;
+}
+
+bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info) {
+ const auto 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
diff --git a/src/core/hle/service/nfc/common/amiibo_crypto.h b/src/core/hle/service/nfc/common/amiibo_crypto.h
new file mode 100644
index 000000000..bf3044ed9
--- /dev/null
+++ b/src/core/hle/service/nfc/common/amiibo_crypto.h
@@ -0,0 +1,106 @@
+// 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;
+ NFC::UniqueSerialNumber uid_1;
+ u8 nintendo_id_1;
+ NFC::UniqueSerialNumber uid_2;
+ u8 nintendo_id_2;
+ std::array<u8, 0x20> keygen_salt;
+};
+static_assert(sizeof(HashSeed) == 0x40, "HashSeed is an invalid size");
+
+struct InternalKey {
+ HmacKey hmac_key;
+ std::array<char, 0xE> type_string;
+ u8 reserved;
+ u8 magic_length;
+ std::array<u8, 0x10> magic_bytes;
+ std::array<u8, 0x20> xor_pad;
+};
+static_assert(sizeof(InternalKey) == 0x50, "InternalKey is an invalid size");
+static_assert(std::is_trivially_copyable_v<InternalKey>, "InternalKey must be trivially copyable.");
+
+struct CryptoCtx {
+ std::array<char, 480> buffer;
+ bool used;
+ std::size_t buffer_size;
+ s16 counter;
+};
+
+struct DerivedKeys {
+ std::array<u8, 0x10> aes_key;
+ std::array<u8, 0x10> aes_iv;
+ std::array<u8, 0x10> hmac_key;
+};
+static_assert(sizeof(DerivedKeys) == 0x30, "DerivedKeys is an invalid size");
+
+/// Validates that the amiibo file is not corrupted
+bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file);
+
+/// Validates that the amiibo file is not corrupted
+bool IsAmiiboValid(const NTAG215File& ntag_file);
+
+/// Converts from encrypted file format to encoded file format
+NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data);
+
+/// Converts from encoded file format to encrypted file format
+EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data);
+
+/// Returns password needed to allow write access to protected memory
+u32 GetTagPassword(const TagUuid& uuid);
+
+// 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,
+ const std::vector<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
diff --git a/src/core/hle/service/nfc/common/device.cpp b/src/core/hle/service/nfc/common/device.cpp
new file mode 100644
index 000000000..e5de65ce0
--- /dev/null
+++ b/src/core/hle/service/nfc/common/device.cpp
@@ -0,0 +1,1249 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#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 "common/input.h"
+#include "common/logging/log.h"
+#include "common/string_util.h"
+#include "common/tiny_mt.h"
+#include "core/core.h"
+#include "core/hid/emulated_controller.h"
+#include "core/hid/hid_core.h"
+#include "core/hid/hid_types.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/mii/types.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/time/time_manager.h"
+#include "core/hle/service/time/time_zone_content_manager.h"
+#include "core/hle/service/time/time_zone_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);
+
+ auto& standard_steady_clock{system.GetTimeManager().GetStandardSteadyClockCore()};
+ current_posix_time = standard_steady_clock.GetCurrentTimePoint(system).time_point;
+}
+
+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 (!is_initalized) {
+ return;
+ }
+
+ if (type == Core::HID::ControllerTriggerType::Connected) {
+ Initialize();
+ availability_change_event->Signal();
+ return;
+ }
+
+ if (type == Core::HID::ControllerTriggerType::Disconnected) {
+ device_state = DeviceState::Unavailable;
+ availability_change_event->Signal();
+ return;
+ }
+
+ if (type != Core::HID::ControllerTriggerType::Nfc) {
+ return;
+ }
+
+ if (!npad_device->IsConnected()) {
+ return;
+ }
+
+ const auto nfc_status = npad_device->GetNfc();
+ switch (nfc_status.state) {
+ case Common::Input::NfcState::NewAmiibo:
+ LoadNfcTag(nfc_status.data);
+ 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(std::span<const u8> data) {
+ if (device_state != DeviceState::SearchingForTag) {
+ LOG_ERROR(Service_NFC, "Game is not looking for nfc tag, current state {}", device_state);
+ return false;
+ }
+
+ if (data.size() < sizeof(NFP::EncryptedNTAG215File)) {
+ LOG_ERROR(Service_NFC, "Not an amiibo, size={}", data.size());
+ return false;
+ }
+
+ mifare_data.resize(data.size());
+ memcpy(mifare_data.data(), data.data(), data.size());
+
+ memcpy(&tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File));
+ is_plain_amiibo = NFP::AmiiboCrypto::IsAmiiboValid(tag_data);
+
+ if (is_plain_amiibo) {
+ encrypted_tag_data = NFP::AmiiboCrypto::EncodedDataToNfcData(tag_data);
+ LOG_INFO(Service_NFP, "Using plain amiibo");
+ } else {
+ tag_data = {};
+ memcpy(&encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File));
+ }
+
+ device_state = DeviceState::TagFound;
+ deactivate_event->GetReadableEvent().Clear();
+ activate_event->Signal();
+ 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 = {};
+ mifare_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 = {};
+ mifare_data = {};
+ is_initalized = true;
+}
+
+void NfcDevice::Finalize() {
+ if (device_state == DeviceState::TagMounted) {
+ Unmount();
+ }
+ if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) {
+ StopDetection();
+ }
+ device_state = DeviceState::Unavailable;
+ is_initalized = 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->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
+ Common::Input::PollingMode::NFC) !=
+ Common::Input::DriverResult::Success) {
+ LOG_ERROR(Service_NFC, "Nfc not supported");
+ return ResultNfcDisabled;
+ }
+
+ device_state = DeviceState::SearchingForTag;
+ allowed_protocols = allowed_protocol;
+ return ResultSuccess;
+}
+
+Result NfcDevice::StopDetection() {
+ npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
+ Common::Input::PollingMode::Active);
+
+ 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) {
+ device_state = DeviceState::Initialized;
+ return ResultSuccess;
+ }
+
+ LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
+ return ResultWrongDeviceState;
+}
+
+Result NfcDevice::GetTagInfo(NFP::TagInfo& tag_info, bool is_mifare) 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;
+ }
+
+ if (is_mifare) {
+ tag_info = {
+ .uuid = encrypted_tag_data.uuid.uid,
+ .uuid_extension = {},
+ .uuid_length = static_cast<u8>(encrypted_tag_data.uuid.uid.size()),
+ .protocol = NfcProtocol::TypeA,
+ .tag_type = TagType::Type4,
+ };
+ return ResultSuccess;
+ }
+
+ // Protocol and tag type may change here
+ tag_info = {
+ .uuid = encrypted_tag_data.uuid.uid,
+ .uuid_extension = {},
+ .uuid_length = static_cast<u8>(encrypted_tag_data.uuid.uid.size()),
+ .protocol = NfcProtocol::TypeA,
+ .tag_type = TagType::Type2,
+ };
+
+ return ResultSuccess;
+}
+
+Result NfcDevice::ReadMifare(std::span<const MifareReadBlockParameter> parameters,
+ std::span<MifareReadBlockData> read_block_data) const {
+ Result result = ResultSuccess;
+
+ for (std::size_t i = 0; i < parameters.size(); i++) {
+ result = ReadMifare(parameters[i], read_block_data[i]);
+ if (result.IsError()) {
+ break;
+ }
+ }
+
+ return result;
+}
+
+Result NfcDevice::ReadMifare(const MifareReadBlockParameter& parameter,
+ MifareReadBlockData& read_block_data) const {
+ const std::size_t sector_index = parameter.sector_number * sizeof(DataBlock);
+ read_block_data.sector_number = parameter.sector_number;
+
+ 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;
+ }
+
+ if (mifare_data.size() < sector_index + sizeof(DataBlock)) {
+ return Mifare::ResultReadError;
+ }
+
+ // TODO: Use parameter.sector_key to read encrypted data
+ memcpy(read_block_data.data.data(), mifare_data.data() + sector_index, sizeof(DataBlock));
+
+ return ResultSuccess;
+}
+
+Result NfcDevice::WriteMifare(std::span<const MifareWriteBlockParameter> parameters) {
+ Result result = ResultSuccess;
+
+ for (std::size_t i = 0; i < parameters.size(); i++) {
+ result = WriteMifare(parameters[i]);
+ if (result.IsError()) {
+ break;
+ }
+ }
+
+ if (!npad_device->WriteNfc(mifare_data)) {
+ LOG_ERROR(Service_NFP, "Error writing to file");
+ return Mifare::ResultReadError;
+ }
+
+ return result;
+}
+
+Result NfcDevice::WriteMifare(const MifareWriteBlockParameter& parameter) {
+ const std::size_t sector_index = parameter.sector_number * sizeof(DataBlock);
+
+ 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;
+ }
+
+ if (mifare_data.size() < sector_index + sizeof(DataBlock)) {
+ return Mifare::ResultReadError;
+ }
+
+ // TODO: Use parameter.sector_key to encrypt the data
+ memcpy(mifare_data.data() + sector_index, parameter.data.data(), sizeof(DataBlock));
+
+ return ResultSuccess;
+}
+
+Result NfcDevice::SendCommandByPassThrough(const Time::Clock::TimeSpanType& 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_) {
+ if (device_state != DeviceState::TagFound) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ return ResultWrongDeviceState;
+ }
+
+ // The loaded amiibo is not encrypted
+ if (is_plain_amiibo) {
+ device_state = DeviceState::TagMounted;
+ mount_target = mount_target_;
+ return ResultSuccess;
+ }
+
+ if (!NFP::AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) {
+ LOG_ERROR(Service_NFP, "Not an amiibo");
+ return ResultNotAnAmiibo;
+ }
+
+ // Mark amiibos as read only when keys are missing
+ if (!NFP::AmiiboCrypto::IsKeyAvailable()) {
+ LOG_ERROR(Service_NFP, "No keys detected");
+ device_state = DeviceState::TagMounted;
+ mount_target = NFP::MountTarget::Rom;
+ return ResultSuccess;
+ }
+
+ if (!NFP::AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) {
+ LOG_ERROR(Service_NFP, "Can't decode amiibo {}", device_state);
+ return ResultCorruptedData;
+ }
+
+ device_state = DeviceState::TagMounted;
+ mount_target = mount_target_;
+ 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(current_posix_time);
+ if (settings.write_date.raw_date != current_date.raw_date) {
+ settings.write_date = current_date;
+ UpdateSettingsCrc();
+ }
+
+ tag_data.write_counter++;
+
+ FlushWithBreak(NFP::BreakType::Normal);
+
+ is_data_moddified = false;
+
+ return ResultSuccess;
+}
+
+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++;
+
+ FlushWithBreak(NFP::BreakType::Normal);
+
+ is_data_moddified = false;
+
+ return ResultSuccess;
+}
+
+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;
+ }
+
+ std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File));
+ if (is_plain_amiibo) {
+ memcpy(data.data(), &tag_data, sizeof(tag_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));
+ }
+
+ if (!npad_device->WriteNfc(data)) {
+ LOG_ERROR(Service_NFP, "Error writing to file");
+ return ResultWriteAmiiboFailed;
+ }
+
+ return ResultSuccess;
+}
+
+Result NfcDevice::Restore() {
+ 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_NFC, "Amiibo is read only", device_state);
+ return ResultWrongDeviceState;
+ }
+
+ // TODO: Load amiibo from backup on system
+ LOG_ERROR(Service_NFP, "Not Implemented");
+ 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.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;
+ }
+
+ Service::Mii::MiiManager manager;
+ const auto& settings = tag_data.settings;
+
+ // TODO: Validate this data
+ register_info = {
+ .mii_char_info = manager.ConvertV3ToCharInfo(tag_data.owner_mii),
+ .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;
+ }
+
+ Service::Mii::MiiManager manager;
+ const auto& settings = tag_data.settings;
+
+ // TODO: Validate and complete this data
+ register_info = {
+ .mii_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.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;
+ }
+
+ Service::Mii::MiiManager manager;
+ const auto mii = manager.BuildDefault(0);
+ auto& settings = tag_data.settings;
+
+ if (tag_data.settings.settings.amiibo_initialized == 0) {
+ settings.init_date = GetAmiiboDate(current_posix_time);
+ settings.write_date.raw_date = 0;
+ }
+
+ SetAmiiboName(settings, register_info.amiibo_name);
+ tag_data.owner_mii = manager.BuildFromStoreData(mii);
+ tag_data.mii_extension = manager.SetFromStoreData(mii);
+ 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::RestoreAmiibo() {
+ 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;
+ }
+
+ // TODO: Load amiibo from backup on system
+ LOG_ERROR(Service_NFP, "Not Implemented");
+ return ResultSuccess;
+}
+
+Result NfcDevice::Format() {
+ auto result1 = DeleteApplicationArea();
+ auto result2 = DeleteRegisterInfo();
+
+ if (result1.IsError()) {
+ return result1;
+ }
+
+ if (result2.IsError()) {
+ return result2;
+ }
+
+ 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{};
+ 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{};
+ 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.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{};
+ Service::Mii::MiiManager manager;
+ 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::ReadBackupData(std::span<u8> data) const {
+ // Not implemented
+ return ResultSuccess;
+}
+
+Result NfcDevice::WriteBackupData(std::span<const u8> data) {
+ // Not implemented
+ return ResultSuccess;
+}
+
+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) {
+ 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 {
+ const auto& time_zone_manager =
+ system.GetTimeManager().GetTimeZoneContentManager().GetTimeZoneManager();
+ Time::TimeZone::CalendarInfo calendar_info{};
+ NFP::AmiiboDate amiibo_date{};
+
+ amiibo_date.SetYear(2000);
+ amiibo_date.SetMonth(1);
+ amiibo_date.SetDay(1);
+
+ if (time_zone_manager.ToCalendarTime({}, posix_time, calendar_info) == ResultSuccess) {
+ amiibo_date.SetYear(calendar_info.time.year);
+ amiibo_date.SetMonth(calendar_info.time.month);
+ amiibo_date.SetDay(calendar_info.time.day);
+ }
+
+ return amiibo_date;
+}
+
+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();
+}
+
+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 {
+ out_npad_id = npad_id;
+ return ResultSuccess;
+}
+
+} // namespace Service::NFC
diff --git a/src/core/hle/service/nfc/common/device.h b/src/core/hle/service/nfc/common/device.h
new file mode 100644
index 000000000..654eda98e
--- /dev/null
+++ b/src/core/hle/service/nfc/common/device.h
@@ -0,0 +1,138 @@
+// 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"
+#include "core/hle/service/time/clock_types.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, bool is_mifare) const;
+
+ Result ReadMifare(std::span<const MifareReadBlockParameter> parameters,
+ std::span<MifareReadBlockData> read_block_data) const;
+ Result ReadMifare(const MifareReadBlockParameter& parameter,
+ MifareReadBlockData& read_block_data) const;
+
+ Result WriteMifare(std::span<const MifareWriteBlockParameter> parameters);
+ Result WriteMifare(const MifareWriteBlockParameter& parameter);
+
+ Result SendCommandByPassThrough(const Time::Clock::TimeSpanType& 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 RestoreAmiibo();
+ 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 ReadBackupData(std::span<u8> data) const;
+ Result WriteBackupData(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(std::span<const u8> data);
+ void CloseNfcTag();
+
+ NFP::AmiiboName GetAmiiboName(const NFP::AmiiboSettings& settings) const;
+ void SetAmiiboName(NFP::AmiiboSettings& settings, const NFP::AmiiboName& amiibo_name);
+ NFP::AmiiboDate GetAmiiboDate(s64 posix_time) const;
+ u64 RemoveVersionByte(u64 application_id) const;
+ void UpdateSettingsCrc();
+ void UpdateRegisterInfoCrc();
+
+ 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_initalized{};
+ NfcProtocol allowed_protocols{};
+ DeviceState device_state{DeviceState::Unavailable};
+
+ // NFP data
+ bool is_data_moddified{};
+ bool is_app_area_open{};
+ bool is_plain_amiibo{};
+ s64 current_posix_time{};
+ NFP::MountTarget mount_target{NFP::MountTarget::None};
+
+ NFP::NTAG215File tag_data{};
+ std::vector<u8> mifare_data{};
+ NFP::EncryptedNTAG215File encrypted_tag_data{};
+};
+
+} // namespace Service::NFC
diff --git a/src/core/hle/service/nfc/common/device_manager.cpp b/src/core/hle/service/nfc/common/device_manager.cpp
new file mode 100644
index 000000000..d5deaaf27
--- /dev/null
+++ b/src/core/hle/service/nfc/common/device_manager.cpp
@@ -0,0 +1,695 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/hid/hid_types.h"
+#include "core/hle/kernel/k_event.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/time/clock_types.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>(Core::HID::IndexToNpadIdType(device_index), system,
+ service_context, availability_change_event);
+ }
+
+ is_initialized = false;
+}
+
+DeviceManager ::~DeviceManager() {
+ 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) const {
+ for (auto& device : devices) {
+ if (nfp_devices.size() >= max_allowed_devices) {
+ continue;
+ }
+ if (device->GetCurrentState() != DeviceState::Unavailable) {
+ nfp_devices.push_back(device->GetHandle());
+ }
+ }
+
+ if (nfp_devices.empty()) {
+ return ResultDeviceNotFound;
+ }
+
+ return ResultSuccess;
+}
+
+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::Unavailable;
+}
+
+Result DeviceManager::GetNpadId(u64 device_handle, Core::HID::NpadIdType& npad_id) const {
+ 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, bool is_mifare) const {
+ 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, is_mifare);
+ result = VerifyDeviceResult(device, result);
+ }
+
+ return result;
+}
+
+Kernel::KReadableEvent& DeviceManager::AttachActivateEvent(u64 device_handle) const {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ GetDeviceFromHandle(device_handle, device, false);
+
+ // TODO: Return proper error code on failure
+ return device->GetActivateEvent();
+}
+
+Kernel::KReadableEvent& DeviceManager::AttachDeactivateEvent(u64 device_handle) const {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ GetDeviceFromHandle(device_handle, device, false);
+
+ // TODO: Return proper error code on failure
+ return device->GetDeactivateEvent();
+}
+
+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 Time::Clock::TimeSpanType& 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) const {
+ 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) const {
+ 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) const {
+ 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) const {
+ 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) const {
+ 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) const {
+ 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) const {
+ 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) const {
+ 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) const {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ auto result = GetDeviceHandle(device_handle, device);
+
+ if (result.IsSuccess()) {
+ result = device->ReadBackupData(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);
+
+ if (result.IsSuccess()) {
+ result = device->WriteBackupData(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::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) const {
+ 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;
+ }
+
+ return operation_result;
+}
+
+Result DeviceManager::CheckDeviceState(std::shared_ptr<NfcDevice> device) const {
+ if (device == nullptr) {
+ return ResultInvalidArgument;
+ }
+
+ return ResultSuccess;
+}
+
+Result DeviceManager::IsNfcEnabled() const {
+ // TODO: This calls nn::settings::detail::GetNfcEnableFlag
+ const bool is_enabled = true;
+ 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
diff --git a/src/core/hle/service/nfc/common/device_manager.h b/src/core/hle/service/nfc/common/device_manager.h
new file mode 100644
index 000000000..2971e280f
--- /dev/null
+++ b/src/core/hle/service/nfc/common/device_manager.h
@@ -0,0 +1,100 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <array>
+#include <memory>
+#include <optional>
+#include <span>
+
+#include "core/hid/hid_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"
+#include "core/hle/service/time/clock_types.h"
+
+namespace Service::NFC {
+class NfcDevice;
+
+class DeviceManager {
+public:
+ explicit DeviceManager(Core::System& system_, KernelHelpers::ServiceContext& service_context_);
+ ~DeviceManager();
+
+ // Nfc device manager
+ Result Initialize();
+ Result Finalize();
+ Result ListDevices(std::vector<u64>& nfp_devices, std::size_t max_allowed_devices) const;
+ DeviceState GetDeviceState(u64 device_handle) const;
+ Result GetNpadId(u64 device_handle, Core::HID::NpadIdType& npad_id) const;
+ Kernel::KReadableEvent& AttachAvailabilityChangeEvent() const;
+ Result StartDetection(u64 device_handle, NfcProtocol tag_protocol);
+ Result StopDetection(u64 device_handle);
+ Result GetTagInfo(u64 device_handle, NFP::TagInfo& tag_info, bool is_mifare) const;
+ Kernel::KReadableEvent& AttachActivateEvent(u64 device_handle) const;
+ Kernel::KReadableEvent& AttachDeactivateEvent(u64 device_handle) const;
+ Result ReadMifare(u64 device_handle,
+ const std::span<const MifareReadBlockParameter> read_parameters,
+ std::span<MifareReadBlockData> read_data);
+ Result WriteMifare(u64 device_handle,
+ std::span<const MifareWriteBlockParameter> write_parameters);
+ Result SendCommandByPassThrough(u64 device_handle, const Time::Clock::TimeSpanType& timeout,
+ std::span<const u8> command_data, std::span<u8> out_data);
+
+ // Nfp device manager
+ Result Mount(u64 device_handle, NFP::ModelType model_type, NFP::MountTarget mount_target);
+ Result Unmount(u64 device_handle);
+ Result OpenApplicationArea(u64 device_handle, u32 access_id);
+ Result GetApplicationArea(u64 device_handle, std::span<u8> data) const;
+ Result SetApplicationArea(u64 device_handle, std::span<const u8> data);
+ Result Flush(u64 device_handle);
+ Result Restore(u64 device_handle);
+ Result CreateApplicationArea(u64 device_handle, u32 access_id, std::span<const u8> data);
+ Result GetRegisterInfo(u64 device_handle, NFP::RegisterInfo& register_info) const;
+ Result GetCommonInfo(u64 device_handle, NFP::CommonInfo& common_info) const;
+ Result GetModelInfo(u64 device_handle, NFP::ModelInfo& model_info) const;
+ u32 GetApplicationAreaSize() const;
+ Result RecreateApplicationArea(u64 device_handle, u32 access_id, std::span<const u8> data);
+ Result Format(u64 device_handle);
+ Result GetAdminInfo(u64 device_handle, NFP::AdminInfo& admin_info) const;
+ Result GetRegisterInfoPrivate(u64 device_handle, NFP::RegisterInfoPrivate& register_info) const;
+ Result SetRegisterInfoPrivate(u64 device_handle, const NFP::RegisterInfoPrivate& register_info);
+ Result DeleteRegisterInfo(u64 device_handle);
+ Result DeleteApplicationArea(u64 device_handle);
+ Result ExistsApplicationArea(u64 device_handle, bool& has_application_area) const;
+ Result GetAll(u64 device_handle, NFP::NfpData& nfp_data) const;
+ Result SetAll(u64 device_handle, const NFP::NfpData& nfp_data);
+ Result FlushDebug(u64 device_handle);
+ Result BreakTag(u64 device_handle, NFP::BreakType break_type);
+ Result ReadBackupData(u64 device_handle, std::span<u8> data) const;
+ Result WriteBackupData(u64 device_handle, std::span<const u8> data);
+ Result WriteNtf(u64 device_handle, NFP::WriteType, std::span<const u8> data);
+
+private:
+ Result IsNfcEnabled() const;
+ Result IsNfcParameterSet() const;
+ Result IsNfcInitialized() const;
+
+ Result GetDeviceFromHandle(u64 handle, std::shared_ptr<NfcDevice>& device,
+ bool check_state) const;
+
+ Result GetDeviceHandle(u64 handle, std::shared_ptr<NfcDevice>& device) const;
+ Result VerifyDeviceResult(std::shared_ptr<NfcDevice> device, Result operation_result) const;
+ Result CheckDeviceState(std::shared_ptr<NfcDevice> device) const;
+
+ std::optional<std::shared_ptr<NfcDevice>> GetNfcDevice(u64 handle);
+ const std::optional<std::shared_ptr<NfcDevice>> GetNfcDevice(u64 handle) const;
+
+ bool is_initialized = false;
+ mutable std::mutex mutex;
+ std::array<std::shared_ptr<NfcDevice>, 10> devices{};
+
+ Core::System& system;
+ KernelHelpers::ServiceContext service_context;
+ Kernel::KEvent* availability_change_event;
+};
+
+} // namespace Service::NFC