service: nfc: Implement amiibo encryption and appdata (#6340)
This commit is contained in:
parent
ca2d87e5e3
commit
3d0a3c2c45
@ -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();
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
374
src/core/hle/service/nfc/amiibo_crypto.cpp
Normal file
374
src/core/hle/service/nfc/amiibo_crypto.cpp
Normal 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
|
109
src/core/hle/service/nfc/amiibo_crypto.h
Normal file
109
src/core/hle/service/nfc/amiibo_crypto.h
Normal 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
|
@ -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;
|
||||
|
@ -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);
|
||||
|
1110
src/core/hle/service/nfc/nfc_device.cpp
Normal file
1110
src/core/hle/service/nfc/nfc_device.cpp
Normal file
File diff suppressed because it is too large
Load Diff
111
src/core/hle/service/nfc/nfc_device.h
Normal file
111
src/core/hle/service/nfc/nfc_device.h
Normal 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)
|
@ -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);
|
||||
|
62
src/core/hle/service/nfc/nfc_results.h
Normal file
62
src/core/hle/service/nfc/nfc_results.h
Normal 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
|
460
src/core/hle/service/nfc/nfc_types.h
Normal file
460
src/core/hle/service/nfc/nfc_types.h
Normal 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
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user