summaryrefslogtreecommitdiffstats
path: root/src/core/hle/service/nfp
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/hle/service/nfp')
-rw-r--r--src/core/hle/service/nfp/amiibo_crypto.cpp388
-rw-r--r--src/core/hle/service/nfp/amiibo_crypto.h100
-rw-r--r--src/core/hle/service/nfp/nfp.cpp808
-rw-r--r--src/core/hle/service/nfp/nfp.h258
-rw-r--r--src/core/hle/service/nfp/nfp_device.cpp681
-rw-r--r--src/core/hle/service/nfp/nfp_device.h101
-rw-r--r--src/core/hle/service/nfp/nfp_result.h24
-rw-r--r--src/core/hle/service/nfp/nfp_types.h320
-rw-r--r--src/core/hle/service/nfp/nfp_user.cpp664
-rw-r--r--src/core/hle/service/nfp/nfp_user.h44
10 files changed, 2335 insertions, 1053 deletions
diff --git a/src/core/hle/service/nfp/amiibo_crypto.cpp b/src/core/hle/service/nfp/amiibo_crypto.cpp
new file mode 100644
index 000000000..c32a6816b
--- /dev/null
+++ b/src/core/hle/service/nfp/amiibo_crypto.cpp
@@ -0,0 +1,388 @@
+// 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/path_util.h"
+#include "common/logging/log.h"
+#include "core/hle/service/mii/mii_manager.h"
+#include "core/hle/service/nfp/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 != 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;
+}
+
+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.settings = nfc_data.user_memory.settings;
+ encoded_data.owner_mii = nfc_data.user_memory.owner_mii;
+ encoded_data.title_id = nfc_data.user_memory.title_id;
+ encoded_data.applicaton_write_counter = nfc_data.user_memory.applicaton_write_counter;
+ encoded_data.application_area_id = nfc_data.user_memory.application_area_id;
+ encoded_data.unknown = nfc_data.user_memory.unknown;
+ encoded_data.unknown2 = nfc_data.user_memory.unknown2;
+ 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.settings = encoded_data.settings;
+ nfc_data.user_memory.owner_mii = encoded_data.owner_mii;
+ nfc_data.user_memory.title_id = encoded_data.title_id;
+ nfc_data.user_memory.applicaton_write_counter = encoded_data.applicaton_write_counter;
+ nfc_data.user_memory.application_area_id = encoded_data.application_area_id;
+ nfc_data.user_memory.unknown = encoded_data.unknown;
+ nfc_data.user_memory.unknown2 = encoded_data.unknown2;
+ 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) {
+ // Verifiy 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, "No keys detected");
+ 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 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/nfp/amiibo_crypto.h b/src/core/hle/service/nfp/amiibo_crypto.h
new file mode 100644
index 000000000..0175ced91
--- /dev/null
+++ b/src/core/hle/service/nfp/amiibo_crypto.h
@@ -0,0 +1,100 @@
+// 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;
+ UniqueSerialNumber uid_1;
+ u8 nintendo_id_1;
+ UniqueSerialNumber uid_2;
+ u8 nintendo_id_2;
+ std::array<u8, 0x20> keygen_salt;
+};
+static_assert(sizeof(HashSeed) == 0x40, "HashSeed is an invalid size");
+
+struct InternalKey {
+ HmacKey hmac_key;
+ std::array<char, 0xE> type_string;
+ u8 reserved;
+ u8 magic_length;
+ std::array<u8, 0x10> magic_bytes;
+ std::array<u8, 0x20> xor_pad;
+};
+static_assert(sizeof(InternalKey) == 0x50, "InternalKey is an invalid size");
+static_assert(std::is_trivially_copyable_v<InternalKey>, "InternalKey must be trivially copyable.");
+
+struct CryptoCtx {
+ std::array<char, 480> buffer;
+ bool used;
+ std::size_t buffer_size;
+ s16 counter;
+};
+
+struct DerivedKeys {
+ std::array<u8, 0x10> aes_key;
+ std::array<u8, 0x10> aes_iv;
+ std::array<u8, 0x10> hmac_key;
+};
+static_assert(sizeof(DerivedKeys) == 0x30, "DerivedKeys is an invalid size");
+
+/// Validates that the amiibo file is not corrupted
+bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file);
+
+/// 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);
+
+/// Decodes encripted amiibo data returns true if output is valid
+bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& tag_data);
+
+/// Encodes plain amiibo data returns true if output is valid
+bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_tag_data);
+
+} // namespace Service::NFP::AmiiboCrypto
diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp
index 6c5b41dd1..0cb55ca49 100644
--- a/src/core/hle/service/nfp/nfp.cpp
+++ b/src/core/hle/service/nfp/nfp.cpp
@@ -1,811 +1,43 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include <array>
-#include <atomic>
-
#include "common/logging/log.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/ipc_helpers.h"
-#include "core/hle/kernel/k_event.h"
-#include "core/hle/service/mii/mii_manager.h"
#include "core/hle/service/nfp/nfp.h"
#include "core/hle/service/nfp/nfp_user.h"
namespace Service::NFP {
-namespace ErrCodes {
-constexpr Result DeviceNotFound(ErrorModule::NFP, 64);
-constexpr Result WrongDeviceState(ErrorModule::NFP, 73);
-constexpr Result ApplicationAreaIsNotInitialized(ErrorModule::NFP, 128);
-constexpr Result ApplicationAreaExist(ErrorModule::NFP, 168);
-} // namespace ErrCodes
-
-constexpr u32 ApplicationAreaSize = 0xD8;
-
-IUser::IUser(Module::Interface& nfp_interface_, Core::System& system_)
- : ServiceFramework{system_, "NFP::IUser"}, service_context{system_, service_name},
- nfp_interface{nfp_interface_} {
- static const FunctionInfo functions[] = {
- {0, &IUser::Initialize, "Initialize"},
- {1, &IUser::Finalize, "Finalize"},
- {2, &IUser::ListDevices, "ListDevices"},
- {3, &IUser::StartDetection, "StartDetection"},
- {4, &IUser::StopDetection, "StopDetection"},
- {5, &IUser::Mount, "Mount"},
- {6, &IUser::Unmount, "Unmount"},
- {7, &IUser::OpenApplicationArea, "OpenApplicationArea"},
- {8, &IUser::GetApplicationArea, "GetApplicationArea"},
- {9, &IUser::SetApplicationArea, "SetApplicationArea"},
- {10, nullptr, "Flush"},
- {11, nullptr, "Restore"},
- {12, &IUser::CreateApplicationArea, "CreateApplicationArea"},
- {13, &IUser::GetTagInfo, "GetTagInfo"},
- {14, &IUser::GetRegisterInfo, "GetRegisterInfo"},
- {15, &IUser::GetCommonInfo, "GetCommonInfo"},
- {16, &IUser::GetModelInfo, "GetModelInfo"},
- {17, &IUser::AttachActivateEvent, "AttachActivateEvent"},
- {18, &IUser::AttachDeactivateEvent, "AttachDeactivateEvent"},
- {19, &IUser::GetState, "GetState"},
- {20, &IUser::GetDeviceState, "GetDeviceState"},
- {21, &IUser::GetNpadId, "GetNpadId"},
- {22, &IUser::GetApplicationAreaSize, "GetApplicationAreaSize"},
- {23, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
- {24, nullptr, "RecreateApplicationArea"},
- };
- RegisterHandlers(functions);
-
- availability_change_event = service_context.CreateEvent("IUser:AvailabilityChangeEvent");
-}
-
-void IUser::Initialize(Kernel::HLERequestContext& ctx) {
- LOG_INFO(Service_NFC, "called");
-
- state = State::Initialized;
-
- // TODO(german77): Loop through all interfaces
- nfp_interface.Initialize();
-
- IPC::ResponseBuilder rb{ctx, 2, 0};
- rb.Push(ResultSuccess);
-}
-
-void IUser::Finalize(Kernel::HLERequestContext& ctx) {
- LOG_INFO(Service_NFP, "called");
-
- state = State::NonInitialized;
-
- // TODO(german77): Loop through all interfaces
- nfp_interface.Finalize();
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void IUser::ListDevices(Kernel::HLERequestContext& ctx) {
- LOG_INFO(Service_NFP, "called");
-
- std::vector<u64> devices;
-
- // TODO(german77): Loop through all interfaces
- devices.push_back(nfp_interface.GetHandle());
-
- ctx.WriteBuffer(devices);
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push(static_cast<s32>(devices.size()));
-}
-
-void IUser::StartDetection(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- const auto nfp_protocol{rp.Pop<s32>()};
- LOG_INFO(Service_NFP, "called, device_handle={}, nfp_protocol={}", device_handle, nfp_protocol);
-
- // TODO(german77): Loop through all interfaces
- if (device_handle == nfp_interface.GetHandle()) {
- const auto result = nfp_interface.StartDetection(nfp_protocol);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
- return;
- }
-
- LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::StopDetection(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
-
- // TODO(german77): Loop through all interfaces
- if (device_handle == nfp_interface.GetHandle()) {
- const auto result = nfp_interface.StopDetection();
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
- return;
- }
-
- LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::Mount(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- const auto model_type{rp.PopEnum<ModelType>()};
- const auto mount_target{rp.PopEnum<MountTarget>()};
- LOG_INFO(Service_NFP, "called, device_handle={}, model_type={}, mount_target={}", device_handle,
- model_type, mount_target);
-
- // TODO(german77): Loop through all interfaces
- if (device_handle == nfp_interface.GetHandle()) {
- const auto result = nfp_interface.Mount();
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
- return;
- }
-
- LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::Unmount(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
-
- // TODO(german77): Loop through all interfaces
- if (device_handle == nfp_interface.GetHandle()) {
- const auto result = nfp_interface.Unmount();
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
- return;
- }
-
- LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::OpenApplicationArea(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- const auto access_id{rp.Pop<u32>()};
- LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, access_id={}", device_handle,
- access_id);
-
- // TODO(german77): Loop through all interfaces
- if (device_handle == nfp_interface.GetHandle()) {
- const auto result = nfp_interface.OpenApplicationArea(access_id);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
- return;
- }
-
- LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::GetApplicationArea(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
-
- // TODO(german77): Loop through all interfaces
- if (device_handle == nfp_interface.GetHandle()) {
- std::vector<u8> data{};
- const auto result = nfp_interface.GetApplicationArea(data);
- ctx.WriteBuffer(data);
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(result);
- rb.Push(static_cast<u32>(data.size()));
- return;
- }
-
- LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::SetApplicationArea(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- const auto data{ctx.ReadBuffer()};
- LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, data_size={}", device_handle,
- data.size());
-
- // TODO(german77): Loop through all interfaces
- if (device_handle == nfp_interface.GetHandle()) {
- const auto result = nfp_interface.SetApplicationArea(data);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
- return;
- }
- LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::CreateApplicationArea(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- const auto access_id{rp.Pop<u32>()};
- const auto data{ctx.ReadBuffer()};
- LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, data_size={}, access_id={}",
- device_handle, access_id, data.size());
-
- // TODO(german77): Loop through all interfaces
- if (device_handle == nfp_interface.GetHandle()) {
- const auto result = nfp_interface.CreateApplicationArea(access_id, data);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
- return;
- }
-
- LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::GetTagInfo(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
-
- // TODO(german77): Loop through all interfaces
- if (device_handle == nfp_interface.GetHandle()) {
- TagInfo tag_info{};
- const auto result = nfp_interface.GetTagInfo(tag_info);
- ctx.WriteBuffer(tag_info);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
- return;
- }
-
- LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::GetRegisterInfo(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
-
- // TODO(german77): Loop through all interfaces
- if (device_handle == nfp_interface.GetHandle()) {
- RegisterInfo register_info{};
- const auto result = nfp_interface.GetRegisterInfo(register_info);
- ctx.WriteBuffer(register_info);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
- return;
- }
-
- LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::GetCommonInfo(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
-
- // TODO(german77): Loop through all interfaces
- if (device_handle == nfp_interface.GetHandle()) {
- CommonInfo common_info{};
- const auto result = nfp_interface.GetCommonInfo(common_info);
- ctx.WriteBuffer(common_info);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
- return;
- }
-
- LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::GetModelInfo(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
-
- // TODO(german77): Loop through all interfaces
- if (device_handle == nfp_interface.GetHandle()) {
- ModelInfo model_info{};
- const auto result = nfp_interface.GetModelInfo(model_info);
- ctx.WriteBuffer(model_info);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
- return;
- }
-
- LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::AttachActivateEvent(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
-
- // TODO(german77): Loop through all interfaces
- if (device_handle == nfp_interface.GetHandle()) {
- IPC::ResponseBuilder rb{ctx, 2, 1};
- rb.Push(ResultSuccess);
- rb.PushCopyObjects(nfp_interface.GetActivateEvent());
- return;
- }
-
- LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::AttachDeactivateEvent(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
-
- // TODO(german77): Loop through all interfaces
- if (device_handle == nfp_interface.GetHandle()) {
- IPC::ResponseBuilder rb{ctx, 2, 1};
- rb.Push(ResultSuccess);
- rb.PushCopyObjects(nfp_interface.GetDeactivateEvent());
- return;
- }
-
- LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::GetState(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_NFC, "called");
-
- IPC::ResponseBuilder rb{ctx, 3, 0};
- rb.Push(ResultSuccess);
- rb.PushEnum(state);
-}
-
-void IUser::GetDeviceState(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
-
- // TODO(german77): Loop through all interfaces
- if (device_handle == nfp_interface.GetHandle()) {
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.PushEnum(nfp_interface.GetCurrentState());
- return;
- }
-
- LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::GetNpadId(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
-
- // TODO(german77): Loop through all interfaces
- if (device_handle == nfp_interface.GetHandle()) {
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.PushEnum(nfp_interface.GetNpadId());
- return;
- }
-
- LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::GetApplicationAreaSize(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
-
- // TODO(german77): Loop through all interfaces
- if (device_handle == nfp_interface.GetHandle()) {
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push(ApplicationAreaSize);
- return;
- }
-
- LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ErrCodes::DeviceNotFound);
-}
-
-void IUser::AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_NFP, "(STUBBED) called");
-
- IPC::ResponseBuilder rb{ctx, 2, 1};
- rb.Push(ResultSuccess);
- rb.PushCopyObjects(availability_change_event->GetReadableEvent());
-}
-
-Module::Interface::Interface(std::shared_ptr<Module> module_, Core::System& system_,
- const char* name)
- : ServiceFramework{system_, name}, module{std::move(module_)},
- npad_id{Core::HID::NpadIdType::Player1}, service_context{system_, service_name} {
- activate_event = service_context.CreateEvent("IUser:NFPActivateEvent");
- deactivate_event = service_context.CreateEvent("IUser:NFPDeactivateEvent");
-}
-
-Module::Interface::~Interface() = default;
-
-void Module::Interface::CreateUserInterface(Kernel::HLERequestContext& ctx) {
- LOG_DEBUG(Service_NFP, "called");
-
- IPC::ResponseBuilder rb{ctx, 2, 0, 1};
- rb.Push(ResultSuccess);
- rb.PushIpcInterface<IUser>(*this, system);
-}
-
-bool Module::Interface::LoadAmiibo(const std::vector<u8>& buffer) {
- if (device_state != DeviceState::SearchingForTag) {
- LOG_ERROR(Service_NFP, "Game is not looking for amiibos, current state {}", device_state);
- return false;
- }
-
- constexpr auto tag_size = sizeof(NTAG215File);
- constexpr auto tag_size_without_password = sizeof(NTAG215File) - sizeof(NTAG215Password);
-
- std::vector<u8> amiibo_buffer = buffer;
-
- if (amiibo_buffer.size() < tag_size_without_password) {
- LOG_ERROR(Service_NFP, "Wrong file size {}", buffer.size());
- return false;
- }
-
- // Ensure it has the correct size
- if (amiibo_buffer.size() != tag_size) {
- amiibo_buffer.resize(tag_size, 0);
- }
-
- LOG_INFO(Service_NFP, "Amiibo detected");
- std::memcpy(&tag_data, buffer.data(), tag_size);
-
- if (!IsAmiiboValid()) {
- return false;
- }
-
- // This value can't be dumped from a tag. Generate it
- tag_data.password.PWD = GetTagPassword(tag_data.uuid);
-
- device_state = DeviceState::TagFound;
- activate_event->GetWritableEvent().Signal();
- return true;
-}
-
-void Module::Interface::CloseAmiibo() {
- LOG_INFO(Service_NFP, "Remove amiibo");
- device_state = DeviceState::TagRemoved;
- is_application_area_initialized = false;
- application_area_id = 0;
- application_area_data.clear();
- deactivate_event->GetWritableEvent().Signal();
-}
-
-bool Module::Interface::IsAmiiboValid() const {
- const auto& amiibo_data = tag_data.user_memory;
- LOG_DEBUG(Service_NFP, "uuid_lock=0x{0:x}", tag_data.lock_bytes);
- LOG_DEBUG(Service_NFP, "compability_container=0x{0:x}", tag_data.compability_container);
- LOG_DEBUG(Service_NFP, "crypto_init=0x{0:x}", amiibo_data.crypto_init);
- LOG_DEBUG(Service_NFP, "write_count={}", amiibo_data.write_count);
-
- 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}", amiibo_data.model_info.model_number);
- LOG_DEBUG(Service_NFP, "series={}", amiibo_data.model_info.series);
- LOG_DEBUG(Service_NFP, "fixed_value=0x{0:x}", amiibo_data.model_info.fixed);
-
- LOG_DEBUG(Service_NFP, "tag_dynamic_lock=0x{0:x}", tag_data.dynamic_lock);
- LOG_DEBUG(Service_NFP, "tag_CFG0=0x{0:x}", tag_data.CFG0);
- LOG_DEBUG(Service_NFP, "tag_CFG1=0x{0:x}", tag_data.CFG1);
-
- // Check against all know constants on an amiibo binary
- if (tag_data.lock_bytes != 0xE00F) {
- return false;
- }
- if (tag_data.compability_container != 0xEEFF10F1U) {
- return false;
- }
- if ((amiibo_data.crypto_init & 0xFF) != 0xA5) {
- return false;
- }
- if (amiibo_data.model_info.fixed != 0x02) {
- return false;
- }
- if ((tag_data.dynamic_lock & 0xFFFFFF) != 0x0F0001) {
- return false;
- }
- if (tag_data.CFG0 != 0x04000000U) {
- return false;
- }
- if (tag_data.CFG1 != 0x5F) {
- return false;
- }
- return true;
-}
-
-Kernel::KReadableEvent& Module::Interface::GetActivateEvent() const {
- return activate_event->GetReadableEvent();
-}
-
-Kernel::KReadableEvent& Module::Interface::GetDeactivateEvent() const {
- return deactivate_event->GetReadableEvent();
-}
-
-void Module::Interface::Initialize() {
- device_state = DeviceState::Initialized;
-}
-
-void Module::Interface::Finalize() {
- device_state = DeviceState::Unaviable;
- is_application_area_initialized = false;
- application_area_id = 0;
- application_area_data.clear();
-}
-
-Result Module::Interface::StartDetection(s32 protocol_) {
- auto npad_device = system.HIDCore().GetEmulatedController(npad_id);
-
- // TODO(german77): Add callback for when nfc data is available
-
- if (device_state == DeviceState::Initialized || device_state == DeviceState::TagRemoved) {
- npad_device->SetPollingMode(Common::Input::PollingMode::NFC);
- device_state = DeviceState::SearchingForTag;
- protocol = protocol_;
- return ResultSuccess;
- }
-
- LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
- return ErrCodes::WrongDeviceState;
-}
-
-Result Module::Interface::StopDetection() {
- auto npad_device = system.HIDCore().GetEmulatedController(npad_id);
- npad_device->SetPollingMode(Common::Input::PollingMode::Active);
-
- if (device_state == DeviceState::TagFound || device_state == DeviceState::TagMounted) {
- CloseAmiibo();
- return ResultSuccess;
- }
- if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) {
- device_state = DeviceState::Initialized;
- return ResultSuccess;
- }
-
- LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
- return ErrCodes::WrongDeviceState;
-}
-
-Result Module::Interface::Mount() {
- if (device_state == DeviceState::TagFound) {
- device_state = DeviceState::TagMounted;
- return ResultSuccess;
- }
-
- LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
- return ErrCodes::WrongDeviceState;
-}
-
-Result Module::Interface::Unmount() {
- if (device_state == DeviceState::TagMounted) {
- is_application_area_initialized = false;
- application_area_id = 0;
- application_area_data.clear();
- device_state = DeviceState::TagFound;
- return ResultSuccess;
- }
-
- LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
- return ErrCodes::WrongDeviceState;
-}
-
-Result Module::Interface::GetTagInfo(TagInfo& tag_info) const {
- if (device_state == DeviceState::TagFound || device_state == DeviceState::TagMounted) {
- tag_info = {
- .uuid = tag_data.uuid,
- .uuid_length = static_cast<u8>(tag_data.uuid.size()),
- .protocol = protocol,
- .tag_type = static_cast<u32>(tag_data.user_memory.model_info.amiibo_type),
+class IUserManager final : public ServiceFramework<IUserManager> {
+public:
+ explicit IUserManager(Core::System& system_) : ServiceFramework{system_, "nfp:user"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &IUserManager::CreateUserInterface, "CreateUserInterface"},
};
- return ResultSuccess;
- }
-
- LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
- return ErrCodes::WrongDeviceState;
-}
+ // clang-format on
-Result Module::Interface::GetCommonInfo(CommonInfo& common_info) const {
- if (device_state != DeviceState::TagMounted) {
- LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
- return ErrCodes::WrongDeviceState;
+ RegisterHandlers(functions);
}
- // Read this data from the amiibo save file
- common_info = {
- .last_write_year = 2022,
- .last_write_month = 2,
- .last_write_day = 7,
- .write_counter = tag_data.user_memory.write_count,
- .version = 1,
- .application_area_size = ApplicationAreaSize,
- };
- return ResultSuccess;
-}
-
-Result Module::Interface::GetModelInfo(ModelInfo& model_info) const {
- if (device_state != DeviceState::TagMounted) {
- LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
- return ErrCodes::WrongDeviceState;
- }
+private:
+ void CreateUserInterface(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_NFP, "called");
- model_info = tag_data.user_memory.model_info;
- return ResultSuccess;
-}
+ if (user_interface == nullptr) {
+ user_interface = std::make_shared<IUser>(system);
+ }
-Result Module::Interface::GetRegisterInfo(RegisterInfo& register_info) const {
- if (device_state != DeviceState::TagMounted) {
- LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
- return ErrCodes::WrongDeviceState;
- }
-
- Service::Mii::MiiManager manager;
-
- // Read this data from the amiibo save file
- register_info = {
- .mii_char_info = manager.BuildDefault(0),
- .first_write_year = 2022,
- .first_write_month = 2,
- .first_write_day = 7,
- .amiibo_name = {'Y', 'u', 'z', 'u', 'A', 'm', 'i', 'i', 'b', 'o', 0},
- .unknown = {},
- };
- return ResultSuccess;
-}
-
-Result Module::Interface::OpenApplicationArea(u32 access_id) {
- if (device_state != DeviceState::TagMounted) {
- LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
- return ErrCodes::WrongDeviceState;
- }
- if (AmiiboApplicationDataExist(access_id)) {
- application_area_data = LoadAmiiboApplicationData(access_id);
- application_area_id = access_id;
- is_application_area_initialized = true;
- }
- if (!is_application_area_initialized) {
- LOG_WARNING(Service_NFP, "Application area is not initialized");
- return ErrCodes::ApplicationAreaIsNotInitialized;
- }
- return ResultSuccess;
-}
-
-Result Module::Interface::GetApplicationArea(std::vector<u8>& data) const {
- if (device_state != DeviceState::TagMounted) {
- LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
- return ErrCodes::WrongDeviceState;
- }
- if (!is_application_area_initialized) {
- LOG_ERROR(Service_NFP, "Application area is not initialized");
- return ErrCodes::ApplicationAreaIsNotInitialized;
- }
-
- data = application_area_data;
-
- return ResultSuccess;
-}
-
-Result Module::Interface::SetApplicationArea(const std::vector<u8>& data) {
- if (device_state != DeviceState::TagMounted) {
- LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
- return ErrCodes::WrongDeviceState;
- }
- if (!is_application_area_initialized) {
- LOG_ERROR(Service_NFP, "Application area is not initialized");
- return ErrCodes::ApplicationAreaIsNotInitialized;
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<IUser>(user_interface);
}
- application_area_data = data;
- SaveAmiiboApplicationData(application_area_id, application_area_data);
- return ResultSuccess;
-}
-Result Module::Interface::CreateApplicationArea(u32 access_id, const std::vector<u8>& data) {
- if (device_state != DeviceState::TagMounted) {
- LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
- return ErrCodes::WrongDeviceState;
- }
- if (AmiiboApplicationDataExist(access_id)) {
- LOG_ERROR(Service_NFP, "Application area already exist");
- return ErrCodes::ApplicationAreaExist;
- }
- application_area_data = data;
- application_area_id = access_id;
- SaveAmiiboApplicationData(application_area_id, application_area_data);
- return ResultSuccess;
-}
-
-bool Module::Interface::AmiiboApplicationDataExist(u32 access_id) const {
- // TODO(german77): Check if file exist
- return false;
-}
-
-std::vector<u8> Module::Interface::LoadAmiiboApplicationData(u32 access_id) const {
- // TODO(german77): Read file
- std::vector<u8> data(ApplicationAreaSize);
- return data;
-}
-
-void Module::Interface::SaveAmiiboApplicationData(u32 access_id,
- const std::vector<u8>& data) const {
- // TODO(german77): Save file
-}
-
-u64 Module::Interface::GetHandle() const {
- // Generate a handle based of the npad id
- return static_cast<u64>(npad_id);
-}
-
-DeviceState Module::Interface::GetCurrentState() const {
- return device_state;
-}
-
-Core::HID::NpadIdType Module::Interface::GetNpadId() const {
- return npad_id;
-}
-
-u32 Module::Interface::GetTagPassword(const TagUuid& uuid) const {
- // Verifiy that the generated password is correct
- u32 password = 0xAA ^ (uuid[1] ^ uuid[3]);
- password &= (0x55 ^ (uuid[2] ^ uuid[4])) << 8;
- password &= (0xAA ^ (uuid[3] ^ uuid[5])) << 16;
- password &= (0x55 ^ (uuid[4] ^ uuid[6])) << 24;
- return password;
-}
+ std::shared_ptr<IUser> user_interface;
+};
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
- auto module = std::make_shared<Module>();
- std::make_shared<NFP_User>(module, system)->InstallAsService(service_manager);
+ std::make_shared<IUserManager>(system)->InstallAsService(service_manager);
}
} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp.h b/src/core/hle/service/nfp/nfp.h
index 0fc808781..a25c362b8 100644
--- a/src/core/hle/service/nfp/nfp.h
+++ b/src/core/hle/service/nfp/nfp.h
@@ -3,268 +3,10 @@
#pragma once
-#include <array>
-#include <vector>
-
-#include "common/common_funcs.h"
-#include "core/hle/service/kernel_helpers.h"
-#include "core/hle/service/mii/types.h"
#include "core/hle/service/service.h"
-namespace Kernel {
-class KEvent;
-class KReadableEvent;
-} // namespace Kernel
-
-namespace Core::HID {
-enum class NpadIdType : u32;
-} // namespace Core::HID
-
namespace Service::NFP {
-enum class ServiceType : u32 {
- User,
- Debug,
- System,
-};
-
-enum class State : u32 {
- NonInitialized,
- Initialized,
-};
-
-enum class DeviceState : u32 {
- Initialized,
- SearchingForTag,
- TagFound,
- TagRemoved,
- TagMounted,
- Unaviable,
- Finalized,
-};
-
-enum class ModelType : u32 {
- Amiibo,
-};
-
-enum class MountTarget : u32 {
- Rom,
- Ram,
- All,
-};
-
-enum class AmiiboType : u8 {
- Figure,
- Card,
- Yarn,
-};
-
-enum class AmiiboSeries : u8 {
- SuperSmashBros,
- SuperMario,
- ChibiRobo,
- YoshiWoollyWorld,
- Splatoon,
- AnimalCrossing,
- EightBitMario,
- Skylanders,
- Unknown8,
- TheLegendOfZelda,
- ShovelKnight,
- Unknown11,
- Kiby,
- Pokemon,
- MarioSportsSuperstars,
- MonsterHunter,
- BoxBoy,
- Pikmin,
- FireEmblem,
- Metroid,
- Others,
- MegaMan,
- Diablo
-};
-
-using TagUuid = std::array<u8, 10>;
-
-struct TagInfo {
- TagUuid uuid;
- u8 uuid_length;
- INSERT_PADDING_BYTES(0x15);
- s32 protocol;
- u32 tag_type;
- INSERT_PADDING_BYTES(0x30);
-};
-static_assert(sizeof(TagInfo) == 0x58, "TagInfo is an invalid size");
-
-struct CommonInfo {
- u16 last_write_year;
- u8 last_write_month;
- u8 last_write_day;
- u16 write_counter;
- u16 version;
- u32 application_area_size;
- INSERT_PADDING_BYTES(0x34);
-};
-static_assert(sizeof(CommonInfo) == 0x40, "CommonInfo is an invalid size");
-
-struct ModelInfo {
- u16 character_id;
- u8 character_variant;
- AmiiboType amiibo_type;
- u16 model_number;
- AmiiboSeries series;
- u8 fixed; // Must be 02
- INSERT_PADDING_BYTES(0x4); // Unknown
- INSERT_PADDING_BYTES(0x20); // Probably a SHA256-(HMAC?) hash
- INSERT_PADDING_BYTES(0x14); // SHA256-HMAC
-};
-static_assert(sizeof(ModelInfo) == 0x40, "ModelInfo is an invalid size");
-
-struct RegisterInfo {
- Service::Mii::MiiInfo mii_char_info;
- u16 first_write_year;
- u8 first_write_month;
- u8 first_write_day;
- std::array<u8, 11> amiibo_name;
- u8 unknown;
- INSERT_PADDING_BYTES(0x98);
-};
-static_assert(sizeof(RegisterInfo) == 0x100, "RegisterInfo is an invalid size");
-
-class Module final {
-public:
- class Interface : public ServiceFramework<Interface> {
- public:
- explicit Interface(std::shared_ptr<Module> module_, Core::System& system_,
- const char* name);
- ~Interface() override;
-
- struct EncryptedAmiiboFile {
- u16 crypto_init; // Must be A5 XX
- u16 write_count; // Number of times the amiibo has been written?
- INSERT_PADDING_BYTES(0x20); // System crypts
- INSERT_PADDING_BYTES(0x20); // SHA256-(HMAC?) hash
- ModelInfo model_info; // This struct is bigger than documentation
- INSERT_PADDING_BYTES(0xC); // SHA256-HMAC
- INSERT_PADDING_BYTES(0x114); // section 1 encrypted buffer
- INSERT_PADDING_BYTES(0x54); // section 2 encrypted buffer
- };
- static_assert(sizeof(EncryptedAmiiboFile) == 0x1F8, "AmiiboFile is an invalid size");
-
- struct NTAG215Password {
- u32 PWD; // Password to allow write access
- u16 PACK; // Password acknowledge reply
- u16 RFUI; // Reserved for future use
- };
- static_assert(sizeof(NTAG215Password) == 0x8, "NTAG215Password is an invalid size");
-
- struct NTAG215File {
- TagUuid uuid; // Unique serial number
- u16 lock_bytes; // Set defined pages as read only
- u32 compability_container; // Defines available memory
- EncryptedAmiiboFile user_memory; // Writable data
- u32 dynamic_lock; // Dynamic lock
- u32 CFG0; // Defines memory protected by password
- u32 CFG1; // Defines number of verification attempts
- NTAG215Password password; // Password data
- };
- static_assert(sizeof(NTAG215File) == 0x21C, "NTAG215File is an invalid size");
-
- void CreateUserInterface(Kernel::HLERequestContext& ctx);
- bool LoadAmiibo(const std::vector<u8>& buffer);
- void CloseAmiibo();
-
- void Initialize();
- void Finalize();
-
- Result StartDetection(s32 protocol_);
- Result StopDetection();
- Result Mount();
- Result Unmount();
-
- Result GetTagInfo(TagInfo& tag_info) const;
- Result GetCommonInfo(CommonInfo& common_info) const;
- Result GetModelInfo(ModelInfo& model_info) const;
- Result GetRegisterInfo(RegisterInfo& register_info) const;
-
- Result OpenApplicationArea(u32 access_id);
- Result GetApplicationArea(std::vector<u8>& data) const;
- Result SetApplicationArea(const std::vector<u8>& data);
- Result CreateApplicationArea(u32 access_id, const std::vector<u8>& data);
-
- u64 GetHandle() const;
- DeviceState GetCurrentState() const;
- Core::HID::NpadIdType GetNpadId() const;
-
- Kernel::KReadableEvent& GetActivateEvent() const;
- Kernel::KReadableEvent& GetDeactivateEvent() const;
-
- protected:
- std::shared_ptr<Module> module;
-
- private:
- /// Validates that the amiibo file is not corrupted
- bool IsAmiiboValid() const;
-
- bool AmiiboApplicationDataExist(u32 access_id) const;
- std::vector<u8> LoadAmiiboApplicationData(u32 access_id) const;
- void SaveAmiiboApplicationData(u32 access_id, const std::vector<u8>& data) const;
-
- /// return password needed to allow write access to protected memory
- u32 GetTagPassword(const TagUuid& uuid) const;
-
- const Core::HID::NpadIdType npad_id;
-
- DeviceState device_state{DeviceState::Unaviable};
- KernelHelpers::ServiceContext service_context;
- Kernel::KEvent* activate_event;
- Kernel::KEvent* deactivate_event;
- NTAG215File tag_data{};
- s32 protocol;
- bool is_application_area_initialized{};
- u32 application_area_id;
- std::vector<u8> application_area_data;
- };
-};
-
-class IUser final : public ServiceFramework<IUser> {
-public:
- explicit IUser(Module::Interface& nfp_interface_, Core::System& system_);
-
-private:
- void Initialize(Kernel::HLERequestContext& ctx);
- void Finalize(Kernel::HLERequestContext& ctx);
- void ListDevices(Kernel::HLERequestContext& ctx);
- void StartDetection(Kernel::HLERequestContext& ctx);
- void StopDetection(Kernel::HLERequestContext& ctx);
- void Mount(Kernel::HLERequestContext& ctx);
- void Unmount(Kernel::HLERequestContext& ctx);
- void OpenApplicationArea(Kernel::HLERequestContext& ctx);
- void GetApplicationArea(Kernel::HLERequestContext& ctx);
- void SetApplicationArea(Kernel::HLERequestContext& ctx);
- void CreateApplicationArea(Kernel::HLERequestContext& ctx);
- void GetTagInfo(Kernel::HLERequestContext& ctx);
- void GetRegisterInfo(Kernel::HLERequestContext& ctx);
- void GetCommonInfo(Kernel::HLERequestContext& ctx);
- void GetModelInfo(Kernel::HLERequestContext& ctx);
- void AttachActivateEvent(Kernel::HLERequestContext& ctx);
- void AttachDeactivateEvent(Kernel::HLERequestContext& ctx);
- void GetState(Kernel::HLERequestContext& ctx);
- void GetDeviceState(Kernel::HLERequestContext& ctx);
- void GetNpadId(Kernel::HLERequestContext& ctx);
- void GetApplicationAreaSize(Kernel::HLERequestContext& ctx);
- void AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx);
-
- KernelHelpers::ServiceContext service_context;
-
- // TODO(german77): We should have a vector of interfaces
- Module::Interface& nfp_interface;
-
- State state{State::NonInitialized};
- Kernel::KEvent* availability_change_event;
-};
-
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);
} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp_device.cpp b/src/core/hle/service/nfp/nfp_device.cpp
new file mode 100644
index 000000000..76f8a267a
--- /dev/null
+++ b/src/core/hle/service/nfp/nfp_device.cpp
@@ -0,0 +1,681 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <array>
+#include <atomic>
+
+#include "common/fs/file.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/hid/emulated_controller.h"
+#include "core/hid/hid_core.h"
+#include "core/hid/hid_types.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/service/mii/mii_manager.h"
+#include "core/hle/service/nfp/amiibo_crypto.h"
+#include "core/hle/service/nfp/nfp.h"
+#include "core/hle/service/nfp/nfp_device.h"
+#include "core/hle/service/nfp/nfp_result.h"
+#include "core/hle/service/nfp/nfp_user.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::NFP {
+NfpDevice::NfpDevice(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("IUser:NFPActivateEvent");
+ deactivate_event = service_context.CreateEvent("IUser:NFPDeactivateEvent");
+ 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;
+}
+
+NfpDevice::~NfpDevice() {
+ if (!is_controller_set) {
+ return;
+ }
+ npad_device->DeleteCallback(callback_key);
+ is_controller_set = false;
+};
+
+void NfpDevice::NpadUpdate(Core::HID::ControllerTriggerType type) {
+ if (type == Core::HID::ControllerTriggerType::Connected ||
+ type == Core::HID::ControllerTriggerType::Disconnected) {
+ 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:
+ LoadAmiibo(nfc_status.data);
+ break;
+ case Common::Input::NfcState::AmiiboRemoved:
+ if (device_state != DeviceState::SearchingForTag) {
+ CloseAmiibo();
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+bool NfpDevice::LoadAmiibo(std::span<const u8> data) {
+ if (device_state != DeviceState::SearchingForTag) {
+ LOG_ERROR(Service_NFP, "Game is not looking for amiibos, current state {}", device_state);
+ return false;
+ }
+
+ if (data.size() != sizeof(EncryptedNTAG215File)) {
+ LOG_ERROR(Service_NFP, "Not an amiibo, size={}", data.size());
+ return false;
+ }
+
+ memcpy(&encrypted_tag_data, data.data(), sizeof(EncryptedNTAG215File));
+
+ device_state = DeviceState::TagFound;
+ deactivate_event->GetReadableEvent().Clear();
+ activate_event->Signal();
+ return true;
+}
+
+void NfpDevice::CloseAmiibo() {
+ LOG_INFO(Service_NFP, "Remove amiibo");
+
+ if (device_state == DeviceState::TagMounted) {
+ Unmount();
+ }
+
+ device_state = DeviceState::TagRemoved;
+ encrypted_tag_data = {};
+ tag_data = {};
+ activate_event->GetReadableEvent().Clear();
+ deactivate_event->Signal();
+}
+
+Kernel::KReadableEvent& NfpDevice::GetActivateEvent() const {
+ return activate_event->GetReadableEvent();
+}
+
+Kernel::KReadableEvent& NfpDevice::GetDeactivateEvent() const {
+ return deactivate_event->GetReadableEvent();
+}
+
+void NfpDevice::Initialize() {
+ device_state = npad_device->HasNfc() ? DeviceState::Initialized : DeviceState::Unavailable;
+ encrypted_tag_data = {};
+ tag_data = {};
+}
+
+void NfpDevice::Finalize() {
+ if (device_state == DeviceState::TagMounted) {
+ Unmount();
+ }
+ if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) {
+ StopDetection();
+ }
+ device_state = DeviceState::Unavailable;
+}
+
+Result NfpDevice::StartDetection(s32 protocol_) {
+ if (device_state != DeviceState::Initialized && device_state != DeviceState::TagRemoved) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ return WrongDeviceState;
+ }
+
+ if (!npad_device->SetPollingMode(Common::Input::PollingMode::NFC)) {
+ LOG_ERROR(Service_NFP, "Nfc not supported");
+ return NfcDisabled;
+ }
+
+ device_state = DeviceState::SearchingForTag;
+ protocol = protocol_;
+ return ResultSuccess;
+}
+
+Result NfpDevice::StopDetection() {
+ npad_device->SetPollingMode(Common::Input::PollingMode::Active);
+
+ if (device_state == DeviceState::Initialized) {
+ return ResultSuccess;
+ }
+
+ if (device_state == DeviceState::TagFound || device_state == DeviceState::TagMounted) {
+ CloseAmiibo();
+ return ResultSuccess;
+ }
+ if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) {
+ device_state = DeviceState::Initialized;
+ return ResultSuccess;
+ }
+
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ return WrongDeviceState;
+}
+
+Result NfpDevice::Flush() {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+ return WrongDeviceState;
+ }
+
+ 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;
+ settings.crc_counter++;
+ // TODO: Find how to calculate the crc check
+ // settings.crc = CalculateCRC(settings);
+ }
+
+ tag_data.write_counter++;
+
+ if (!AmiiboCrypto::EncodeAmiibo(tag_data, encrypted_tag_data)) {
+ LOG_ERROR(Service_NFP, "Failed to encode data");
+ return WriteAmiiboFailed;
+ }
+
+ std::vector<u8> data(sizeof(encrypted_tag_data));
+ memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data));
+
+ if (!npad_device->WriteNfc(data)) {
+ LOG_ERROR(Service_NFP, "Error writing to file");
+ return WriteAmiiboFailed;
+ }
+
+ is_data_moddified = false;
+
+ return ResultSuccess;
+}
+
+Result NfpDevice::Mount(MountTarget mount_target_) {
+ if (device_state != DeviceState::TagFound) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ return WrongDeviceState;
+ }
+
+ if (!AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) {
+ LOG_ERROR(Service_NFP, "Not an amiibo");
+ return NotAnAmiibo;
+ }
+
+ if (!AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) {
+ LOG_ERROR(Service_NFP, "Can't decode amiibo {}", device_state);
+ return CorruptedData;
+ }
+
+ device_state = DeviceState::TagMounted;
+ mount_target = mount_target_;
+ return ResultSuccess;
+}
+
+Result NfpDevice::Unmount() {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ // Save data before unloading the amiibo
+ if (is_data_moddified) {
+ Flush();
+ }
+
+ device_state = DeviceState::TagFound;
+ mount_target = MountTarget::None;
+ is_app_area_open = false;
+
+ return ResultSuccess;
+}
+
+Result NfpDevice::GetTagInfo(TagInfo& tag_info) const {
+ if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ tag_info = {
+ .uuid = encrypted_tag_data.uuid.uid,
+ .uuid_length = static_cast<u8>(encrypted_tag_data.uuid.uid.size()),
+ .protocol = TagProtocol::TypeA,
+ .tag_type = TagType::Type2,
+ };
+
+ return ResultSuccess;
+}
+
+Result NfpDevice::GetCommonInfo(CommonInfo& common_info) const {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+ return WrongDeviceState;
+ }
+
+ 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 = 0,
+ .application_area_size = sizeof(ApplicationArea),
+ };
+ return ResultSuccess;
+}
+
+Result NfpDevice::GetModelInfo(ModelInfo& model_info) const {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ 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 NfpDevice::GetRegisterInfo(RegisterInfo& register_info) const {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+ return WrongDeviceState;
+ }
+
+ if (tag_data.settings.settings.amiibo_initialized == 0) {
+ return RegistrationIsNotInitialized;
+ }
+
+ 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 = {},
+ };
+
+ return ResultSuccess;
+}
+
+Result NfpDevice::SetNicknameAndOwner(const AmiiboName& amiibo_name) {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+ return WrongDeviceState;
+ }
+
+ Service::Mii::MiiManager manager;
+ auto& settings = tag_data.settings;
+
+ settings.init_date = GetAmiiboDate(current_posix_time);
+ settings.write_date = GetAmiiboDate(current_posix_time);
+ settings.crc_counter++;
+ // TODO: Find how to calculate the crc check
+ // settings.crc = CalculateCRC(settings);
+
+ SetAmiiboName(settings, amiibo_name);
+ tag_data.owner_mii = manager.ConvertCharInfoToV3(manager.BuildDefault(0));
+ settings.settings.amiibo_initialized.Assign(1);
+
+ return Flush();
+}
+
+Result NfpDevice::RestoreAmiibo() {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+ return WrongDeviceState;
+ }
+
+ // TODO: Load amiibo from backup on system
+ LOG_ERROR(Service_NFP, "Not Implemented");
+ return ResultSuccess;
+}
+
+Result NfpDevice::DeleteAllData() {
+ const auto result = DeleteApplicationArea();
+ if (result.IsError()) {
+ return result;
+ }
+
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ Common::TinyMT rng{};
+ rng.GenerateRandomBytes(&tag_data.owner_mii, sizeof(tag_data.owner_mii));
+ tag_data.settings.settings.amiibo_initialized.Assign(0);
+
+ return Flush();
+}
+
+Result NfpDevice::OpenApplicationArea(u32 access_id) {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+ return WrongDeviceState;
+ }
+
+ if (tag_data.settings.settings.appdata_initialized.Value() == 0) {
+ LOG_WARNING(Service_NFP, "Application area is not initialized");
+ return ApplicationAreaIsNotInitialized;
+ }
+
+ if (tag_data.application_area_id != access_id) {
+ LOG_WARNING(Service_NFP, "Wrong application area id");
+ return WrongApplicationAreaId;
+ }
+
+ is_app_area_open = true;
+
+ return ResultSuccess;
+}
+
+Result NfpDevice::GetApplicationArea(std::vector<u8>& data) const {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+ return WrongDeviceState;
+ }
+
+ if (!is_app_area_open) {
+ LOG_ERROR(Service_NFP, "Application area is not open");
+ return WrongDeviceState;
+ }
+
+ if (tag_data.settings.settings.appdata_initialized.Value() == 0) {
+ LOG_ERROR(Service_NFP, "Application area is not initialized");
+ return ApplicationAreaIsNotInitialized;
+ }
+
+ if (data.size() > sizeof(ApplicationArea)) {
+ data.resize(sizeof(ApplicationArea));
+ }
+
+ memcpy(data.data(), tag_data.application_area.data(), data.size());
+
+ return ResultSuccess;
+}
+
+Result NfpDevice::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 TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+ return WrongDeviceState;
+ }
+
+ if (!is_app_area_open) {
+ LOG_ERROR(Service_NFP, "Application area is not open");
+ return WrongDeviceState;
+ }
+
+ if (tag_data.settings.settings.appdata_initialized.Value() == 0) {
+ LOG_ERROR(Service_NFP, "Application area is not initialized");
+ return ApplicationAreaIsNotInitialized;
+ }
+
+ if (data.size() > sizeof(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(ApplicationArea) - data.size());
+
+ tag_data.applicaton_write_counter++;
+ is_data_moddified = true;
+
+ return ResultSuccess;
+}
+
+Result NfpDevice::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 TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ if (tag_data.settings.settings.appdata_initialized.Value() != 0) {
+ LOG_ERROR(Service_NFP, "Application area already exist");
+ return ApplicationAreaExist;
+ }
+
+ return RecreateApplicationArea(access_id, data);
+}
+
+Result NfpDevice::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 TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+ return WrongDeviceState;
+ }
+
+ if (data.size() > sizeof(ApplicationArea)) {
+ LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
+ return WrongApplicationAreaSize;
+ }
+
+ 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(ApplicationArea) - data.size());
+
+ // TODO: Investigate why the title id needs to be moddified
+ tag_data.title_id = system.GetCurrentProcessProgramID();
+ tag_data.title_id = tag_data.title_id | 0x30000000ULL;
+ tag_data.settings.settings.appdata_initialized.Assign(1);
+ tag_data.application_area_id = access_id;
+ tag_data.applicaton_write_counter++;
+ tag_data.unknown = {};
+
+ return Flush();
+}
+
+Result NfpDevice::DeleteApplicationArea() {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return TagRemoved;
+ }
+ return WrongDeviceState;
+ }
+
+ if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+ return WrongDeviceState;
+ }
+
+ Common::TinyMT rng{};
+ rng.GenerateRandomBytes(tag_data.application_area.data(), sizeof(ApplicationArea));
+ rng.GenerateRandomBytes(&tag_data.title_id, sizeof(u64));
+ rng.GenerateRandomBytes(&tag_data.application_area_id, sizeof(u32));
+ tag_data.settings.settings.appdata_initialized.Assign(0);
+ tag_data.applicaton_write_counter++;
+ tag_data.unknown = {};
+
+ return Flush();
+}
+
+u64 NfpDevice::GetHandle() const {
+ // Generate a handle based of the npad id
+ return static_cast<u64>(npad_id);
+}
+
+u32 NfpDevice::GetApplicationAreaSize() const {
+ return sizeof(ApplicationArea);
+}
+
+DeviceState NfpDevice::GetCurrentState() const {
+ return device_state;
+}
+
+Core::HID::NpadIdType NfpDevice::GetNpadId() const {
+ return npad_id;
+}
+
+AmiiboName NfpDevice::GetAmiiboName(const AmiiboSettings& settings) const {
+ std::array<char16_t, amiibo_name_length> settings_amiibo_name{};
+ AmiiboName amiibo_name{};
+
+ // Convert from big endian to little endian
+ for (std::size_t i = 0; i < 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 NfpDevice::SetAmiiboName(AmiiboSettings& settings, const AmiiboName& amiibo_name) {
+ std::array<char16_t, 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 < amiibo_name_length; i++) {
+ settings.amiibo_name[i] = static_cast<u16_be>(settings_amiibo_name[i]);
+ }
+}
+
+AmiiboDate NfpDevice::GetAmiiboDate(s64 posix_time) const {
+ const auto& time_zone_manager =
+ system.GetTimeManager().GetTimeZoneContentManager().GetTimeZoneManager();
+ Time::TimeZone::CalendarInfo calendar_info{};
+ 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;
+}
+
+} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp_device.h b/src/core/hle/service/nfp/nfp_device.h
new file mode 100644
index 000000000..a5b72cf19
--- /dev/null
+++ b/src/core/hle/service/nfp/nfp_device.h
@@ -0,0 +1,101 @@
+// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <array>
+#include <vector>
+
+#include "common/common_funcs.h"
+#include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/mii/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::NFP {
+class NfpDevice {
+public:
+ NfpDevice(Core::HID::NpadIdType npad_id_, Core::System& system_,
+ KernelHelpers::ServiceContext& service_context_,
+ Kernel::KEvent* availability_change_event_);
+ ~NfpDevice();
+
+ void Initialize();
+ void Finalize();
+
+ Result StartDetection(s32 protocol_);
+ Result StopDetection();
+ Result Mount(MountTarget mount_target);
+ Result Unmount();
+ Result Flush();
+
+ Result GetTagInfo(TagInfo& tag_info) const;
+ Result GetCommonInfo(CommonInfo& common_info) const;
+ Result GetModelInfo(ModelInfo& model_info) const;
+ Result GetRegisterInfo(RegisterInfo& register_info) const;
+
+ Result SetNicknameAndOwner(const AmiiboName& amiibo_name);
+ Result RestoreAmiibo();
+ Result DeleteAllData();
+
+ Result OpenApplicationArea(u32 access_id);
+ Result GetApplicationArea(std::vector<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();
+
+ u64 GetHandle() const;
+ u32 GetApplicationAreaSize() const;
+ DeviceState GetCurrentState() const;
+ Core::HID::NpadIdType GetNpadId() const;
+
+ Kernel::KReadableEvent& GetActivateEvent() const;
+ Kernel::KReadableEvent& GetDeactivateEvent() const;
+
+private:
+ void NpadUpdate(Core::HID::ControllerTriggerType type);
+ bool LoadAmiibo(std::span<const u8> data);
+ void CloseAmiibo();
+
+ AmiiboName GetAmiiboName(const AmiiboSettings& settings) const;
+ void SetAmiiboName(AmiiboSettings& settings, const AmiiboName& amiibo_name);
+ AmiiboDate GetAmiiboDate(s64 posix_time) 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_data_moddified{};
+ bool is_app_area_open{};
+ s32 protocol{};
+ s64 current_posix_time{};
+ MountTarget mount_target{MountTarget::None};
+ DeviceState device_state{DeviceState::Unavailable};
+
+ NTAG215File tag_data{};
+ EncryptedNTAG215File encrypted_tag_data{};
+};
+
+} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp_result.h b/src/core/hle/service/nfp/nfp_result.h
new file mode 100644
index 000000000..d8e4cf094
--- /dev/null
+++ b/src/core/hle/service/nfp/nfp_result.h
@@ -0,0 +1,24 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "core/hle/result.h"
+
+namespace Service::NFP {
+
+constexpr Result DeviceNotFound(ErrorModule::NFP, 64);
+constexpr Result InvalidArgument(ErrorModule::NFP, 65);
+constexpr Result WrongApplicationAreaSize(ErrorModule::NFP, 68);
+constexpr Result WrongDeviceState(ErrorModule::NFP, 73);
+constexpr Result NfcDisabled(ErrorModule::NFP, 80);
+constexpr Result WriteAmiiboFailed(ErrorModule::NFP, 88);
+constexpr Result TagRemoved(ErrorModule::NFP, 97);
+constexpr Result RegistrationIsNotInitialized(ErrorModule::NFP, 120);
+constexpr Result ApplicationAreaIsNotInitialized(ErrorModule::NFP, 128);
+constexpr Result CorruptedData(ErrorModule::NFP, 144);
+constexpr Result WrongApplicationAreaId(ErrorModule::NFP, 152);
+constexpr Result ApplicationAreaExist(ErrorModule::NFP, 168);
+constexpr Result NotAnAmiibo(ErrorModule::NFP, 178);
+
+} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp_types.h b/src/core/hle/service/nfp/nfp_types.h
new file mode 100644
index 000000000..c09f9ddb6
--- /dev/null
+++ b/src/core/hle/service/nfp/nfp_types.h
@@ -0,0 +1,320 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include "common/swap.h"
+#include "core/hle/service/mii/types.h"
+
+namespace Service::NFP {
+static constexpr std::size_t amiibo_name_length = 0xA;
+
+enum class ServiceType : u32 {
+ User,
+ Debug,
+ System,
+};
+
+enum class State : u32 {
+ NonInitialized,
+ Initialized,
+};
+
+enum class DeviceState : u32 {
+ Initialized,
+ SearchingForTag,
+ TagFound,
+ TagRemoved,
+ TagMounted,
+ Unavailable,
+ Finalized,
+};
+
+enum class ModelType : u32 {
+ Amiibo,
+};
+
+enum class MountTarget : u32 {
+ None,
+ Rom,
+ Ram,
+ All,
+};
+
+enum class AmiiboType : u8 {
+ Figure,
+ Card,
+ Yarn,
+};
+
+enum class AmiiboSeries : u8 {
+ SuperSmashBros,
+ SuperMario,
+ ChibiRobo,
+ YoshiWoollyWorld,
+ Splatoon,
+ AnimalCrossing,
+ EightBitMario,
+ Skylanders,
+ Unknown8,
+ TheLegendOfZelda,
+ ShovelKnight,
+ Unknown11,
+ Kiby,
+ Pokemon,
+ MarioSportsSuperstars,
+ MonsterHunter,
+ BoxBoy,
+ Pikmin,
+ FireEmblem,
+ Metroid,
+ Others,
+ MegaMan,
+ Diablo,
+};
+
+enum class TagType : u32 {
+ None,
+ Type1, // ISO14443A RW 96-2k bytes 106kbit/s
+ Type2, // ISO14443A RW/RO 540 bytes 106kbit/s
+ Type3, // Sony Felica RW/RO 2k bytes 212kbit/s
+ Type4, // ISO14443A RW/RO 4k-32k bytes 424kbit/s
+ Type5, // ISO15693 RW/RO 540 bytes 106kbit/s
+};
+
+enum class PackedTagType : u8 {
+ None,
+ Type1, // ISO14443A RW 96-2k bytes 106kbit/s
+ Type2, // ISO14443A RW/RO 540 bytes 106kbit/s
+ Type3, // Sony Felica RW/RO 2k bytes 212kbit/s
+ Type4, // ISO14443A RW/RO 4k-32k bytes 424kbit/s
+ Type5, // ISO15693 RW/RO 540 bytes 106kbit/s
+};
+
+enum class TagProtocol : u32 {
+ None,
+ TypeA, // ISO14443A
+ TypeB, // ISO14443B
+ TypeF, // Sony Felica
+};
+
+using UniqueSerialNumber = std::array<u8, 7>;
+using LockBytes = std::array<u8, 2>;
+using HashData = std::array<u8, 0x20>;
+using ApplicationArea = std::array<u8, 0xD8>;
+using AmiiboName = std::array<char, (amiibo_name_length * 4) + 1>;
+
+struct TagUuid {
+ UniqueSerialNumber uid;
+ u8 nintendo_id;
+ LockBytes lock_bytes;
+};
+static_assert(sizeof(TagUuid) == 10, "TagUuid is an invalid size");
+
+struct WriteDate {
+ u16 year;
+ u8 month;
+ u8 day;
+};
+static_assert(sizeof(WriteDate) == 0x4, "WriteDate is an invalid size");
+
+struct AmiiboDate {
+ u16 raw_date{};
+
+ u16 GetValue() const {
+ return Common::swap16(raw_date);
+ }
+
+ u16 GetYear() const {
+ return static_cast<u16>(((GetValue() & 0xFE00) >> 9) + 2000);
+ }
+ u8 GetMonth() const {
+ return static_cast<u8>((GetValue() & 0x01E0) >> 5);
+ }
+ u8 GetDay() const {
+ return static_cast<u8>(GetValue() & 0x001F);
+ }
+
+ WriteDate GetWriteDate() const {
+ if (!IsValidDate()) {
+ return {
+ .year = 2000,
+ .month = 1,
+ .day = 1,
+ };
+ }
+ return {
+ .year = GetYear(),
+ .month = GetMonth(),
+ .day = GetDay(),
+ };
+ }
+
+ void SetYear(u16 year) {
+ const u16 year_converted = static_cast<u16>((year - 2000) << 9);
+ raw_date = Common::swap16((GetValue() & ~0xFE00) | year_converted);
+ }
+ void SetMonth(u8 month) {
+ const u16 month_converted = static_cast<u16>(month << 5);
+ raw_date = Common::swap16((GetValue() & ~0x01E0) | month_converted);
+ }
+ void SetDay(u8 day) {
+ const u16 day_converted = static_cast<u16>(day);
+ raw_date = Common::swap16((GetValue() & ~0x001F) | day_converted);
+ }
+
+ bool IsValidDate() const {
+ const bool is_day_valid = GetDay() > 0 && GetDay() < 32;
+ const bool is_month_valid = GetMonth() > 0 && GetMonth() < 13;
+ const bool is_year_valid = GetYear() >= 2000;
+ return is_year_valid && is_month_valid && is_day_valid;
+ }
+};
+static_assert(sizeof(AmiiboDate) == 2, "AmiiboDate is an invalid size");
+
+struct Settings {
+ union {
+ u8 raw{};
+
+ BitField<4, 1, u8> amiibo_initialized;
+ BitField<5, 1, u8> appdata_initialized;
+ };
+};
+static_assert(sizeof(Settings) == 1, "AmiiboDate is an invalid size");
+
+struct AmiiboSettings {
+ Settings settings;
+ u8 country_code_id;
+ u16_be crc_counter; // Incremented each time crc is changed
+ AmiiboDate init_date;
+ AmiiboDate write_date;
+ u32_be crc;
+ std::array<u16_be, amiibo_name_length> amiibo_name; // UTF-16 text
+};
+static_assert(sizeof(AmiiboSettings) == 0x20, "AmiiboSettings is an invalid size");
+
+struct AmiiboModelInfo {
+ u16 character_id;
+ u8 character_variant;
+ AmiiboType amiibo_type;
+ u16_be model_number;
+ AmiiboSeries series;
+ PackedTagType tag_type;
+ INSERT_PADDING_BYTES(0x4); // Unknown
+};
+static_assert(sizeof(AmiiboModelInfo) == 0xC, "AmiiboModelInfo is an invalid size");
+
+struct NTAG215Password {
+ u32 PWD; // Password to allow write access
+ u16 PACK; // Password acknowledge reply
+ u16 RFUI; // Reserved for future use
+};
+static_assert(sizeof(NTAG215Password) == 0x8, "NTAG215Password is an invalid size");
+
+#pragma pack(1)
+struct EncryptedAmiiboFile {
+ u8 constant_value; // Must be A5
+ u16_be write_counter; // Number of times the amiibo has been written?
+ INSERT_PADDING_BYTES(0x1); // Unknown 1
+ AmiiboSettings settings; // Encrypted amiibo settings
+ HashData hmac_tag; // Hash
+ AmiiboModelInfo model_info; // Encrypted amiibo model info
+ HashData keygen_salt; // Salt
+ HashData hmac_data; // Hash
+ Service::Mii::Ver3StoreData owner_mii; // Encrypted Mii data
+ u64_be title_id; // Encrypted Game id
+ u16_be applicaton_write_counter; // Encrypted Counter
+ u32_be application_area_id; // Encrypted Game id
+ std::array<u8, 0x2> unknown;
+ std::array<u32, 0x8> unknown2;
+ ApplicationArea application_area; // Encrypted Game data
+};
+static_assert(sizeof(EncryptedAmiiboFile) == 0x1F8, "AmiiboFile is an invalid size");
+
+struct NTAG215File {
+ LockBytes lock_bytes; // Tag UUID
+ u16 static_lock; // Set defined pages as read only
+ u32 compability_container; // Defines available memory
+ HashData hmac_data; // Hash
+ u8 constant_value; // Must be A5
+ u16_be write_counter; // Number of times the amiibo has been written?
+ INSERT_PADDING_BYTES(0x1); // Unknown 1
+ AmiiboSettings settings;
+ Service::Mii::Ver3StoreData owner_mii; // Encrypted Mii data
+ u64_be title_id;
+ u16_be applicaton_write_counter; // Encrypted Counter
+ u32_be application_area_id;
+ std::array<u8, 0x2> unknown;
+ std::array<u32, 0x8> unknown2;
+ ApplicationArea application_area; // Encrypted Game data
+ HashData hmac_tag; // Hash
+ UniqueSerialNumber uid; // Unique serial number
+ u8 nintendo_id; // Tag UUID
+ AmiiboModelInfo model_info;
+ HashData keygen_salt; // Salt
+ u32 dynamic_lock; // Dynamic lock
+ u32 CFG0; // Defines memory protected by password
+ u32 CFG1; // Defines number of verification attempts
+ NTAG215Password password; // Password data
+};
+static_assert(sizeof(NTAG215File) == 0x21C, "NTAG215File is an invalid size");
+static_assert(std::is_trivially_copyable_v<NTAG215File>, "NTAG215File must be trivially copyable.");
+#pragma pack()
+
+struct EncryptedNTAG215File {
+ TagUuid uuid; // Unique serial number
+ u16 static_lock; // Set defined pages as read only
+ u32 compability_container; // Defines available memory
+ EncryptedAmiiboFile user_memory; // Writable data
+ u32 dynamic_lock; // Dynamic lock
+ u32 CFG0; // Defines memory protected by password
+ u32 CFG1; // Defines number of verification attempts
+ NTAG215Password password; // Password data
+};
+static_assert(sizeof(EncryptedNTAG215File) == 0x21C, "EncryptedNTAG215File is an invalid size");
+static_assert(std::is_trivially_copyable_v<EncryptedNTAG215File>,
+ "EncryptedNTAG215File must be trivially copyable.");
+
+struct TagInfo {
+ UniqueSerialNumber uuid;
+ INSERT_PADDING_BYTES(0x3);
+ u8 uuid_length;
+ INSERT_PADDING_BYTES(0x15);
+ TagProtocol protocol;
+ TagType tag_type;
+ INSERT_PADDING_BYTES(0x30);
+};
+static_assert(sizeof(TagInfo) == 0x58, "TagInfo is an invalid size");
+
+struct CommonInfo {
+ WriteDate last_write_date;
+ u16 write_counter;
+ u8 version;
+ INSERT_PADDING_BYTES(0x1);
+ u32 application_area_size;
+ INSERT_PADDING_BYTES(0x34);
+};
+static_assert(sizeof(CommonInfo) == 0x40, "CommonInfo is an invalid size");
+
+struct ModelInfo {
+ u16 character_id;
+ u8 character_variant;
+ AmiiboType amiibo_type;
+ u16 model_number;
+ AmiiboSeries series;
+ INSERT_PADDING_BYTES(0x39); // Unknown
+};
+static_assert(sizeof(ModelInfo) == 0x40, "ModelInfo is an invalid size");
+
+struct RegisterInfo {
+ Service::Mii::CharInfo mii_char_info;
+ WriteDate creation_date;
+ AmiiboName amiibo_name;
+ u8 font_region;
+ INSERT_PADDING_BYTES(0x7A);
+};
+static_assert(sizeof(RegisterInfo) == 0x100, "RegisterInfo is an invalid size");
+
+} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp_user.cpp b/src/core/hle/service/nfp/nfp_user.cpp
index 2d7b156cf..4ed53b534 100644
--- a/src/core/hle/service/nfp/nfp_user.cpp
+++ b/src/core/hle/service/nfp/nfp_user.cpp
@@ -1,18 +1,674 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include <array>
+#include <atomic>
+
+#include "common/logging/log.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/ipc_helpers.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/service/mii/mii_manager.h"
+#include "core/hle/service/nfp/nfp_device.h"
+#include "core/hle/service/nfp/nfp_result.h"
#include "core/hle/service/nfp/nfp_user.h"
namespace Service::NFP {
-NFP_User::NFP_User(std::shared_ptr<Module> module_, Core::System& system_)
- : Interface(std::move(module_), system_, "nfp:user") {
+IUser::IUser(Core::System& system_)
+ : ServiceFramework{system_, "NFP::IUser"}, service_context{system_, service_name} {
static const FunctionInfo functions[] = {
- {0, &NFP_User::CreateUserInterface, "CreateUserInterface"},
+ {0, &IUser::Initialize, "Initialize"},
+ {1, &IUser::Finalize, "Finalize"},
+ {2, &IUser::ListDevices, "ListDevices"},
+ {3, &IUser::StartDetection, "StartDetection"},
+ {4, &IUser::StopDetection, "StopDetection"},
+ {5, &IUser::Mount, "Mount"},
+ {6, &IUser::Unmount, "Unmount"},
+ {7, &IUser::OpenApplicationArea, "OpenApplicationArea"},
+ {8, &IUser::GetApplicationArea, "GetApplicationArea"},
+ {9, &IUser::SetApplicationArea, "SetApplicationArea"},
+ {10, &IUser::Flush, "Flush"},
+ {11, &IUser::Restore, "Restore"},
+ {12, &IUser::CreateApplicationArea, "CreateApplicationArea"},
+ {13, &IUser::GetTagInfo, "GetTagInfo"},
+ {14, &IUser::GetRegisterInfo, "GetRegisterInfo"},
+ {15, &IUser::GetCommonInfo, "GetCommonInfo"},
+ {16, &IUser::GetModelInfo, "GetModelInfo"},
+ {17, &IUser::AttachActivateEvent, "AttachActivateEvent"},
+ {18, &IUser::AttachDeactivateEvent, "AttachDeactivateEvent"},
+ {19, &IUser::GetState, "GetState"},
+ {20, &IUser::GetDeviceState, "GetDeviceState"},
+ {21, &IUser::GetNpadId, "GetNpadId"},
+ {22, &IUser::GetApplicationAreaSize, "GetApplicationAreaSize"},
+ {23, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
+ {24, &IUser::RecreateApplicationArea, "RecreateApplicationArea"},
};
RegisterHandlers(functions);
+
+ availability_change_event = service_context.CreateEvent("IUser:AvailabilityChangeEvent");
+
+ for (u32 device_index = 0; device_index < 10; device_index++) {
+ devices[device_index] =
+ std::make_shared<NfpDevice>(Core::HID::IndexToNpadIdType(device_index), system,
+ service_context, availability_change_event);
+ }
+}
+
+void IUser::Initialize(Kernel::HLERequestContext& ctx) {
+ LOG_INFO(Service_NFC, "called");
+
+ state = State::Initialized;
+
+ for (auto& device : devices) {
+ device->Initialize();
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2, 0};
+ rb.Push(ResultSuccess);
+}
+
+void IUser::Finalize(Kernel::HLERequestContext& ctx) {
+ LOG_INFO(Service_NFP, "called");
+
+ state = State::NonInitialized;
+
+ for (auto& device : devices) {
+ device->Finalize();
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void IUser::ListDevices(Kernel::HLERequestContext& ctx) {
+ LOG_INFO(Service_NFP, "called");
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ if (!ctx.CanWriteBuffer()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(InvalidArgument);
+ return;
+ }
+
+ if (ctx.GetWriteBufferSize() == 0) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(InvalidArgument);
+ return;
+ }
+
+ std::vector<u64> nfp_devices;
+ const std::size_t max_allowed_devices = ctx.GetWriteBufferSize() / sizeof(u64);
+
+ 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.size() == 0) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ ctx.WriteBuffer(nfp_devices);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(static_cast<s32>(nfp_devices.size()));
+}
+
+void IUser::StartDetection(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ const auto nfp_protocol{rp.Pop<s32>()};
+ LOG_INFO(Service_NFP, "called, device_handle={}, nfp_protocol={}", device_handle, nfp_protocol);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ const auto result = device.value()->StartDetection(nfp_protocol);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::StopDetection(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ const auto result = device.value()->StopDetection();
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::Mount(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ const auto model_type{rp.PopEnum<ModelType>()};
+ const auto mount_target{rp.PopEnum<MountTarget>()};
+ LOG_INFO(Service_NFP, "called, device_handle={}, model_type={}, mount_target={}", device_handle,
+ model_type, mount_target);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ const auto result = device.value()->Mount(mount_target);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::Unmount(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ const auto result = device.value()->Unmount();
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::OpenApplicationArea(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ const auto access_id{rp.Pop<u32>()};
+ LOG_INFO(Service_NFP, "called, device_handle={}, access_id={}", device_handle, access_id);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ const auto result = device.value()->OpenApplicationArea(access_id);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::GetApplicationArea(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ const auto data_size = ctx.GetWriteBufferSize();
+ LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ if (!ctx.CanWriteBuffer()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(InvalidArgument);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ std::vector<u8> data(data_size);
+ const auto result = device.value()->GetApplicationArea(data);
+ ctx.WriteBuffer(data);
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(result);
+ rb.Push(static_cast<u32>(data_size));
+}
+
+void IUser::SetApplicationArea(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ const auto data{ctx.ReadBuffer()};
+ LOG_INFO(Service_NFP, "called, device_handle={}, data_size={}", device_handle, data.size());
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ if (!ctx.CanReadBuffer()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(InvalidArgument);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ const auto result = device.value()->SetApplicationArea(data);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::Flush(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ const auto result = device.value()->Flush();
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::Restore(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ const auto result = device.value()->RestoreAmiibo();
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::CreateApplicationArea(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ const auto access_id{rp.Pop<u32>()};
+ const auto data{ctx.ReadBuffer()};
+ LOG_INFO(Service_NFP, "called, device_handle={}, data_size={}, access_id={}", device_handle,
+ access_id, data.size());
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ if (!ctx.CanReadBuffer()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(InvalidArgument);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ const auto result = device.value()->CreateApplicationArea(access_id, data);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::GetTagInfo(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ TagInfo tag_info{};
+ const auto result = device.value()->GetTagInfo(tag_info);
+ ctx.WriteBuffer(tag_info);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::GetRegisterInfo(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ RegisterInfo register_info{};
+ const auto result = device.value()->GetRegisterInfo(register_info);
+ ctx.WriteBuffer(register_info);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::GetCommonInfo(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ CommonInfo common_info{};
+ const auto result = device.value()->GetCommonInfo(common_info);
+ ctx.WriteBuffer(common_info);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::GetModelInfo(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ ModelInfo model_info{};
+ const auto result = device.value()->GetModelInfo(model_info);
+ ctx.WriteBuffer(model_info);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void IUser::AttachActivateEvent(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(device.value()->GetActivateEvent());
+}
+
+void IUser::AttachDeactivateEvent(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(device.value()->GetDeactivateEvent());
+}
+
+void IUser::GetState(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_NFC, "called");
+
+ IPC::ResponseBuilder rb{ctx, 3, 0};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(state);
+}
+
+void IUser::GetDeviceState(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(device.value()->GetCurrentState());
+}
+
+void IUser::GetNpadId(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(device.value()->GetNpadId());
+}
+
+void IUser::GetApplicationAreaSize(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(device.value()->GetApplicationAreaSize());
}
-NFP_User::~NFP_User() = default;
+void IUser::AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx) {
+ LOG_INFO(Service_NFP, "called");
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(availability_change_event->GetReadableEvent());
+}
+
+void IUser::RecreateApplicationArea(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ const auto access_id{rp.Pop<u32>()};
+ const auto data{ctx.ReadBuffer()};
+ LOG_INFO(Service_NFP, "called, device_handle={}, data_size={}, access_id={}", device_handle,
+ access_id, data.size());
+
+ if (state == State::NonInitialized) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(NfcDisabled);
+ return;
+ }
+
+ auto device = GetNfpDevice(device_handle);
+
+ if (!device.has_value()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(DeviceNotFound);
+ return;
+ }
+
+ const auto result = device.value()->RecreateApplicationArea(access_id, data);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+std::optional<std::shared_ptr<NfpDevice>> IUser::GetNfpDevice(u64 handle) {
+ for (auto& device : devices) {
+ if (device->GetHandle() == handle) {
+ return device;
+ }
+ }
+ return std::nullopt;
+}
} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp_user.h b/src/core/hle/service/nfp/nfp_user.h
index 519ff56ee..68c60ae82 100644
--- a/src/core/hle/service/nfp/nfp_user.h
+++ b/src/core/hle/service/nfp/nfp_user.h
@@ -3,14 +3,52 @@
#pragma once
+#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/nfp/nfp.h"
+#include "core/hle/service/nfp/nfp_types.h"
namespace Service::NFP {
+class NfpDevice;
-class NFP_User final : public Module::Interface {
+class IUser final : public ServiceFramework<IUser> {
public:
- explicit NFP_User(std::shared_ptr<Module> module_, Core::System& system_);
- ~NFP_User() override;
+ explicit IUser(Core::System& system_);
+
+private:
+ void Initialize(Kernel::HLERequestContext& ctx);
+ void Finalize(Kernel::HLERequestContext& ctx);
+ void ListDevices(Kernel::HLERequestContext& ctx);
+ void StartDetection(Kernel::HLERequestContext& ctx);
+ void StopDetection(Kernel::HLERequestContext& ctx);
+ void Mount(Kernel::HLERequestContext& ctx);
+ void Unmount(Kernel::HLERequestContext& ctx);
+ void OpenApplicationArea(Kernel::HLERequestContext& ctx);
+ void GetApplicationArea(Kernel::HLERequestContext& ctx);
+ void SetApplicationArea(Kernel::HLERequestContext& ctx);
+ void Flush(Kernel::HLERequestContext& ctx);
+ void Restore(Kernel::HLERequestContext& ctx);
+ void CreateApplicationArea(Kernel::HLERequestContext& ctx);
+ void GetTagInfo(Kernel::HLERequestContext& ctx);
+ void GetRegisterInfo(Kernel::HLERequestContext& ctx);
+ void GetCommonInfo(Kernel::HLERequestContext& ctx);
+ void GetModelInfo(Kernel::HLERequestContext& ctx);
+ void AttachActivateEvent(Kernel::HLERequestContext& ctx);
+ void AttachDeactivateEvent(Kernel::HLERequestContext& ctx);
+ void GetState(Kernel::HLERequestContext& ctx);
+ void GetDeviceState(Kernel::HLERequestContext& ctx);
+ void GetNpadId(Kernel::HLERequestContext& ctx);
+ void GetApplicationAreaSize(Kernel::HLERequestContext& ctx);
+ void AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx);
+ void RecreateApplicationArea(Kernel::HLERequestContext& ctx);
+
+ std::optional<std::shared_ptr<NfpDevice>> GetNfpDevice(u64 handle);
+
+ KernelHelpers::ServiceContext service_context;
+
+ std::array<std::shared_ptr<NfpDevice>, 10> devices{};
+
+ State state{State::NonInitialized};
+ Kernel::KEvent* availability_change_event;
};
} // namespace Service::NFP