citra-emu
/
citra
Archived
1
0
Fork 0

service: nfc: Implement amiibo encryption and appdata (#6340)

This commit is contained in:
Narr the Reg 2023-06-30 14:15:58 -06:00 committed by GitHub
parent ca2d87e5e3
commit 3d0a3c2c45
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 3016 additions and 323 deletions

View File

@ -585,7 +585,7 @@ public final class NativeLibrary {
/// Notifies that the activity is now in foreground and camera devices can now be reloaded
public static native void ReloadCameraDevices();
public static native boolean LoadAmiibo(byte[] bytes);
public static native boolean LoadAmiibo(String path);
public static native void RemoveAmiibo();

View File

@ -570,15 +570,7 @@ public final class EmulationActivity extends AppCompatActivity {
}
private void onAmiiboSelected(String selectedFile) {
boolean success = false;
try {
Uri uri = Uri.parse(selectedFile);
DocumentFile file = DocumentFile.fromSingleUri(this, uri);
byte[] bytes = FileUtil.getBytesFromFile(this, file);
success = NativeLibrary.LoadAmiibo(bytes);
} catch (IOException e) {
e.printStackTrace();
}
boolean success = NativeLibrary.LoadAmiibo(selectedFile);
if (!success) {
new MaterialAlertDialogBuilder(this)

View File

@ -569,20 +569,16 @@ void Java_org_citra_citra_1emu_NativeLibrary_ReloadCameraDevices(JNIEnv* env, jc
}
jboolean Java_org_citra_citra_1emu_NativeLibrary_LoadAmiibo(JNIEnv* env, jclass clazz,
jbyteArray bytes) {
jstring j_file) {
std::string filepath = GetJString(env, j_file);
Core::System& system{Core::System::GetInstance()};
Service::SM::ServiceManager& sm = system.ServiceManager();
auto nfc = sm.GetService<Service::NFC::Module::Interface>("nfc:u");
if (nfc == nullptr || env->GetArrayLength(bytes) != sizeof(Service::NFC::AmiiboData)) {
if (nfc == nullptr) {
return static_cast<jboolean>(false);
}
Service::NFC::AmiiboData amiibo_data{};
env->GetByteArrayRegion(bytes, 0, sizeof(Service::NFC::AmiiboData),
reinterpret_cast<jbyte*>(&amiibo_data));
nfc->LoadAmiibo(amiibo_data);
return static_cast<jboolean>(true);
return static_cast<jboolean>(nfc->LoadAmiibo(filepath));
}
void Java_org_citra_citra_1emu_NativeLibrary_RemoveAmiibo(JNIEnv* env, jclass clazz) {

View File

@ -142,7 +142,7 @@ JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_ReloadCameraDevic
jclass clazz);
JNIEXPORT jboolean Java_org_citra_citra_1emu_NativeLibrary_LoadAmiibo(JNIEnv* env, jclass clazz,
jbyteArray bytes);
jstring j_file);
JNIEXPORT void Java_org_citra_citra_1emu_NativeLibrary_RemoveAmiibo(JNIEnv* env, jclass clazz);

View File

@ -2017,6 +2017,25 @@ void GMainWindow::OnLoadAmiibo() {
return;
}
Core::System& system{Core::System::GetInstance()};
Service::SM::ServiceManager& sm = system.ServiceManager();
auto nfc = sm.GetService<Service::NFC::Module::Interface>("nfc:u");
if (nfc == nullptr) {
return;
}
if (nfc->IsTagActive()) {
QMessageBox::warning(this, tr("Error opening amiibo data file"),
tr("A tag is already in use."));
return;
}
if (!nfc->IsSearchingForAmiibos()) {
QMessageBox::warning(this, tr("Error opening amiibo data file"),
tr("Game is not looking for amiibos."));
return;
}
const QString extensions{QStringLiteral("*.bin")};
const QString file_filter = tr("Amiibo File (%1);; All Files (*.*)").arg(extensions);
const QString filename = QFileDialog::getOpenFileName(this, tr("Load Amiibo"), {}, file_filter);
@ -2035,26 +2054,12 @@ void GMainWindow::LoadAmiibo(const QString& filename) {
return;
}
QFile nfc_file{filename};
if (!nfc_file.open(QIODevice::ReadOnly)) {
QMessageBox::warning(this, tr("Error opening Amiibo data file"),
tr("Unable to open Amiibo file \"%1\" for reading.").arg(filename));
if (!nfc->LoadAmiibo(filename.toStdString())) {
QMessageBox::warning(this, tr("Error opening amiibo data file"),
tr("Unable to open amiibo file \"%1\" for reading.").arg(filename));
return;
}
Service::NFC::AmiiboData amiibo_data{};
const u64 read_size =
nfc_file.read(reinterpret_cast<char*>(&amiibo_data), sizeof(Service::NFC::AmiiboData));
if (read_size != sizeof(Service::NFC::AmiiboData)) {
QMessageBox::warning(this, tr("Error reading Amiibo data file"),
tr("Unable to fully read Amiibo data. Expected to read %1 bytes, but "
"was only able to read %2 bytes.")
.arg(sizeof(Service::NFC::AmiiboData))
.arg(read_size));
return;
}
nfc->LoadAmiibo(amiibo_data);
ui->action_Remove_Amiibo->setEnabled(true);
}

View File

@ -333,10 +333,16 @@ add_library(citra_core STATIC
hle/service/news/news_s.h
hle/service/news/news_u.cpp
hle/service/news/news_u.h
hle/service/nfc/amiibo_crypto.cpp
hle/service/nfc/amiibo_crypto.h
hle/service/nfc/nfc.cpp
hle/service/nfc/nfc.h
hle/service/nfc/nfc_device.cpp
hle/service/nfc/nfc_device.h
hle/service/nfc/nfc_m.cpp
hle/service/nfc/nfc_m.h
hle/service/nfc/nfc_results.h
hle/service/nfc/nfc_types.h
hle/service/nfc/nfc_u.cpp
hle/service/nfc/nfc_u.h
hle/service/nim/nim.cpp

View File

@ -0,0 +1,374 @@
// Copyright 2022 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
// Copyright 2017 socram8888/amiitool
// Licensed under MIT
// Refer to the license.txt file included.
#include <array>
#include <cryptopp/aes.h>
#include <cryptopp/hmac.h>
#include <cryptopp/modes.h>
#include <cryptopp/sha.h>
#include "common/file_util.h"
#include "common/logging/log.h"
#include "core/hle/service/nfc/amiibo_crypto.h"
namespace Service::NFC::AmiiboCrypto {
bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) {
const auto& amiibo_data = ntag_file.user_memory;
LOG_DEBUG(Service_NFC, "uuid_lock=0x{0:x}", ntag_file.static_lock);
LOG_DEBUG(Service_NFC, "compability_container=0x{0:x}", ntag_file.compability_container);
LOG_DEBUG(Service_NFC, "write_count={}", static_cast<u16>(amiibo_data.write_counter));
LOG_DEBUG(Service_NFC, "character_id=0x{0:x}", amiibo_data.model_info.character_id);
LOG_DEBUG(Service_NFC, "character_variant={}", amiibo_data.model_info.character_variant);
LOG_DEBUG(Service_NFC, "amiibo_type={}", amiibo_data.model_info.amiibo_type);
LOG_DEBUG(Service_NFC, "model_number=0x{0:x}",
static_cast<u16>(amiibo_data.model_info.model_number));
LOG_DEBUG(Service_NFC, "series={}", amiibo_data.model_info.series);
LOG_DEBUG(Service_NFC, "tag_type=0x{0:x}", amiibo_data.model_info.tag_type);
LOG_DEBUG(Service_NFC, "tag_dynamic_lock=0x{0:x}", ntag_file.dynamic_lock);
LOG_DEBUG(Service_NFC, "tag_CFG0=0x{0:x}", ntag_file.CFG0);
LOG_DEBUG(Service_NFC, "tag_CFG1=0x{0:x}", ntag_file.CFG1);
// Validate UUID
constexpr u8 CT = 0x88; // As defined in `ISO / IEC 14443 - 3`
if ((CT ^ ntag_file.uuid.uid[0] ^ ntag_file.uuid.uid[1] ^ ntag_file.uuid.uid[2]) !=
ntag_file.uuid.uid[3]) {
return false;
}
if ((ntag_file.uuid.uid[4] ^ ntag_file.uuid.uid[5] ^ ntag_file.uuid.uid[6] ^
ntag_file.uuid.nintendo_id) != ntag_file.uuid.lock_bytes[0]) {
return false;
}
// Check against all know constants on an amiibo binary
if (ntag_file.static_lock != 0xE00F) {
return false;
}
if (ntag_file.compability_container != 0xEEFF10F1U) {
return false;
}
if (amiibo_data.model_info.tag_type != PackedTagType::Type2) {
return false;
}
if ((ntag_file.dynamic_lock & 0xFFFFFF) != 0x0F0001U) {
return false;
}
if (ntag_file.CFG0 != 0x04000000U) {
return false;
}
if (ntag_file.CFG1 != 0x5F) {
return false;
}
return true;
}
bool IsAmiiboValid(const NTAG215File& ntag_file) {
return IsAmiiboValid(EncodedDataToNfcData(ntag_file));
}
NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) {
NTAG215File encoded_data{};
encoded_data.uid = nfc_data.uuid.uid;
encoded_data.nintendo_id = nfc_data.uuid.nintendo_id;
encoded_data.static_lock = nfc_data.static_lock;
encoded_data.compability_container = nfc_data.compability_container;
encoded_data.hmac_data = nfc_data.user_memory.hmac_data;
encoded_data.constant_value = nfc_data.user_memory.constant_value;
encoded_data.write_counter = nfc_data.user_memory.write_counter;
encoded_data.amiibo_version = nfc_data.user_memory.amiibo_version;
encoded_data.settings = nfc_data.user_memory.settings;
encoded_data.owner_mii = nfc_data.user_memory.owner_mii;
encoded_data.padding = nfc_data.user_memory.padding;
encoded_data.owner_mii_aes_ccm = nfc_data.user_memory.owner_mii_aes_ccm;
encoded_data.application_id = nfc_data.user_memory.application_id;
encoded_data.application_write_counter = nfc_data.user_memory.application_write_counter;
encoded_data.application_area_id = nfc_data.user_memory.application_area_id;
encoded_data.application_id_byte = nfc_data.user_memory.application_id_byte;
encoded_data.unknown = nfc_data.user_memory.unknown;
encoded_data.mii_extension = nfc_data.user_memory.mii_extension;
encoded_data.unknown2 = nfc_data.user_memory.unknown2;
encoded_data.register_info_crc = nfc_data.user_memory.register_info_crc;
encoded_data.application_area = nfc_data.user_memory.application_area;
encoded_data.hmac_tag = nfc_data.user_memory.hmac_tag;
encoded_data.lock_bytes = nfc_data.uuid.lock_bytes;
encoded_data.model_info = nfc_data.user_memory.model_info;
encoded_data.keygen_salt = nfc_data.user_memory.keygen_salt;
encoded_data.dynamic_lock = nfc_data.dynamic_lock;
encoded_data.CFG0 = nfc_data.CFG0;
encoded_data.CFG1 = nfc_data.CFG1;
encoded_data.password = nfc_data.password;
return encoded_data;
}
EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) {
EncryptedNTAG215File nfc_data{};
nfc_data.uuid.uid = encoded_data.uid;
nfc_data.uuid.nintendo_id = encoded_data.nintendo_id;
nfc_data.uuid.lock_bytes = encoded_data.lock_bytes;
nfc_data.static_lock = encoded_data.static_lock;
nfc_data.compability_container = encoded_data.compability_container;
nfc_data.user_memory.hmac_data = encoded_data.hmac_data;
nfc_data.user_memory.constant_value = encoded_data.constant_value;
nfc_data.user_memory.write_counter = encoded_data.write_counter;
nfc_data.user_memory.amiibo_version = encoded_data.amiibo_version;
nfc_data.user_memory.settings = encoded_data.settings;
nfc_data.user_memory.owner_mii = encoded_data.owner_mii;
nfc_data.user_memory.padding = encoded_data.padding;
nfc_data.user_memory.owner_mii_aes_ccm = encoded_data.owner_mii_aes_ccm;
nfc_data.user_memory.application_id = encoded_data.application_id;
nfc_data.user_memory.application_write_counter = encoded_data.application_write_counter;
nfc_data.user_memory.application_area_id = encoded_data.application_area_id;
nfc_data.user_memory.application_id_byte = encoded_data.application_id_byte;
nfc_data.user_memory.unknown = encoded_data.unknown;
nfc_data.user_memory.mii_extension = encoded_data.mii_extension;
nfc_data.user_memory.unknown2 = encoded_data.unknown2;
nfc_data.user_memory.register_info_crc = encoded_data.register_info_crc;
nfc_data.user_memory.application_area = encoded_data.application_area;
nfc_data.user_memory.hmac_tag = encoded_data.hmac_tag;
nfc_data.user_memory.model_info = encoded_data.model_info;
nfc_data.user_memory.keygen_salt = encoded_data.keygen_salt;
nfc_data.dynamic_lock = encoded_data.dynamic_lock;
nfc_data.CFG0 = encoded_data.CFG0;
nfc_data.CFG1 = encoded_data.CFG1;
nfc_data.password = encoded_data.password;
return nfc_data;
}
HashSeed GetSeed(const NTAG215File& data) {
HashSeed seed{
.magic = data.write_counter,
.padding = {},
.uid_1 = data.uid,
.nintendo_id_1 = data.nintendo_id,
.uid_2 = data.uid,
.nintendo_id_2 = data.nintendo_id,
.keygen_salt = data.keygen_salt,
};
return seed;
}
std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed) {
const std::size_t seed_part1_len = sizeof(key.magic_bytes) - key.magic_length;
const std::size_t string_size = key.type_string.size();
std::vector<u8> output(string_size + seed_part1_len);
// Copy whole type string
memccpy(output.data(), key.type_string.data(), '\0', string_size);
// Append (16 - magic_length) from the input seed
memcpy(output.data() + string_size, &seed, seed_part1_len);
// Append all bytes from magicBytes
output.insert(output.end(), key.magic_bytes.begin(),
key.magic_bytes.begin() + key.magic_length);
output.insert(output.end(), seed.uid_1.begin(), seed.uid_1.end());
output.emplace_back(seed.nintendo_id_1);
output.insert(output.end(), seed.uid_2.begin(), seed.uid_2.end());
output.emplace_back(seed.nintendo_id_2);
for (std::size_t i = 0; i < sizeof(seed.keygen_salt); i++) {
output.emplace_back(static_cast<u8>(seed.keygen_salt[i] ^ key.xor_pad[i]));
}
return output;
}
void CryptoInit(CryptoCtx& ctx, CryptoPP::HMAC<CryptoPP::SHA256>& hmac_ctx, const HmacKey& hmac_key,
const std::vector<u8>& seed) {
// Initialize context
ctx.used = false;
ctx.counter = 0;
ctx.buffer_size = sizeof(ctx.counter) + seed.size();
memcpy(ctx.buffer.data() + sizeof(u16), seed.data(), seed.size());
// Initialize HMAC context
hmac_ctx.SetKey(hmac_key.data(), hmac_key.size());
}
void CryptoStep(CryptoCtx& ctx, CryptoPP::HMAC<CryptoPP::SHA256>& hmac_ctx, DrgbOutput& output) {
// If used at least once, reinitialize the HMAC
if (ctx.used) {
hmac_ctx.Restart();
}
ctx.used = true;
// Store counter in big endian, and increment it
ctx.buffer[0] = static_cast<u8>(ctx.counter >> 8);
ctx.buffer[1] = static_cast<u8>(ctx.counter >> 0);
ctx.counter++;
// Do HMAC magic
hmac_ctx.CalculateDigest(
output.data(), reinterpret_cast<const unsigned char*>(ctx.buffer.data()), ctx.buffer_size);
}
DerivedKeys GenerateKey(const InternalKey& key, const NTAG215File& data) {
const auto seed = GetSeed(data);
// Generate internal seed
const std::vector<u8> internal_key = GenerateInternalKey(key, seed);
// Initialize context
CryptoCtx ctx{};
CryptoPP::HMAC<CryptoPP::SHA256> hmac_ctx;
CryptoInit(ctx, hmac_ctx, key.hmac_key, internal_key);
// Generate derived keys
DerivedKeys derived_keys{};
std::array<DrgbOutput, 2> temp{};
CryptoStep(ctx, hmac_ctx, temp[0]);
CryptoStep(ctx, hmac_ctx, temp[1]);
memcpy(&derived_keys, temp.data(), sizeof(DerivedKeys));
return derived_keys;
}
void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& out_data) {
CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption d2;
d2.SetKeyWithIV(keys.aes_key.data(), keys.aes_key.size(), keys.aes_iv.data(),
keys.aes_iv.size());
constexpr std::size_t encrypted_data_size = HMAC_TAG_START - SETTINGS_START;
d2.ProcessData(reinterpret_cast<unsigned char*>(&out_data.settings),
reinterpret_cast<const unsigned char*>(&in_data.settings), encrypted_data_size);
// Copy the rest of the data directly
out_data.uid = in_data.uid;
out_data.nintendo_id = in_data.nintendo_id;
out_data.lock_bytes = in_data.lock_bytes;
out_data.static_lock = in_data.static_lock;
out_data.compability_container = in_data.compability_container;
out_data.constant_value = in_data.constant_value;
out_data.write_counter = in_data.write_counter;
out_data.model_info = in_data.model_info;
out_data.keygen_salt = in_data.keygen_salt;
out_data.dynamic_lock = in_data.dynamic_lock;
out_data.CFG0 = in_data.CFG0;
out_data.CFG1 = in_data.CFG1;
out_data.password = in_data.password;
}
bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info) {
const auto citra_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir);
auto keys_file = FileUtil::IOFile(citra_keys_dir + "key_retail.bin", "rb");
if (!keys_file.IsOpen()) {
LOG_ERROR(Service_NFC, "No keys detected");
return false;
}
if (keys_file.ReadBytes(&unfixed_info, sizeof(InternalKey)) != sizeof(InternalKey)) {
LOG_ERROR(Service_NFC, "Failed to read unfixed_info");
return false;
}
if (keys_file.ReadBytes(&locked_secret, sizeof(InternalKey)) != sizeof(InternalKey)) {
LOG_ERROR(Service_NFC, "Failed to read locked-secret");
return false;
}
return true;
}
bool IsKeyAvailable() {
const auto citra_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir);
return FileUtil::Exists(citra_keys_dir + "key_retail.bin");
}
bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& tag_data) {
InternalKey locked_secret{};
InternalKey unfixed_info{};
if (!LoadKeys(locked_secret, unfixed_info)) {
return false;
}
// Generate keys
NTAG215File encoded_data = NfcDataToEncodedData(encrypted_tag_data);
const auto data_keys = GenerateKey(unfixed_info, encoded_data);
const auto tag_keys = GenerateKey(locked_secret, encoded_data);
// Decrypt
Cipher(data_keys, encoded_data, tag_data);
// Regenerate tag HMAC. Note: order matters, data HMAC depends on tag HMAC!
constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START;
CryptoPP::HMAC<CryptoPP::SHA256> tag_hmac(tag_keys.hmac_key.data(), sizeof(HmacKey));
tag_hmac.CalculateDigest(reinterpret_cast<unsigned char*>(&tag_data.hmac_tag),
reinterpret_cast<const unsigned char*>(&tag_data.uid), input_length);
// Regenerate data HMAC
constexpr std::size_t input_length2 = DYNAMIC_LOCK_START - WRITE_COUNTER_START;
CryptoPP::HMAC<CryptoPP::SHA256> data_hmac(data_keys.hmac_key.data(), sizeof(HmacKey));
data_hmac.CalculateDigest(reinterpret_cast<unsigned char*>(&tag_data.hmac_data),
reinterpret_cast<const unsigned char*>(&tag_data.write_counter),
input_length2);
if (tag_data.hmac_data != encrypted_tag_data.user_memory.hmac_data) {
LOG_ERROR(Service_NFC, "hmac_data doesn't match");
return false;
}
if (tag_data.hmac_tag != encrypted_tag_data.user_memory.hmac_tag) {
LOG_ERROR(Service_NFC, "hmac_tag doesn't match");
return false;
}
return true;
}
bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_tag_data) {
InternalKey locked_secret{};
InternalKey unfixed_info{};
if (!LoadKeys(locked_secret, unfixed_info)) {
return false;
}
// Generate keys
const auto data_keys = GenerateKey(unfixed_info, tag_data);
const auto tag_keys = GenerateKey(locked_secret, tag_data);
NTAG215File encoded_tag_data{};
// Generate tag HMAC
constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START;
constexpr std::size_t input_length2 = HMAC_TAG_START - WRITE_COUNTER_START;
CryptoPP::HMAC<CryptoPP::SHA256> tag_hmac(tag_keys.hmac_key.data(), sizeof(HmacKey));
tag_hmac.CalculateDigest(reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag),
reinterpret_cast<const unsigned char*>(&tag_data.uid), input_length);
// Generate data HMAC
CryptoPP::HMAC<CryptoPP::SHA256> data_hmac(data_keys.hmac_key.data(), sizeof(HmacKey));
data_hmac.Update(reinterpret_cast<const unsigned char*>(&tag_data.write_counter),
input_length2);
data_hmac.Update(reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag),
sizeof(HashData));
data_hmac.Update(reinterpret_cast<const unsigned char*>(&tag_data.uid), input_length);
data_hmac.Final(reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_data));
// Encrypt
Cipher(data_keys, tag_data, encoded_tag_data);
// Convert back to hardware
encrypted_tag_data = EncodedDataToNfcData(encoded_tag_data);
return true;
}
} // namespace Service::NFC::AmiiboCrypto

View File

@ -0,0 +1,109 @@
// Copyright 2022 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <vector>
#include "core/hle/service/nfc/nfc_types.h"
namespace CryptoPP {
class SHA256;
template <class T>
class HMAC;
} // namespace CryptoPP
namespace Service::NFC::AmiiboCrypto {
// Byte locations in Service::NFC::NTAG215File
constexpr std::size_t HMAC_DATA_START = 0x8;
constexpr std::size_t SETTINGS_START = 0x2c;
constexpr std::size_t WRITE_COUNTER_START = 0x29;
constexpr std::size_t HMAC_TAG_START = 0x1B4;
constexpr std::size_t UUID_START = 0x1D4;
constexpr std::size_t DYNAMIC_LOCK_START = 0x208;
using HmacKey = std::array<u8, 0x10>;
using DrgbOutput = std::array<u8, 0x20>;
struct HashSeed {
u16_be magic;
std::array<u8, 0xE> padding;
UniqueSerialNumber uid_1;
u8 nintendo_id_1;
UniqueSerialNumber uid_2;
u8 nintendo_id_2;
std::array<u8, 0x20> keygen_salt;
};
static_assert(sizeof(HashSeed) == 0x40, "HashSeed is an invalid size");
struct InternalKey {
HmacKey hmac_key;
std::array<char, 0xE> type_string;
u8 reserved;
u8 magic_length;
std::array<u8, 0x10> magic_bytes;
std::array<u8, 0x20> xor_pad;
};
static_assert(sizeof(InternalKey) == 0x50, "InternalKey is an invalid size");
static_assert(std::is_trivially_copyable_v<InternalKey>, "InternalKey must be trivially copyable.");
struct CryptoCtx {
std::array<char, 480> buffer;
bool used;
std::size_t buffer_size;
s16 counter;
};
struct DerivedKeys {
std::array<u8, 0x10> aes_key;
std::array<u8, 0x10> aes_iv;
std::array<u8, 0x10> hmac_key;
};
static_assert(sizeof(DerivedKeys) == 0x30, "DerivedKeys is an invalid size");
/// Validates that the amiibo file is not corrupted
bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file);
/// Validates that the amiibo file is not corrupted
bool IsAmiiboValid(const NTAG215File& ntag_file);
/// Converts from encrypted file format to encoded file format
NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data);
/// Converts from encoded file format to encrypted file format
EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data);
// Generates Seed needed for key derivation
HashSeed GetSeed(const NTAG215File& data);
// Middle step on the generation of derived keys
std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed);
// Initializes mbedtls context
void CryptoInit(CryptoCtx& ctx, CryptoPP::HMAC<CryptoPP::SHA256>& hmac_ctx, const HmacKey& hmac_key,
const std::vector<u8>& seed);
// Feeds data to mbedtls context to generate the derived key
void CryptoStep(CryptoCtx& ctx, CryptoPP::HMAC<CryptoPP::SHA256>& hmac_ctx, DrgbOutput& output);
// Generates the derived key from amiibo data
DerivedKeys GenerateKey(const InternalKey& key, const NTAG215File& data);
// Encodes or decodes amiibo data
void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& out_data);
/// Loads both amiibo keys from key_retail.bin
bool LoadKeys(InternalKey& locked_secret, InternalKey& unfixed_info);
/// Returns true if key_retail.bin exist
bool IsKeyAvailable();
/// Decodes encripted amiibo data returns true if output is valid
bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& tag_data);
/// Encodes plain amiibo data returns true if output is valid
bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_tag_data);
} // namespace Service::NFC::AmiiboCrypto

View File

@ -18,334 +18,662 @@ namespace Service::NFC {
template <class Archive>
void Module::serialize(Archive& ar, const unsigned int) {
ar& tag_in_range_event;
ar& tag_out_of_range_event;
ar& nfc_tag_state;
ar& nfc_status;
ar& amiibo_data;
ar& amiibo_in_range;
ar& nfc_mode;
ar& device;
}
SERIALIZE_IMPL(Module)
struct TagInfo {
u16_le id_offset_size;
u8 unk1;
u8 unk2;
std::array<u8, 7> uuid;
INSERT_PADDING_BYTES(0x20);
};
static_assert(sizeof(TagInfo) == 0x2C, "TagInfo is an invalid size");
struct AmiiboConfig {
u16_le lastwritedate_year;
u8 lastwritedate_month;
u8 lastwritedate_day;
u16_le write_counter;
std::array<u8, 3> characterID;
u8 series;
u16_le amiiboID;
u8 type;
u8 pagex4_byte3;
u16_le appdata_size;
INSERT_PADDING_BYTES(0x30);
};
static_assert(sizeof(AmiiboConfig) == 0x40, "AmiiboConfig is an invalid size");
struct IdentificationBlockReply {
u16_le char_id;
u8 char_variant;
u8 series;
u16_le model_number;
u8 figure_type;
INSERT_PADDING_BYTES(0x2F);
};
static_assert(sizeof(IdentificationBlockReply) == 0x36,
"IdentificationBlockReply is an invalid size");
void Module::Interface::Initialize(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x01, 1, 0);
u8 param = rp.Pop<u8>();
const auto communication_mode = rp.PopEnum<CommunicationMode>();
LOG_INFO(Service_NFC, "called, communication_mode={}", communication_mode);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
if (nfc->nfc_tag_state != TagState::NotInitialized) {
LOG_ERROR(Service_NFC, "Invalid TagState {}", nfc->nfc_tag_state);
rb.Push(ResultCode(ErrCodes::CommandInvalidForState, ErrorModule::NFC,
ErrorSummary::InvalidState, ErrorLevel::Status));
if (nfc->nfc_mode != CommunicationMode::NotInitialized) {
rb.Push(ResultCommandInvalidForState);
return;
}
nfc->nfc_tag_state = TagState::NotScanning;
ResultCode result = RESULT_SUCCESS;
switch (communication_mode) {
case CommunicationMode::Ntag:
case CommunicationMode::Amiibo:
nfc->device->Initialize();
break;
case CommunicationMode::TrainTag:
LOG_ERROR(Service_NFC, "CommunicationMode {} not implemented", communication_mode);
break;
default:
result = ResultInvalidArgumentValue;
break;
}
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_NFC, "(STUBBED) called, param={}", param);
if (result.IsSuccess()) {
nfc->nfc_mode = communication_mode;
}
rb.Push(result);
}
void Module::Interface::Shutdown(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x02, 1, 0);
u8 param = rp.Pop<u8>();
const auto communication_mode = rp.PopEnum<CommunicationMode>();
nfc->nfc_tag_state = TagState::NotInitialized;
LOG_INFO(Service_NFC, "called, communication_mode={}", communication_mode);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_NFC, "(STUBBED) called, param={}", param);
if (nfc->nfc_mode != communication_mode) {
rb.Push(ResultCommandInvalidForState);
return;
}
ResultCode result = RESULT_SUCCESS;
switch (communication_mode) {
case CommunicationMode::Ntag:
case CommunicationMode::Amiibo:
nfc->device->Finalize();
break;
case CommunicationMode::TrainTag:
LOG_ERROR(Service_NFC, "CommunicationMode {} not implemented", communication_mode);
break;
default:
result = ResultInvalidArgumentValue;
break;
}
if (result.IsSuccess()) {
nfc->nfc_mode = CommunicationMode::NotInitialized;
}
rb.Push(result);
}
void Module::Interface::StartCommunication(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x03, 0, 0);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_NFC, "(STUBBED) called");
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
if (nfc->nfc_mode == CommunicationMode::TrainTag) {
LOG_ERROR(Service_NFC, "CommunicationMode {} not implemented", nfc->nfc_mode);
rb.Push(RESULT_SUCCESS);
return;
}
// TODO: call start communication instead
const auto result = nfc->device->StartCommunication();
rb.Push(result);
}
void Module::Interface::StopCommunication(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x04, 0, 0);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_NFC, "(STUBBED) called");
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
if (nfc->nfc_mode == CommunicationMode::TrainTag) {
LOG_ERROR(Service_NFC, "CommunicationMode {} not implemented", nfc->nfc_mode);
rb.Push(RESULT_SUCCESS);
return;
}
// TODO: call stop communication instead
const auto result = nfc->device->StopCommunication();
rb.Push(result);
}
void Module::Interface::StartTagScanning(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x05, 1, 0); // 0x00050040
IPC::RequestParser rp(ctx, 0x05, 1, 0);
u16 in_val = rp.Pop<u16>();
LOG_INFO(Service_NFC, "called, in_val={:04x}", in_val);
ResultCode result = RESULT_SUCCESS;
switch (nfc->nfc_mode) {
case CommunicationMode::Ntag:
case CommunicationMode::Amiibo:
// in_val probably correlates to the tag protocol to be detected
result = nfc->device->StartDetection(TagProtocol::All);
break;
default:
result = ResultInvalidArgumentValue;
break;
}
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
if (nfc->nfc_tag_state != TagState::NotScanning &&
nfc->nfc_tag_state != TagState::TagOutOfRange) {
LOG_ERROR(Service_NFC, "Invalid TagState {}", nfc->nfc_tag_state);
rb.Push(ResultCode(ErrCodes::CommandInvalidForState, ErrorModule::NFC,
ErrorSummary::InvalidState, ErrorLevel::Status));
return;
}
nfc->nfc_tag_state = TagState::Scanning;
nfc->SyncTagState();
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_NFC, "(STUBBED) called, in_val={:04x}", in_val);
}
void Module::Interface::GetTagInfo(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x11, 0, 0);
if (nfc->nfc_tag_state != TagState::TagInRange &&
nfc->nfc_tag_state != TagState::TagDataLoaded && nfc->nfc_tag_state != TagState::Unknown6) {
LOG_ERROR(Service_NFC, "Invalid TagState {}", nfc->nfc_tag_state);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultCode(ErrCodes::CommandInvalidForState, ErrorModule::NFC,
ErrorSummary::InvalidState, ErrorLevel::Status));
return;
}
TagInfo tag_info{};
tag_info.uuid = nfc->amiibo_data.uuid;
tag_info.id_offset_size = static_cast<u16>(tag_info.uuid.size());
tag_info.unk1 = 0x0;
tag_info.unk2 = 0x2;
IPC::RequestBuilder rb = rp.MakeBuilder(12, 0);
rb.Push(RESULT_SUCCESS);
rb.PushRaw<TagInfo>(tag_info);
LOG_WARNING(Service_NFC, "(STUBBED) called");
}
void Module::Interface::GetAmiiboConfig(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x18, 0, 0);
AmiiboConfig amiibo_config{};
amiibo_config.lastwritedate_year = 2017;
amiibo_config.lastwritedate_month = 10;
amiibo_config.lastwritedate_day = 10;
amiibo_config.write_counter = 0x0;
std::memcpy(amiibo_config.characterID.data(), &nfc->amiibo_data.char_id,
sizeof(nfc->amiibo_data.char_id));
amiibo_config.series = nfc->amiibo_data.series;
amiibo_config.amiiboID = nfc->amiibo_data.model_number;
amiibo_config.type = nfc->amiibo_data.figure_type;
amiibo_config.pagex4_byte3 = 0x0;
amiibo_config.appdata_size = 0xD8;
IPC::RequestBuilder rb = rp.MakeBuilder(17, 0);
rb.Push(RESULT_SUCCESS);
rb.PushRaw<AmiiboConfig>(amiibo_config);
LOG_WARNING(Service_NFC, "(STUBBED) called");
rb.Push(result);
}
void Module::Interface::StopTagScanning(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x06, 0, 0);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
if (nfc->nfc_tag_state == TagState::NotInitialized ||
nfc->nfc_tag_state == TagState::NotScanning) {
LOG_ERROR(Service_NFC, "Invalid TagState {}", nfc->nfc_tag_state);
rb.Push(ResultCode(ErrCodes::CommandInvalidForState, ErrorModule::NFC,
ErrorSummary::InvalidState, ErrorLevel::Status));
return;
LOG_INFO(Service_NFC, "called");
ResultCode result = RESULT_SUCCESS;
switch (nfc->nfc_mode) {
case CommunicationMode::Ntag:
case CommunicationMode::Amiibo:
result = nfc->device->StopDetection();
break;
case CommunicationMode::TrainTag:
LOG_ERROR(Service_NFC, "CommunicationMode {} not implemented", nfc->nfc_mode);
break;
default:
result = ResultCommandInvalidForState;
break;
}
nfc->nfc_tag_state = TagState::NotScanning;
rb.Push(RESULT_SUCCESS);
LOG_DEBUG(Service_NFC, "called");
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(result);
}
void Module::Interface::LoadAmiiboData(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x07, 0, 0);
// TODO(FearlessTobi): Add state checking when this function gets properly implemented
LOG_INFO(Service_NFC, "called");
nfc->nfc_tag_state = TagState::TagDataLoaded;
ResultCode result = RESULT_SUCCESS;
switch (nfc->nfc_mode) {
case CommunicationMode::Ntag:
result = nfc->device->Mount();
break;
case CommunicationMode::Amiibo:
result = nfc->device->MountAmiibo();
break;
default:
result = ResultCommandInvalidForState;
break;
}
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_NFC, "(STUBBED) called");
rb.Push(result);
}
void Module::Interface::ResetTagScanState(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x08, 0, 0);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
if (nfc->nfc_tag_state != TagState::TagDataLoaded && nfc->nfc_tag_state != TagState::Unknown6) {
LOG_ERROR(Service_NFC, "Invalid TagState {}", nfc->nfc_tag_state);
rb.Push(ResultCode(ErrCodes::CommandInvalidForState, ErrorModule::NFC,
ErrorSummary::InvalidState, ErrorLevel::Status));
return;
LOG_INFO(Service_NFC, "called");
ResultCode result = RESULT_SUCCESS;
switch (nfc->nfc_mode) {
case CommunicationMode::Ntag:
case CommunicationMode::Amiibo:
result = nfc->device->ResetTagScanState();
break;
default:
result = ResultCommandInvalidForState;
break;
}
nfc->nfc_tag_state = TagState::TagInRange;
nfc->SyncTagState();
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(result);
}
rb.Push(RESULT_SUCCESS);
LOG_DEBUG(Service_NFC, "called");
void Module::Interface::UpdateStoredAmiiboData(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x09, 0, 0);
LOG_INFO(Service_NFC, "called");
ResultCode result = RESULT_SUCCESS;
switch (nfc->nfc_mode) {
case CommunicationMode::Ntag:
LOG_ERROR(Service_NFC, "CommunicationMode {} not implemented", nfc->nfc_mode);
break;
case CommunicationMode::Amiibo:
result = nfc->device->Flush();
break;
default:
result = ResultCommandInvalidForState;
break;
}
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(result);
}
void Module::Interface::GetTagInRangeEvent(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x0B, 0, 0);
if (nfc->nfc_tag_state != TagState::NotScanning) {
LOG_ERROR(Service_NFC, "Invalid TagState {}", nfc->nfc_tag_state);
LOG_INFO(Service_NFC, "called");
if (nfc->nfc_mode == CommunicationMode::TrainTag) {
LOG_ERROR(Service_NFC, "CommunicationMode {} not implemented", nfc->nfc_mode);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultCode(ErrCodes::CommandInvalidForState, ErrorModule::NFC,
ErrorSummary::InvalidState, ErrorLevel::Status));
rb.Push(ResultCommandInvalidForState);
return;
}
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(nfc->tag_in_range_event);
LOG_DEBUG(Service_NFC, "called");
rb.PushCopyObjects(nfc->device->GetActivateEvent());
}
void Module::Interface::GetTagOutOfRangeEvent(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x0C, 0, 0);
if (nfc->nfc_tag_state != TagState::NotScanning) {
LOG_ERROR(Service_NFC, "Invalid TagState {}", nfc->nfc_tag_state);
LOG_INFO(Service_NFC, "called");
if (nfc->nfc_mode == CommunicationMode::TrainTag) {
LOG_ERROR(Service_NFC, "CommunicationMode {} not implemented", nfc->nfc_mode);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultCode(ErrCodes::CommandInvalidForState, ErrorModule::NFC,
ErrorSummary::InvalidState, ErrorLevel::Status));
rb.Push(ResultCommandInvalidForState);
return;
}
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(nfc->tag_out_of_range_event);
LOG_DEBUG(Service_NFC, "called");
rb.PushCopyObjects(nfc->device->GetDeactivateEvent());
}
void Module::Interface::GetTagState(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x0D, 0, 0);
DeviceState state = DeviceState::NotInitialized;
LOG_DEBUG(Service_NFC, "called");
if (nfc->nfc_mode == CommunicationMode::TrainTag) {
LOG_ERROR(Service_NFC, "CommunicationMode {} not implemented", nfc->nfc_mode);
} else {
state = nfc->device->GetCurrentState();
}
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(RESULT_SUCCESS);
rb.PushEnum(nfc->nfc_tag_state);
LOG_DEBUG(Service_NFC, "called");
rb.PushEnum(state);
}
void Module::Interface::CommunicationGetStatus(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x0F, 0, 0);
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(RESULT_SUCCESS);
rb.PushEnum(nfc->nfc_status);
LOG_DEBUG(Service_NFC, "(STUBBED) called");
}
LOG_DEBUG(Service_NFC, "called");
void Module::Interface::Unknown0x1A(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x1A, 0, 0);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
if (nfc->nfc_tag_state != TagState::TagInRange) {
LOG_ERROR(Service_NFC, "Invalid TagState {}", nfc->nfc_tag_state);
rb.Push(ResultCode(ErrCodes::CommandInvalidForState, ErrorModule::NFC,
ErrorSummary::InvalidState, ErrorLevel::Status));
if (nfc->nfc_mode == CommunicationMode::TrainTag) {
LOG_ERROR(Service_NFC, "CommunicationMode {} not implemented", nfc->nfc_mode);
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(RESULT_SUCCESS);
rb.PushEnum(CommunicationState::Idle);
return;
}
nfc->nfc_tag_state = TagState::Unknown6;
CommunicationState status{};
const auto result = nfc->device->GetCommunicationStatus(status);
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(result);
rb.PushEnum(status);
}
void Module::Interface::GetTagInfo2(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x10, 0, 0);
LOG_INFO(Service_NFC, "called");
if (nfc->nfc_mode == CommunicationMode::TrainTag) {
LOG_ERROR(Service_NFC, "CommunicationMode {} not implemented", nfc->nfc_mode);
IPC::RequestBuilder rb = rp.MakeBuilder(26, 0);
rb.Push(RESULT_SUCCESS);
rb.PushRaw<TagInfo2>({});
return;
}
TagInfo2 tag_info{};
const auto result = nfc->device->GetTagInfo2(tag_info);
IPC::RequestBuilder rb = rp.MakeBuilder(26, 0);
rb.Push(result);
rb.PushRaw<TagInfo2>(tag_info);
}
void Module::Interface::GetTagInfo(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x11, 0, 0);
LOG_INFO(Service_NFC, "called");
if (nfc->nfc_mode == CommunicationMode::TrainTag) {
LOG_ERROR(Service_NFC, "CommunicationMode {} not implemented", nfc->nfc_mode);
IPC::RequestBuilder rb = rp.MakeBuilder(12, 0);
rb.Push(RESULT_SUCCESS);
rb.PushRaw<TagInfo>({});
return;
}
TagInfo tag_info{};
const auto result = nfc->device->GetTagInfo(tag_info);
IPC::RequestBuilder rb = rp.MakeBuilder(12, 0);
rb.Push(result);
rb.PushRaw<TagInfo>(tag_info);
}
void Module::Interface::CommunicationGetResult(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x12, 0, 0);
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(RESULT_SUCCESS);
LOG_DEBUG(Service_NFC, "called");
rb.Push(0);
LOG_WARNING(Service_NFC, "(STUBBED) called");
}
void Module::Interface::OpenAppData(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x13, 1, 0);
u32 access_id = rp.Pop<u32>();
LOG_INFO(Service_NFC, "called, access_id={}", access_id);
if (nfc->nfc_mode != CommunicationMode::Amiibo) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultCommandInvalidForState);
return;
}
const auto result = nfc->device->OpenApplicationArea(access_id);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(result);
}
void Module::Interface::InitializeWriteAppData(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x14, 18, 2);
u32 access_id = rp.Pop<u32>();
[[maybe_unused]] u32 size = rp.Pop<u32>();
std::vector<u8> buffer = rp.PopStaticBuffer();
LOG_CRITICAL(Service_NFC, "called, size={}", size);
if (nfc->nfc_mode != CommunicationMode::Amiibo) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultCommandInvalidForState);
return;
}
const auto result = nfc->device->CreateApplicationArea(access_id, buffer);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(result);
}
void Module::Interface::ReadAppData(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x15, 0, 0);
LOG_INFO(Service_NFC, "called");
if (nfc->nfc_mode != CommunicationMode::Amiibo) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultCommandInvalidForState);
return;
}
std::vector<u8> buffer(sizeof(ApplicationArea));
const auto result = nfc->device->GetApplicationArea(buffer);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(result);
rb.PushStaticBuffer(buffer, 0);
}
void Module::Interface::WriteAppData(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x16, 12, 2);
[[maybe_unused]] u32 size = rp.Pop<u32>();
std::vector<u8> tag_uuid_info = rp.PopStaticBuffer();
std::vector<u8> buffer = rp.PopStaticBuffer();
LOG_CRITICAL(Service_NFC, "called, size={}", size);
if (nfc->nfc_mode != CommunicationMode::Amiibo) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultCommandInvalidForState);
return;
}
const auto result = nfc->device->SetApplicationArea(buffer);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(result);
}
void Module::Interface::GetRegisterInfo(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x17, 0, 0);
LOG_INFO(Service_NFC, "called");
if (nfc->nfc_mode != CommunicationMode::Amiibo) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultCommandInvalidForState);
return;
}
RegisterInfo settings_info{};
const auto result = nfc->device->GetRegisterInfo(settings_info);
IPC::RequestBuilder rb = rp.MakeBuilder(43, 0);
rb.Push(result);
rb.PushRaw<RegisterInfo>(settings_info);
}
void Module::Interface::GetCommonInfo(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x18, 0, 0);
LOG_INFO(Service_NFC, "called");
if (nfc->nfc_mode != CommunicationMode::Amiibo) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultCommandInvalidForState);
return;
}
CommonInfo amiibo_config{};
const auto result = nfc->device->GetCommonInfo(amiibo_config);
IPC::RequestBuilder rb = rp.MakeBuilder(17, 0);
rb.Push(result);
rb.PushRaw<CommonInfo>(amiibo_config);
}
void Module::Interface::GetAppDataInitStruct(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x19, 0, 0);
LOG_INFO(Service_NFC, "called");
if (nfc->nfc_mode != CommunicationMode::Amiibo) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultCommandInvalidForState);
return;
}
using InitialStruct = std::array<u8, 0x3c>;
InitialStruct empty{};
IPC::RequestBuilder rb = rp.MakeBuilder(16, 0);
rb.Push(RESULT_SUCCESS);
rb.PushRaw<InitialStruct>(empty);
}
void Module::Interface::LoadAmiiboPartially(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x1A, 0, 0);
LOG_INFO(Service_NFC, "called");
ResultCode result = RESULT_SUCCESS;
switch (nfc->nfc_mode) {
case CommunicationMode::Ntag:
result = nfc->device->PartiallyMount();
break;
case CommunicationMode::Amiibo:
result = nfc->device->PartiallyMountAmiibo();
break;
default:
result = ResultCommandInvalidForState;
break;
}
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(result);
}
void Module::Interface::GetIdentificationBlock(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x1B, 0, 0);
if (nfc->nfc_tag_state != TagState::TagDataLoaded && nfc->nfc_tag_state != TagState::Unknown6) {
LOG_ERROR(Service_NFC, "Invalid TagState {}", nfc->nfc_tag_state);
LOG_INFO(Service_NFC, "called");
if (nfc->nfc_mode != CommunicationMode::Amiibo) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultCode(ErrCodes::CommandInvalidForState, ErrorModule::NFC,
ErrorSummary::InvalidState, ErrorLevel::Status));
rb.Push(ResultCommandInvalidForState);
return;
}
IdentificationBlockReply identification_block_reply{};
identification_block_reply.char_id = nfc->amiibo_data.char_id;
identification_block_reply.char_variant = nfc->amiibo_data.char_variant;
identification_block_reply.series = nfc->amiibo_data.series;
identification_block_reply.model_number = nfc->amiibo_data.model_number;
identification_block_reply.figure_type = nfc->amiibo_data.figure_type;
ModelInfo model_info{};
const auto result = nfc->device->GetModelInfo(model_info);
IPC::RequestBuilder rb = rp.MakeBuilder(0x1F, 0);
rb.Push(result);
rb.PushRaw<ModelInfo>(model_info);
}
void Module::Interface::Format(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x401, 3, 2);
[[maybe_unused]] u32 unknown1 = rp.Pop<u32>();
[[maybe_unused]] u32 unknown2 = rp.Pop<u32>();
[[maybe_unused]] u32 unknown3 = rp.Pop<u32>();
[[maybe_unused]] std::vector<u8> buffer = rp.PopStaticBuffer();
const auto result = nfc->device->Format();
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(result);
LOG_WARNING(Service_NFC, "(STUBBED) called");
}
void Module::Interface::GetAdminInfo(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x402, 0, 0);
LOG_INFO(Service_NFC, "called");
if (nfc->nfc_mode != CommunicationMode::Amiibo) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultCommandInvalidForState);
return;
}
AdminInfo admin_info{};
const auto result = nfc->device->GetAdminInfo(admin_info);
IPC::RequestBuilder rb = rp.MakeBuilder(17, 0);
rb.Push(result);
rb.PushRaw<AdminInfo>(admin_info);
}
void Module::Interface::GetEmptyRegisterInfo(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x403, 0, 0);
LOG_INFO(Service_NFC, "called");
if (nfc->nfc_mode != CommunicationMode::Amiibo) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultCommandInvalidForState);
return;
}
IPC::RequestBuilder rb = rp.MakeBuilder(43, 0);
rb.Push(RESULT_SUCCESS);
rb.PushRaw<IdentificationBlockReply>(identification_block_reply);
LOG_DEBUG(Service_NFC, "called");
rb.PushRaw<RegisterInfo>({});
}
void Module::Interface::SetRegisterInfo(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x404, 41, 0);
const auto register_info = rp.PopRaw<RegisterInfoPrivate>();
LOG_INFO(Service_NFC, "called");
if (nfc->nfc_mode != CommunicationMode::Amiibo) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultCommandInvalidForState);
return;
}
const auto result = nfc->device->SetRegisterInfoPrivate(register_info);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(result);
}
void Module::Interface::DeleteRegisterInfo(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x405, 0, 0);
LOG_INFO(Service_NFC, "called");
if (nfc->nfc_mode != CommunicationMode::Amiibo) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultCommandInvalidForState);
return;
}
const auto result = nfc->device->DeleteRegisterInfo();
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(result);
}
void Module::Interface::DeleteApplicationArea(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x406, 0, 0);
LOG_INFO(Service_NFC, "called");
if (nfc->nfc_mode != CommunicationMode::Amiibo) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultCommandInvalidForState);
return;
}
const auto result = nfc->device->DeleteApplicationArea();
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(result);
}
void Module::Interface::ExistsApplicationArea(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx, 0x407, 0, 0);
if (nfc->nfc_mode != CommunicationMode::Amiibo) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultCommandInvalidForState);
return;
}
bool has_application_area = false;
const auto result = nfc->device->ApplicationAreaExist(has_application_area);
IPC::RequestBuilder rb = rp.MakeBuilder(0x2, 0);
rb.Push(result);
rb.Push(has_application_area);
LOG_INFO(Service_NFC, "called");
}
std::shared_ptr<Module> Module::Interface::GetModule() const {
return nfc;
}
void Module::Interface::LoadAmiibo(const AmiiboData& amiibo_data) {
bool Module::Interface::IsSearchingForAmiibos() {
std::lock_guard lock(HLE::g_hle_lock);
nfc->amiibo_data = amiibo_data;
nfc->amiibo_in_range = true;
nfc->SyncTagState();
const auto state = nfc->device->GetCurrentState();
return state == DeviceState::SearchingForTag;
}
bool Module::Interface::IsTagActive() {
std::lock_guard lock(HLE::g_hle_lock);
const auto state = nfc->device->GetCurrentState();
return state == DeviceState::TagFound || state == DeviceState::TagMounted ||
state == DeviceState::TagPartiallyMounted;
}
bool Module::Interface::LoadAmiibo(const std::string& fullpath) {
std::lock_guard lock(HLE::g_hle_lock);
return nfc->device->LoadAmiibo(fullpath);
}
void Module::Interface::RemoveAmiibo() {
std::lock_guard lock(HLE::g_hle_lock);
nfc->amiibo_in_range = false;
nfc->SyncTagState();
}
void Module::SyncTagState() {
if (amiibo_in_range &&
(nfc_tag_state == TagState::TagOutOfRange || nfc_tag_state == TagState::Scanning)) {
// TODO (wwylele): Should TagOutOfRange->TagInRange transition only happen on the same tag
// detected on Scanning->TagInRange?
nfc_tag_state = TagState::TagInRange;
tag_in_range_event->Signal();
} else if (!amiibo_in_range &&
(nfc_tag_state == TagState::TagInRange || nfc_tag_state == TagState::TagDataLoaded ||
nfc_tag_state == TagState::Unknown6)) {
// TODO (wwylele): If a tag is removed during TagDataLoaded/Unknown6, should this event
// signals early?
nfc_tag_state = TagState::TagOutOfRange;
tag_out_of_range_event->Signal();
}
nfc->device->UnloadAmiibo();
}
Module::Interface::Interface(std::shared_ptr<Module> nfc, const char* name, u32 max_session)
@ -354,10 +682,7 @@ Module::Interface::Interface(std::shared_ptr<Module> nfc, const char* name, u32
Module::Interface::~Interface() = default;
Module::Module(Core::System& system) {
tag_in_range_event =
system.Kernel().CreateEvent(Kernel::ResetType::OneShot, "NFC::tag_in_range_event");
tag_out_of_range_event =
system.Kernel().CreateEvent(Kernel::ResetType::OneShot, "NFC::tag_out_range_event");
device = std::make_shared<NfcDevice>(system);
}
Module::~Module() = default;

View File

@ -8,6 +8,8 @@
#include <memory>
#include <boost/serialization/binary_object.hpp>
#include "common/common_types.h"
#include "core/hle/service/nfc/nfc_device.h"
#include "core/hle/service/nfc/nfc_types.h"
#include "core/hle/service/service.h"
namespace Core {
@ -20,45 +22,11 @@ class Event;
namespace Service::NFC {
namespace ErrCodes {
enum {
CommandInvalidForState = 512,
};
} // namespace ErrCodes
// TODO(FearlessTobi): Add more members to this struct
struct AmiiboData {
std::array<u8, 7> uuid;
INSERT_PADDING_BYTES(0x4D);
u16_le char_id;
u8 char_variant;
u8 figure_type;
u16_be model_number;
u8 series;
INSERT_PADDING_BYTES(0x1C1);
private:
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& boost::serialization::make_binary_object(this, sizeof(AmiiboData));
}
friend class boost::serialization::access;
};
static_assert(sizeof(AmiiboData) == 0x21C, "AmiiboData is an invalid size");
enum class TagState : u8 {
enum class CommunicationMode : u8 {
NotInitialized = 0,
NotScanning = 1,
Scanning = 2,
TagInRange = 3,
TagOutOfRange = 4,
TagDataLoaded = 5,
Unknown6 = 6,
};
enum class CommunicationStatus : u8 {
AttemptInitialize = 1,
NfcInitialized = 2,
Ntag = 1,
Amiibo = 2,
TrainTag = 3,
};
class Module final {
@ -73,7 +41,11 @@ public:
std::shared_ptr<Module> GetModule() const;
void LoadAmiibo(const AmiiboData& amiibo_data);
bool IsSearchingForAmiibos();
bool IsTagActive();
bool LoadAmiibo(const std::string& fullpath);
void RemoveAmiibo();
@ -82,7 +54,7 @@ public:
* NFC::Initialize service function
* Inputs:
* 0 : Header code [0x00010040]
* 1 : (u8) unknown parameter. Can be either value 0x1 or 0x2
* 1 : (u8) CommunicationMode. Can be either value 0x1, 0x2 or 0x3
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
@ -92,7 +64,7 @@ public:
* NFC::Shutdown service function
* Inputs:
* 0 : Header code [0x00020040]
* 1 : (u8) unknown parameter
* 1 : (u8) CommunicationMode.
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
@ -153,6 +125,15 @@ public:
*/
void ResetTagScanState(Kernel::HLERequestContext& ctx);
/**
* NFC::UpdateStoredAmiiboData service function
* Inputs:
* 0 : Header code [0x00090002]
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void UpdateStoredAmiiboData(Kernel::HLERequestContext& ctx);
/**
* NFC::GetTagInRangeEvent service function
* Inputs:
@ -195,6 +176,16 @@ public:
*/
void CommunicationGetStatus(Kernel::HLERequestContext& ctx);
/**
* NFC::GetTagInfo2 service function
* Inputs:
* 0 : Header code [0x00100000]
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
* 2-26 : 0x60-byte struct
*/
void GetTagInfo2(Kernel::HLERequestContext& ctx);
/**
* NFC::GetTagInfo service function
* Inputs:
@ -206,23 +197,102 @@ public:
void GetTagInfo(Kernel::HLERequestContext& ctx);
/**
* NFC::GetAmiiboConfig service function
* NFC::GetTagInfo service function
* Inputs:
* 0 : Header code [0x00120000]
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
* 2 : Output NFC-adapter result-code
*/
void CommunicationGetResult(Kernel::HLERequestContext& ctx);
/**
* NFC::OpenAppData service function
* Inputs:
* 0 : Header code [0x00130040]
* 1 : (u32) App ID
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void OpenAppData(Kernel::HLERequestContext& ctx);
/**
* NFC::InitializeWriteAppData service function
* Inputs:
* 0 : Header code [0x00140384]
* 1 : (u32) App ID
* 2 : Size
* 3-14 : 0x30-byte zeroed-out struct
* 15 : 0x20, PID translate-header for kernel
* 16 : PID written by kernel
* 17 : (Size << 14) | 2
* 18 : Pointer to input buffer
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void InitializeWriteAppData(Kernel::HLERequestContext& ctx);
/**
* NFC::ReadAppData service function
* Inputs:
* 0 : Header code [0x00150040]
* 1 : Size (unused? Hard-coded to be 0xD8)
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void ReadAppData(Kernel::HLERequestContext& ctx);
/**
* NFC::WriteAppData service function
* Inputs:
* 0 : Header code [0x00160242]
* 1 : Size
* 2-9 : AmiiboWriteRequest struct (see above)
* 10 : (Size << 14) | 2
* 11 : Pointer to input appdata buffer
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void WriteAppData(Kernel::HLERequestContext& ctx);
/**
* NFC::GetRegisterInfo service function
* Inputs:
* 0 : Header code [0x00170000]
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
* 2-43 : AmiiboSettings struct (see above)
*/
void GetRegisterInfo(Kernel::HLERequestContext& ctx);
/**
* NFC::GetCommonInfo service function
* Inputs:
* 0 : Header code [0x00180000]
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
* 2-17 : 0x40-byte config struct
*/
void GetAmiiboConfig(Kernel::HLERequestContext& ctx);
void GetCommonInfo(Kernel::HLERequestContext& ctx);
/**
* NFC::Unknown0x1A service function
* NFC::GetAppDataInitStruct service function
* Inputs:
* 0 : Header code [0x00180000]
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
* 2-16 : 0x3C-byte config struct
*/
void GetAppDataInitStruct(Kernel::HLERequestContext& ctx);
/**
* NFC::LoadAmiiboPartially service function
* Inputs:
* 0 : Header code [0x001A0000]
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void Unknown0x1A(Kernel::HLERequestContext& ctx);
void LoadAmiiboPartially(Kernel::HLERequestContext& ctx);
/**
* NFC::GetIdentificationBlock service function
@ -234,21 +304,77 @@ public:
*/
void GetIdentificationBlock(Kernel::HLERequestContext& ctx);
/**
* NFC::Format service function
* Inputs:
* 0 : Header code [0x040100C2]
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void Format(Kernel::HLERequestContext& ctx);
/**
* NFC::GetAdminInfo service function
* Inputs:
* 0 : Header code [0x04020000]
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void GetAdminInfo(Kernel::HLERequestContext& ctx);
/**
* NFC::GetEmptyRegisterInfo service function
* Inputs:
* 0 : Header code [0x04030000]
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void GetEmptyRegisterInfo(Kernel::HLERequestContext& ctx);
/**
* NFC::SetRegisterInfo service function
* Inputs:
* 0 : Header code [0x04040A40]
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void SetRegisterInfo(Kernel::HLERequestContext& ctx);
/**
* NFC::DeleteRegisterInfo service function
* Inputs:
* 0 : Header code [0x04050000]
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void DeleteRegisterInfo(Kernel::HLERequestContext& ctx);
/**
* NFC::DeleteApplicationArea service function
* Inputs:
* 0 : Header code [0x04060000]
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void DeleteApplicationArea(Kernel::HLERequestContext& ctx);
/**
* NFC::ExistsApplicationArea service function
* Inputs:
* 0 : Header code [0x04070000]
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void ExistsApplicationArea(Kernel::HLERequestContext& ctx);
protected:
std::shared_ptr<Module> nfc;
};
private:
// Sync nfc_tag_state with amiibo_in_range and signal events on state change.
void SyncTagState();
CommunicationMode nfc_mode = CommunicationMode::NotInitialized;
std::shared_ptr<Kernel::Event> tag_in_range_event;
std::shared_ptr<Kernel::Event> tag_out_of_range_event;
TagState nfc_tag_state = TagState::NotInitialized;
CommunicationStatus nfc_status = CommunicationStatus::NfcInitialized;
AmiiboData amiibo_data{};
bool amiibo_in_range = false;
std::shared_ptr<NfcDevice> device = nullptr;
template <class Archive>
void serialize(Archive& ar, const unsigned int);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,111 @@
// Copyright 2022 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <span>
#include <vector>
#include <boost/serialization/binary_object.hpp>
#include "common/common_types.h"
#include "core/hle/service/nfc/nfc_results.h"
#include "core/hle/service/nfc/nfc_types.h"
#include "core/hle/service/service.h"
namespace Kernel {
class KEvent;
class KReadableEvent;
} // namespace Kernel
namespace Service::NFC {
class NfcDevice {
public:
NfcDevice(Core::System& system);
~NfcDevice();
bool LoadAmiibo(std::string filename);
void UnloadAmiibo();
void CloseAmiibo();
void Initialize();
void Finalize();
ResultCode StartCommunication();
ResultCode StopCommunication();
ResultCode StartDetection(TagProtocol allowed_protocol);
ResultCode StopDetection();
ResultCode Mount();
ResultCode MountAmiibo();
ResultCode PartiallyMount();
ResultCode PartiallyMountAmiibo();
ResultCode ResetTagScanState();
ResultCode Flush();
ResultCode GetTagInfo2(TagInfo2& tag_info) const;
ResultCode GetTagInfo(TagInfo& tag_info) const;
ResultCode GetCommonInfo(CommonInfo& common_info) const;
ResultCode GetModelInfo(ModelInfo& model_info) const;
ResultCode GetRegisterInfo(RegisterInfo& register_info) const;
ResultCode GetAdminInfo(AdminInfo& admin_info) const;
ResultCode DeleteRegisterInfo();
ResultCode SetRegisterInfoPrivate(const RegisterInfoPrivate& register_info);
ResultCode RestoreAmiibo();
ResultCode Format();
ResultCode OpenApplicationArea(u32 access_id);
ResultCode GetApplicationAreaId(u32& application_area_id) const;
ResultCode GetApplicationArea(std::vector<u8>& data) const;
ResultCode SetApplicationArea(std::span<const u8> data);
ResultCode CreateApplicationArea(u32 access_id, std::span<const u8> data);
ResultCode RecreateApplicationArea(u32 access_id, std::span<const u8> data);
ResultCode DeleteApplicationArea();
ResultCode ApplicationAreaExist(bool& has_application_area);
constexpr u32 GetApplicationAreaSize() const;
DeviceState GetCurrentState() const;
ResultCode GetCommunicationStatus(CommunicationState& status) const;
ResultCode CheckConnectionState() const;
std::shared_ptr<Kernel::Event> GetActivateEvent() const;
std::shared_ptr<Kernel::Event> GetDeactivateEvent() const;
private:
time_t GetCurrentTime() const;
void SetAmiiboName(AmiiboSettings& settings, const AmiiboName& amiibo_name);
AmiiboDate GetAmiiboDate() const;
u64 RemoveVersionByte(u64 application_id) const;
void UpdateSettingsCrc();
void UpdateRegisterInfoCrc();
void BuildAmiiboWithoutKeys();
std::shared_ptr<Kernel::Event> tag_in_range_event = nullptr;
std::shared_ptr<Kernel::Event> tag_out_of_range_event = nullptr;
Core::TimingEventType* remove_amiibo_event = nullptr;
bool is_initalized{};
bool is_data_moddified{};
bool is_app_area_open{};
bool is_plain_amiibo{};
bool is_write_protected{};
bool is_tag_in_range{};
TagProtocol allowed_protocols{};
DeviceState device_state{DeviceState::NotInitialized};
ConnectionState connection_state = ConnectionState::Success;
CommunicationState communication_state = CommunicationState::Idle;
std::string amiibo_filename = "";
SerializableAmiiboFile tag{};
SerializableEncryptedAmiiboFile encrypted_tag{};
template <class Archive>
void serialize(Archive& ar, const unsigned int);
friend class boost::serialization::access;
};
} // namespace Service::NFC
SERVICE_CONSTRUCT(Service::NFC::NfcDevice)

View File

@ -21,25 +21,33 @@ NFC_M::NFC_M(std::shared_ptr<Module> nfc) : Module::Interface(std::move(nfc), "n
{IPC::MakeHeader(0x0006, 0, 0), &NFC_M::StopTagScanning, "StopTagScanning"},
{IPC::MakeHeader(0x0007, 0, 0), &NFC_M::LoadAmiiboData, "LoadAmiiboData"},
{IPC::MakeHeader(0x0008, 0, 0), &NFC_M::ResetTagScanState, "ResetTagScanState"},
{IPC::MakeHeader(0x0009, 0, 2), nullptr, "UpdateStoredAmiiboData"},
{IPC::MakeHeader(0x0009, 0, 2), &NFC_M::UpdateStoredAmiiboData, "UpdateStoredAmiiboData"},
{IPC::MakeHeader(0x000A, 0, 0), nullptr, "Unknown0x0A"},
{IPC::MakeHeader(0x000B, 0, 0), &NFC_M::GetTagInRangeEvent, "GetTagInRangeEvent"},
{IPC::MakeHeader(0x000C, 0, 0), &NFC_M::GetTagOutOfRangeEvent, "GetTagOutOfRangeEvent"},
{IPC::MakeHeader(0x000D, 0, 0), &NFC_M::GetTagState, "GetTagState"},
{IPC::MakeHeader(0x000E, 0, 0), nullptr, "Unknown0x0E"},
{IPC::MakeHeader(0x000F, 0, 0), &NFC_M::CommunicationGetStatus, "CommunicationGetStatus"},
{IPC::MakeHeader(0x0010, 0, 0), nullptr, "GetTagInfo2"},
{IPC::MakeHeader(0x0010, 0, 0), &NFC_M::GetTagInfo2, "GetTagInfo2"},
{IPC::MakeHeader(0x0011, 0, 0), &NFC_M::GetTagInfo, "GetTagInfo"},
{IPC::MakeHeader(0x0012, 0, 0), nullptr, "CommunicationGetResult"},
{IPC::MakeHeader(0x0013, 1, 0), nullptr, "OpenAppData"},
{IPC::MakeHeader(0x0014, 14, 4), nullptr, "InitializeWriteAppData"},
{IPC::MakeHeader(0x0015, 1, 0), nullptr, "ReadAppData"},
{IPC::MakeHeader(0x0016, 9, 2), nullptr, "WriteAppData"},
{IPC::MakeHeader(0x0017, 0, 0), nullptr, "GetAmiiboSettings"},
{IPC::MakeHeader(0x0018, 0, 0), &NFC_M::GetAmiiboConfig, "GetAmiiboConfig"},
{IPC::MakeHeader(0x0019, 0, 0), nullptr, "GetAppDataInitStruct"},
{IPC::MakeHeader(0x001A, 0, 0), &NFC_M::Unknown0x1A, "Unknown0x1A"},
{IPC::MakeHeader(0x0012, 0, 0), &NFC_M::CommunicationGetResult, "CommunicationGetResult"},
{IPC::MakeHeader(0x0013, 1, 0), &NFC_M::OpenAppData, "OpenAppData"},
{IPC::MakeHeader(0x0014, 14, 4), &NFC_M::InitializeWriteAppData, "InitializeWriteAppData"},
{IPC::MakeHeader(0x0015, 1, 0), &NFC_M::ReadAppData, "ReadAppData"},
{IPC::MakeHeader(0x0016, 9, 2), &NFC_M::WriteAppData, "WriteAppData"},
{IPC::MakeHeader(0x0017, 0, 0), &NFC_M::GetRegisterInfo, "GetRegisterInfo"},
{IPC::MakeHeader(0x0018, 0, 0), &NFC_M::GetCommonInfo, "GetCommonInfo"},
{IPC::MakeHeader(0x0019, 0, 0), &NFC_M::GetAppDataInitStruct, "GetAppDataInitStruct"},
{IPC::MakeHeader(0x001A, 0, 0), &NFC_M::LoadAmiiboPartially, "LoadAmiiboPartially"},
{IPC::MakeHeader(0x001B, 0, 0), &NFC_M::GetIdentificationBlock, "GetIdentificationBlock"},
// nfc:m
{IPC::MakeHeader(0x0404, 41, 0), nullptr, "SetAmiiboSettings"}
{IPC::MakeHeader(0x0401, 3, 2), &NFC_M::Format, "Format"},
{IPC::MakeHeader(0x0402, 0, 0), &NFC_M::GetAdminInfo, "GetAdminInfo"},
{IPC::MakeHeader(0x0403, 0, 0), &NFC_M::GetEmptyRegisterInfo, "GetEmptyRegisterInfo"},
{IPC::MakeHeader(0x0404, 41, 0), &NFC_M::SetRegisterInfo, "SetRegisterInfo"},
{IPC::MakeHeader(0x0405, 0, 0), &NFC_M::DeleteRegisterInfo, "DeleteRegisterInfo"},
{IPC::MakeHeader(0x0406, 0, 0), &NFC_M::DeleteApplicationArea, "DeleteApplicationArea"},
{IPC::MakeHeader(0x0407, 0, 0), &NFC_M::ExistsApplicationArea, "ExistsApplicationArea"}
// clang-format on
};
RegisterHandlers(functions);

View File

@ -0,0 +1,62 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/result.h"
namespace Service::NFC {
namespace ErrCodes {
enum {
InvalidArgumentValue = 80,
InvalidArgument = 81,
InvalidChecksum = 200,
WriteFailed = 328,
CommandInvalidForState = 512,
NotAnAmiibo = 522,
CorruptedData = 536,
AppDataUninitialized = 544,
RegistrationUnitialized = 552,
ApplicationAreaExist = 560,
AppIdMismatch = 568,
CommunicationLost = 608,
NoAdapterDetected = 616,
};
} // namespace ErrCodes
constexpr ResultCode ResultInvalidArgumentValue(ErrCodes::InvalidArgumentValue, ErrorModule::NFC,
ErrorSummary::InvalidArgument, ErrorLevel::Status);
constexpr ResultCode ResultInvalidArgument(ErrCodes::InvalidArgument, ErrorModule::NFC,
ErrorSummary::InvalidArgument, ErrorLevel::Status);
constexpr ResultCode ResultCommandInvalidForState(ErrCodes::CommandInvalidForState,
ErrorModule::NFC, ErrorSummary::InvalidState,
ErrorLevel::Status);
constexpr ResultCode ResultNotAnAmiibo(ErrCodes::NotAnAmiibo, ErrorModule::NFC,
ErrorSummary::InvalidState, ErrorLevel::Status);
constexpr ResultCode ResultCorruptedData(ErrCodes::CorruptedData, ErrorModule::NFC,
ErrorSummary::InvalidState, ErrorLevel::Status);
constexpr ResultCode ResultWriteAmiiboFailed(ErrCodes::WriteFailed, ErrorModule::NFC,
ErrorSummary::InvalidState, ErrorLevel::Status);
constexpr ResultCode ResultApplicationAreaIsNotInitialized(ErrCodes::AppDataUninitialized,
ErrorModule::NFC,
ErrorSummary::InvalidState,
ErrorLevel::Status);
constexpr ResultCode ResultRegistrationIsNotInitialized(ErrCodes::RegistrationUnitialized,
ErrorModule::NFC,
ErrorSummary::InvalidState,
ErrorLevel::Status);
constexpr ResultCode ResultApplicationAreaExist(ErrCodes::ApplicationAreaExist, ErrorModule::NFC,
ErrorSummary::InvalidState, ErrorLevel::Status);
constexpr ResultCode ResultWrongApplicationAreaId(ErrCodes::AppIdMismatch, ErrorModule::NFC,
ErrorSummary::InvalidState, ErrorLevel::Status);
constexpr ResultCode ResultCommunicationLost(ErrCodes::CommunicationLost, ErrorModule::NFC,
ErrorSummary::InvalidState, ErrorLevel::Status);
constexpr ResultCode ResultNoAdapterDetected(ErrCodes::NoAdapterDetected, ErrorModule::NFC,
ErrorSummary::InvalidState, ErrorLevel::Status);
} // namespace Service::NFC

View File

@ -0,0 +1,460 @@
// Copyright 2022 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include "common/bit_field.h"
#include "common/common_types.h"
#include "core/hle/applets/mii_selector.h"
namespace Service::NFC {
static constexpr std::size_t amiibo_name_length = 0xA;
static constexpr std::size_t application_id_version_offset = 0x1c;
static constexpr std::size_t counter_limit = 0xffff;
enum class ServiceType : u32 {
User,
Debug,
System,
};
enum class CommunicationState : u8 {
Idle = 0,
SearchingForAdapter = 1,
Initialized = 2,
Active = 3,
};
enum class ConnectionState : u8 {
Success = 0,
NoAdapter = 1,
Lost = 2,
};
enum class DeviceState : u32 {
NotInitialized = 0,
Initialized = 1,
SearchingForTag = 2,
TagFound = 3,
TagRemoved = 4,
TagMounted = 5,
TagPartiallyMounted = 6, // Validate this one seems to have other name
};
enum class ModelType : u32 {
Amiibo,
};
enum class MountTarget : u32 {
None,
Rom,
Ram,
All,
};
enum class AmiiboType : u8 {
Figure,
Card,
Yarn,
};
enum class AmiiboSeries : u8 {
SuperSmashBros,
SuperMario,
ChibiRobo,
YoshiWoollyWorld,
Splatoon,
AnimalCrossing,
EightBitMario,
Skylanders,
Unknown8,
TheLegendOfZelda,
ShovelKnight,
Unknown11,
Kiby,
Pokemon,
MarioSportsSuperstars,
MonsterHunter,
BoxBoy,
Pikmin,
FireEmblem,
Metroid,
Others,
MegaMan,
Diablo,
};
enum class TagType : u32 {
None,
Type1, // ISO14443A RW 96-2k bytes 106kbit/s
Type2, // ISO14443A RW/RO 540 bytes 106kbit/s
Type3, // Sony Felica RW/RO 2k bytes 212kbit/s
Type4, // ISO14443A RW/RO 4k-32k bytes 424kbit/s
Type5, // ISO15693 RW/RO 540 bytes 106kbit/s
};
enum class PackedTagType : u8 {
None,
Type1, // ISO14443A RW 96-2k bytes 106kbit/s
Type2, // ISO14443A RW/RO 540 bytes 106kbit/s
Type3, // Sony Felica RW/RO 2k bytes 212kbit/s
Type4, // ISO14443A RW/RO 4k-32k bytes 424kbit/s
Type5, // ISO15693 RW/RO 540 bytes 106kbit/s
};
// Verify this enum. It might be completely wrong default protocol is 0x0
enum class TagProtocol : u32 {
None,
TypeA = 1U << 0, // ISO14443A
TypeB = 1U << 1, // ISO14443B
TypeF = 1U << 2, // Sony Felica
Unknown1 = 1U << 3,
Unknown2 = 1U << 5,
All = 0xFFFFFFFFU,
};
// Verify this enum. It might be completely wrong default protocol is 0x0
enum class PackedTagProtocol : u8 {
None,
TypeA = 1U << 0, // ISO14443A
TypeB = 1U << 1, // ISO14443B
TypeF = 1U << 2, // Sony Felica
Unknown1 = 1U << 3,
Unknown2 = 1U << 5,
All = 0xFF,
};
enum class AppAreaVersion : u8 {
Nintendo3DS = 0,
NintendoWiiU = 1,
Nintendo3DSv2 = 2,
NintendoSwitch = 3,
NotSet = 0xFF,
};
using UniqueSerialNumber = std::array<u8, 7>;
using LockBytes = std::array<u8, 2>;
using HashData = std::array<u8, 0x20>;
using ApplicationArea = std::array<u8, 0xD8>;
using AmiiboName = std::array<u16_be, amiibo_name_length>;
using DataBlock = std::array<u8, 0x10>;
using KeyData = std::array<u8, 0x6>;
struct TagUuid {
UniqueSerialNumber uid;
u8 nintendo_id;
LockBytes lock_bytes;
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& uid;
ar& nintendo_id;
ar& lock_bytes;
}
friend class boost::serialization::access;
};
static_assert(sizeof(TagUuid) == 10, "TagUuid is an invalid size");
struct WriteDate {
u16 year;
u8 month;
u8 day;
};
static_assert(sizeof(WriteDate) == 0x4, "WriteDate is an invalid size");
struct AmiiboDate {
u16 raw_date{};
u16 GetValue() const {
return Common::swap16(raw_date);
}
u16 GetYear() const {
return static_cast<u16>(((GetValue() & 0xFE00) >> 9) + 2000);
}
u8 GetMonth() const {
return static_cast<u8>((GetValue() & 0x01E0) >> 5);
}
u8 GetDay() const {
return static_cast<u8>(GetValue() & 0x001F);
}
WriteDate GetWriteDate() const {
if (!IsValidDate()) {
return {
.year = 2000,
.month = 1,
.day = 1,
};
}
return {
.year = GetYear(),
.month = GetMonth(),
.day = GetDay(),
};
}
void SetYear(u16 year) {
const u16 year_converted = static_cast<u16>((year - 2000) << 9);
raw_date = Common::swap16((GetValue() & ~0xFE00) | year_converted);
}
void SetMonth(u8 month) {
const u16 month_converted = static_cast<u16>(month << 5);
raw_date = Common::swap16((GetValue() & ~0x01E0) | month_converted);
}
void SetDay(u8 day) {
const u16 day_converted = static_cast<u16>(day);
raw_date = Common::swap16((GetValue() & ~0x001F) | day_converted);
}
bool IsValidDate() const {
const bool is_day_valid = GetDay() > 0 && GetDay() < 32;
const bool is_month_valid = GetMonth() > 0 && GetMonth() < 13;
const bool is_year_valid = GetYear() >= 2000;
return is_year_valid && is_month_valid && is_day_valid;
}
};
static_assert(sizeof(AmiiboDate) == 2, "AmiiboDate is an invalid size");
struct Settings {
union {
u8 raw{};
BitField<0, 4, u8> font_region;
BitField<4, 1, u8> amiibo_initialized;
BitField<5, 1, u8> appdata_initialized;
};
};
static_assert(sizeof(Settings) == 1, "AmiiboDate is an invalid size");
struct AmiiboSettings {
Settings settings;
u8 country_code_id;
u16_be crc_counter; // Incremented each time crc is changed
AmiiboDate init_date;
AmiiboDate write_date;
u32_be crc;
AmiiboName amiibo_name; // UTF-16 text
};
static_assert(sizeof(AmiiboSettings) == 0x20, "AmiiboSettings is an invalid size");
struct AmiiboModelInfo {
u16 character_id;
u8 character_variant;
AmiiboType amiibo_type;
u16_be model_number;
AmiiboSeries series;
PackedTagType tag_type;
INSERT_PADDING_BYTES(0x4); // Unknown
};
static_assert(sizeof(AmiiboModelInfo) == 0xC, "AmiiboModelInfo is an invalid size");
struct NTAG215Password {
u32 PWD; // Password to allow write access
u16 PACK; // Password acknowledge reply
u16 RFUI; // Reserved for future use
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& PWD;
ar& PACK;
ar& RFUI;
}
friend class boost::serialization::access;
};
static_assert(sizeof(NTAG215Password) == 0x8, "NTAG215Password is an invalid size");
#pragma pack(1)
struct EncryptedAmiiboFile {
u8 constant_value; // Must be A5
u16_be write_counter; // Number of times the amiibo has been written?
u8 amiibo_version; // Amiibo file version
AmiiboSettings settings; // Encrypted amiibo settings
HashData hmac_tag; // Hash
AmiiboModelInfo model_info; // Encrypted amiibo model info
HashData keygen_salt; // Salt
HashData hmac_data; // Hash
HLE::Applets::MiiData owner_mii; // Encrypted Mii data
u16 padding; // Mii Padding
u16_be owner_mii_aes_ccm; // Mii data AES-CCM MAC
u64_be application_id; // Encrypted Game id
u16_be application_write_counter; // Encrypted Counter
u32_be application_area_id; // Encrypted Game id
u8 application_id_byte;
u8 unknown;
u64 mii_extension;
std::array<u32, 0x5> unknown2;
u32_be register_info_crc;
ApplicationArea application_area; // Encrypted Game data
};
static_assert(sizeof(EncryptedAmiiboFile) == 0x1F8, "AmiiboFile is an invalid size");
struct NTAG215File {
LockBytes lock_bytes; // Tag UUID
u16 static_lock; // Set defined pages as read only
u32 compability_container; // Defines available memory
HashData hmac_data; // Hash
u8 constant_value; // Must be A5
u16_be write_counter; // Number of times the amiibo has been written?
u8 amiibo_version; // Amiibo file version
AmiiboSettings settings;
HLE::Applets::MiiData owner_mii; // Mii data
u16 padding; // Mii Padding
u16_be owner_mii_aes_ccm; // Mii data AES-CCM MAC
u64_be application_id; // Game id
u16_be application_write_counter; // Counter
u32_be application_area_id;
u8 application_id_byte;
u8 unknown;
u64 mii_extension;
std::array<u32, 0x5> unknown2;
u32_be register_info_crc;
ApplicationArea application_area; // Game data
HashData hmac_tag; // Hash
UniqueSerialNumber uid; // Unique serial number
u8 nintendo_id; // Tag UUID
AmiiboModelInfo model_info;
HashData keygen_salt; // Salt
u32 dynamic_lock; // Dynamic lock
u32 CFG0; // Defines memory protected by password
u32 CFG1; // Defines number of verification attempts
NTAG215Password password; // Password data
};
static_assert(sizeof(NTAG215File) == 0x21C, "NTAG215File is an invalid size");
static_assert(std::is_trivially_copyable_v<NTAG215File>, "NTAG215File must be trivially copyable.");
#pragma pack()
struct EncryptedNTAG215File {
TagUuid uuid; // Unique serial number
u16 static_lock; // Set defined pages as read only
u32 compability_container; // Defines available memory
EncryptedAmiiboFile user_memory; // Writable data
u32 dynamic_lock; // Dynamic lock
u32 CFG0; // Defines memory protected by password
u32 CFG1; // Defines number of verification attempts
NTAG215Password password; // Password data
};
static_assert(sizeof(EncryptedNTAG215File) == 0x21C, "EncryptedNTAG215File is an invalid size");
static_assert(std::is_trivially_copyable_v<EncryptedNTAG215File>,
"EncryptedNTAG215File must be trivially copyable.");
struct SerializableAmiiboFile {
union {
std::array<u8, 0x21C> raw;
NTAG215File file;
};
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& raw;
}
friend class boost::serialization::access;
};
static_assert(sizeof(SerializableAmiiboFile) == 0x21C, "SerializableAmiiboFile is an invalid size");
static_assert(std::is_trivially_copyable_v<SerializableAmiiboFile>,
"SerializableAmiiboFile must be trivially copyable.");
struct SerializableEncryptedAmiiboFile {
union {
std::array<u8, 0x21C> raw;
EncryptedNTAG215File file;
};
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& raw;
}
friend class boost::serialization::access;
};
static_assert(sizeof(SerializableEncryptedAmiiboFile) == 0x21C,
"SerializableEncryptedAmiiboFile is an invalid size");
static_assert(std::is_trivially_copyable_v<SerializableEncryptedAmiiboFile>,
"SerializableEncryptedAmiiboFile must be trivially copyable.");
struct TagInfo {
u16 uuid_length;
PackedTagProtocol protocol;
PackedTagType tag_type;
UniqueSerialNumber uuid;
std::array<u8, 0x21> extra_data;
};
static_assert(sizeof(TagInfo) == 0x2C, "TagInfo is an invalid size");
struct TagInfo2 {
u16 uuid_length;
INSERT_PADDING_BYTES(0x1);
PackedTagType tag_type;
UniqueSerialNumber uuid;
std::array<u8, 0x21> extra_data;
TagProtocol protocol;
std::array<u8, 0x30> extra_data2;
};
static_assert(sizeof(TagInfo2) == 0x60, "TagInfo2 is an invalid size");
struct CommonInfo {
WriteDate last_write_date;
u16 application_write_counter;
u16 character_id;
u8 character_variant;
AmiiboSeries series;
u16 model_number;
AmiiboType amiibo_type;
u8 version;
u16 application_area_size;
INSERT_PADDING_BYTES(0x30);
};
static_assert(sizeof(CommonInfo) == 0x40, "CommonInfo is an invalid size");
struct ModelInfo {
u16 character_id;
u8 character_variant;
AmiiboSeries series;
u16 model_number;
AmiiboType amiibo_type;
INSERT_PADDING_BYTES(0x2F);
};
static_assert(sizeof(ModelInfo) == 0x36, "ModelInfo is an invalid size");
struct RegisterInfo {
HLE::Applets::MiiData mii_data;
INSERT_PADDING_BYTES(0x2);
u16_be owner_mii_aes_ccm; // Mii data AES-CCM MAC
AmiiboName amiibo_name;
INSERT_PADDING_BYTES(0x2); // Zero string terminator
u8 flags;
u8 font_region;
WriteDate creation_date;
INSERT_PADDING_BYTES(0x2C);
};
static_assert(sizeof(RegisterInfo) == 0xA8, "RegisterInfo is an invalid size");
struct RegisterInfoPrivate {
HLE::Applets::MiiData mii_data;
INSERT_PADDING_BYTES(0x2);
u16_be owner_mii_aes_ccm; // Mii data AES-CCM MAC
AmiiboName amiibo_name;
INSERT_PADDING_BYTES(0x2); // Zero string terminator
u8 flags;
u8 font_region;
WriteDate creation_date;
INSERT_PADDING_BYTES(0x28);
};
static_assert(sizeof(RegisterInfoPrivate) == 0xA4, "RegisterInfoPrivate is an invalid size");
static_assert(std::is_trivial_v<RegisterInfoPrivate>, "RegisterInfoPrivate must be trivial.");
static_assert(std::is_trivially_copyable_v<RegisterInfoPrivate>,
"RegisterInfoPrivate must be trivially copyable.");
struct AdminInfo {
u64_be application_id;
u32_be application_area_id;
u16 crc_counter;
u8 flags;
PackedTagType tag_type;
AppAreaVersion app_area_version;
INSERT_PADDING_BYTES(0x7);
INSERT_PADDING_BYTES(0x28);
};
static_assert(sizeof(AdminInfo) == 0x40, "AdminInfo is an invalid size");
} // namespace Service::NFC

View File

@ -20,23 +20,32 @@ NFC_U::NFC_U(std::shared_ptr<Module> nfc) : Module::Interface(std::move(nfc), "n
{IPC::MakeHeader(0x0006, 0, 0), &NFC_U::StopTagScanning, "StopTagScanning"},
{IPC::MakeHeader(0x0007, 0, 0), &NFC_U::LoadAmiiboData, "LoadAmiiboData"},
{IPC::MakeHeader(0x0008, 0, 0), &NFC_U::ResetTagScanState, "ResetTagScanState"},
{IPC::MakeHeader(0x0009, 0, 2), nullptr, "UpdateStoredAmiiboData"},
{IPC::MakeHeader(0x0009, 0, 2), &NFC_U::UpdateStoredAmiiboData, "UpdateStoredAmiiboData"},
{IPC::MakeHeader(0x000A, 0, 0), nullptr, "Unknown0x0A"},
{IPC::MakeHeader(0x000B, 0, 0), &NFC_U::GetTagInRangeEvent, "GetTagInRangeEvent"},
{IPC::MakeHeader(0x000C, 0, 0), &NFC_U::GetTagOutOfRangeEvent, "GetTagOutOfRangeEvent"},
{IPC::MakeHeader(0x000D, 0, 0), &NFC_U::GetTagState, "GetTagState"},
{IPC::MakeHeader(0x000E, 0, 0), nullptr, "Unknown0x0E"},
{IPC::MakeHeader(0x000F, 0, 0), &NFC_U::CommunicationGetStatus, "CommunicationGetStatus"},
{IPC::MakeHeader(0x0010, 0, 0), nullptr, "GetTagInfo2"},
{IPC::MakeHeader(0x0010, 0, 0), &NFC_U::GetTagInfo2, "GetTagInfo2"},
{IPC::MakeHeader(0x0011, 0, 0), &NFC_U::GetTagInfo, "GetTagInfo"},
{IPC::MakeHeader(0x0012, 0, 0), nullptr, "CommunicationGetResult"},
{IPC::MakeHeader(0x0013, 1, 0), nullptr, "OpenAppData"},
{IPC::MakeHeader(0x0014, 14, 4), nullptr, "InitializeWriteAppData"},
{IPC::MakeHeader(0x0015, 1, 0), nullptr, "ReadAppData"},
{IPC::MakeHeader(0x0016, 9, 2), nullptr, "WriteAppData"},
{IPC::MakeHeader(0x0017, 0, 0), nullptr, "GetAmiiboSettings"},
{IPC::MakeHeader(0x0018, 0, 0), &NFC_U::GetAmiiboConfig, "GetAmiiboConfig"},
{IPC::MakeHeader(0x0019, 0, 0), nullptr, "GetAppDataInitStruct"},
{IPC::MakeHeader(0x001A, 0, 0), &NFC_U::Unknown0x1A, "Unknown0x1A"},
{IPC::MakeHeader(0x0012, 0, 0), &NFC_U::CommunicationGetResult, "CommunicationGetResult"},
{IPC::MakeHeader(0x0013, 1, 0), &NFC_U::OpenAppData, "OpenAppData"},
{IPC::MakeHeader(0x0014, 14, 4), &NFC_U::InitializeWriteAppData, "InitializeWriteAppData"},
{IPC::MakeHeader(0x0015, 1, 0), &NFC_U::ReadAppData, "ReadAppData"},
{IPC::MakeHeader(0x0016, 9, 2), &NFC_U::WriteAppData, "WriteAppData"},
{IPC::MakeHeader(0x0017, 0, 0), &NFC_U::GetRegisterInfo, "GetRegisterInfo"},
{IPC::MakeHeader(0x0018, 0, 0), &NFC_U::GetCommonInfo, "GetCommonInfo"},
{IPC::MakeHeader(0x0019, 0, 0), &NFC_U::GetAppDataInitStruct, "GetAppDataInitStruct"},
{IPC::MakeHeader(0x001A, 0, 0), &NFC_U::LoadAmiiboPartially, "LoadAmiiboPartially"},
{IPC::MakeHeader(0x001B, 0, 0), &NFC_U::GetIdentificationBlock, "GetIdentificationBlock"},
{IPC::MakeHeader(0x001C, 0, 0), nullptr, "Unknown0x1C"},
{IPC::MakeHeader(0x001D, 0, 0), nullptr, "Unknown0x1D"},
{IPC::MakeHeader(0x001E, 0, 0), nullptr, "Unknown0x1E"},
{IPC::MakeHeader(0x001F, 0, 0), nullptr, "Unknown0x1F"},
{IPC::MakeHeader(0x0020, 0, 0), nullptr, "Unknown0x20"},
{IPC::MakeHeader(0x0021, 0, 0), nullptr, "Unknown0x21"},
{IPC::MakeHeader(0x0022, 0, 0), nullptr, "Unknown0x22"},
// clang-format on
};
RegisterHandlers(functions);