diff options
Diffstat (limited to '')
-rw-r--r-- | src/core/hle/service/nfc/common/device.cpp | 1483 |
1 files changed, 1483 insertions, 0 deletions
diff --git a/src/core/hle/service/nfc/common/device.cpp b/src/core/hle/service/nfc/common/device.cpp new file mode 100644 index 000000000..5bf289818 --- /dev/null +++ b/src/core/hle/service/nfc/common/device.cpp @@ -0,0 +1,1483 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4701) // Potentially uninitialized local variable 'result' used +#endif + +#include <boost/crc.hpp> + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include <fmt/format.h> + +#include "common/fs/file.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" +#include "common/input.h" +#include "common/logging/log.h" +#include "common/string_util.h" +#include "common/tiny_mt.h" +#include "core/core.h" +#include "core/hid/emulated_controller.h" +#include "core/hid/hid_core.h" +#include "core/hid/hid_types.h" +#include "core/hle/kernel/k_event.h" +#include "core/hle/service/ipc_helpers.h" +#include "core/hle/service/mii/mii_manager.h" +#include "core/hle/service/mii/types.h" +#include "core/hle/service/nfc/common/amiibo_crypto.h" +#include "core/hle/service/nfc/common/device.h" +#include "core/hle/service/nfc/mifare_result.h" +#include "core/hle/service/nfc/nfc_result.h" +#include "core/hle/service/time/time_manager.h" +#include "core/hle/service/time/time_zone_content_manager.h" +#include "core/hle/service/time/time_zone_types.h" + +namespace Service::NFC { +NfcDevice::NfcDevice(Core::HID::NpadIdType npad_id_, Core::System& system_, + KernelHelpers::ServiceContext& service_context_, + Kernel::KEvent* availability_change_event_) + : npad_id{npad_id_}, system{system_}, service_context{service_context_}, + availability_change_event{availability_change_event_} { + activate_event = service_context.CreateEvent("NFC:ActivateEvent"); + deactivate_event = service_context.CreateEvent("NFC:DeactivateEvent"); + npad_device = system.HIDCore().GetEmulatedController(npad_id); + + Core::HID::ControllerUpdateCallback engine_callback{ + .on_change = [this](Core::HID::ControllerTriggerType type) { NpadUpdate(type); }, + .is_npad_service = false, + }; + is_controller_set = true; + callback_key = npad_device->SetCallback(engine_callback); +} + +NfcDevice::~NfcDevice() { + service_context.CloseEvent(activate_event); + service_context.CloseEvent(deactivate_event); + if (!is_controller_set) { + return; + } + npad_device->DeleteCallback(callback_key); + is_controller_set = false; +}; + +void NfcDevice::NpadUpdate(Core::HID::ControllerTriggerType type) { + if (!is_initalized) { + return; + } + + if (type == Core::HID::ControllerTriggerType::Connected) { + Initialize(); + availability_change_event->Signal(); + return; + } + + if (type == Core::HID::ControllerTriggerType::Disconnected) { + device_state = DeviceState::Unavailable; + availability_change_event->Signal(); + return; + } + + if (type != Core::HID::ControllerTriggerType::Nfc) { + return; + } + + if (!npad_device->IsConnected()) { + return; + } + + const auto nfc_status = npad_device->GetNfc(); + switch (nfc_status.state) { + case Common::Input::NfcState::NewAmiibo: + LoadNfcTag(nfc_status.protocol, nfc_status.tag_type, nfc_status.uuid_length, + nfc_status.uuid); + break; + case Common::Input::NfcState::AmiiboRemoved: + if (device_state == DeviceState::Initialized || device_state == DeviceState::TagRemoved) { + break; + } + if (device_state != DeviceState::SearchingForTag) { + CloseNfcTag(); + } + break; + default: + break; + } +} + +bool NfcDevice::LoadNfcTag(u8 protocol, u8 tag_type, u8 uuid_length, UniqueSerialNumber uuid) { + if (device_state != DeviceState::SearchingForTag) { + LOG_ERROR(Service_NFC, "Game is not looking for nfc tag, current state {}", device_state); + return false; + } + + if ((protocol & static_cast<u8>(allowed_protocols)) == 0) { + LOG_ERROR(Service_NFC, "Protocol not supported {}", protocol); + return false; + } + + real_tag_info = { + .uuid = uuid, + .uuid_length = uuid_length, + .protocol = static_cast<NfcProtocol>(protocol), + .tag_type = static_cast<TagType>(tag_type), + }; + + device_state = DeviceState::TagFound; + deactivate_event->GetReadableEvent().Clear(); + activate_event->Signal(); + return true; +} + +bool NfcDevice::LoadAmiiboData() { + std::vector<u8> data{}; + + if (!npad_device->ReadAmiiboData(data)) { + return false; + } + + if (data.size() < sizeof(NFP::EncryptedNTAG215File)) { + LOG_ERROR(Service_NFC, "Not an amiibo, size={}", data.size()); + return false; + } + + memcpy(&tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File)); + is_plain_amiibo = NFP::AmiiboCrypto::IsAmiiboValid(tag_data); + is_write_protected = false; + + // Fallback for plain amiibos + if (is_plain_amiibo) { + LOG_INFO(Service_NFP, "Using plain amiibo"); + encrypted_tag_data = NFP::AmiiboCrypto::EncodedDataToNfcData(tag_data); + return true; + } + + // Fallback for encrypted amiibos without keys + if (!NFP::AmiiboCrypto::IsKeyAvailable()) { + LOG_INFO(Service_NFC, "Loading amiibo without keys"); + memcpy(&encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File)); + BuildAmiiboWithoutKeys(tag_data, encrypted_tag_data); + is_plain_amiibo = true; + is_write_protected = true; + return true; + } + + LOG_INFO(Service_NFP, "Using encrypted amiibo"); + tag_data = {}; + memcpy(&encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File)); + return true; +} + +void NfcDevice::CloseNfcTag() { + LOG_INFO(Service_NFC, "Remove nfc tag"); + + if (device_state == DeviceState::TagMounted) { + Unmount(); + } + + device_state = DeviceState::TagRemoved; + encrypted_tag_data = {}; + tag_data = {}; + activate_event->GetReadableEvent().Clear(); + deactivate_event->Signal(); +} + +Kernel::KReadableEvent& NfcDevice::GetActivateEvent() const { + return activate_event->GetReadableEvent(); +} + +Kernel::KReadableEvent& NfcDevice::GetDeactivateEvent() const { + return deactivate_event->GetReadableEvent(); +} + +void NfcDevice::Initialize() { + device_state = npad_device->HasNfc() ? DeviceState::Initialized : DeviceState::Unavailable; + encrypted_tag_data = {}; + tag_data = {}; + + if (device_state != DeviceState::Initialized) { + return; + } + + is_initalized = npad_device->AddNfcHandle(); +} + +void NfcDevice::Finalize() { + if (device_state == DeviceState::TagMounted) { + Unmount(); + } + if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) { + StopDetection(); + } + + if (device_state != DeviceState::Unavailable) { + npad_device->RemoveNfcHandle(); + } + + device_state = DeviceState::Unavailable; + is_initalized = false; +} + +Result NfcDevice::StartDetection(NfcProtocol allowed_protocol) { + if (device_state != DeviceState::Initialized && device_state != DeviceState::TagRemoved) { + LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); + return ResultWrongDeviceState; + } + + if (!npad_device->StartNfcPolling()) { + LOG_ERROR(Service_NFC, "Nfc polling not supported"); + return ResultNfcDisabled; + } + + device_state = DeviceState::SearchingForTag; + allowed_protocols = allowed_protocol; + return ResultSuccess; +} + +Result NfcDevice::StopDetection() { + if (device_state == DeviceState::Initialized) { + return ResultSuccess; + } + + if (device_state == DeviceState::TagFound || device_state == DeviceState::TagMounted) { + CloseNfcTag(); + } + + if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) { + npad_device->StopNfcPolling(); + device_state = DeviceState::Initialized; + return ResultSuccess; + } + + LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); + return ResultWrongDeviceState; +} + +Result NfcDevice::GetTagInfo(NFP::TagInfo& tag_info) const { + if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) { + LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); + if (device_state == DeviceState::TagRemoved) { + return ResultTagRemoved; + } + return ResultWrongDeviceState; + } + + tag_info = real_tag_info; + + // Generate random UUID to bypass amiibo load limits + if (real_tag_info.tag_type == TagType::Type2 && Settings::values.random_amiibo_id) { + Common::TinyMT rng{}; + rng.Initialize(static_cast<u32>(GetCurrentPosixTime())); + rng.GenerateRandomBytes(tag_info.uuid.data(), tag_info.uuid_length); + } + + return ResultSuccess; +} + +Result NfcDevice::ReadMifare(std::span<const MifareReadBlockParameter> parameters, + std::span<MifareReadBlockData> read_block_data) const { + if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) { + LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); + if (device_state == DeviceState::TagRemoved) { + return ResultTagRemoved; + } + return ResultWrongDeviceState; + } + + Result result = ResultSuccess; + + TagInfo tag_info{}; + result = GetTagInfo(tag_info); + + if (result.IsError()) { + return result; + } + + if (tag_info.protocol != NfcProtocol::TypeA || tag_info.tag_type != TagType::Mifare) { + return ResultInvalidTagType; + } + + if (parameters.size() == 0) { + return ResultInvalidArgument; + } + + Common::Input::MifareRequest request{}; + Common::Input::MifareRequest out_data{}; + const auto unknown = parameters[0].sector_key.unknown; + for (std::size_t i = 0; i < parameters.size(); i++) { + if (unknown != parameters[i].sector_key.unknown) { + return ResultInvalidArgument; + } + } + + for (std::size_t i = 0; i < parameters.size(); i++) { + if (parameters[i].sector_key.command == MifareCmd::None) { + continue; + } + request.data[i].command = static_cast<u8>(parameters[i].sector_key.command); + request.data[i].sector = parameters[i].sector_number; + memcpy(request.data[i].key.data(), parameters[i].sector_key.sector_key.data(), + sizeof(KeyData)); + } + + if (!npad_device->ReadMifareData(request, out_data)) { + return ResultMifareError288; + } + + for (std::size_t i = 0; i < read_block_data.size(); i++) { + if (static_cast<MifareCmd>(out_data.data[i].command) == MifareCmd::None) { + continue; + } + + read_block_data[i] = { + .data = out_data.data[i].data, + .sector_number = out_data.data[i].sector, + }; + } + + return ResultSuccess; +} + +Result NfcDevice::WriteMifare(std::span<const MifareWriteBlockParameter> parameters) { + Result result = ResultSuccess; + + TagInfo tag_info{}; + result = GetTagInfo(tag_info); + + if (result.IsError()) { + return result; + } + + if (tag_info.protocol != NfcProtocol::TypeA || tag_info.tag_type != TagType::Mifare) { + return ResultInvalidTagType; + } + + if (parameters.size() == 0) { + return ResultInvalidArgument; + } + + const auto unknown = parameters[0].sector_key.unknown; + for (std::size_t i = 0; i < parameters.size(); i++) { + if (unknown != parameters[i].sector_key.unknown) { + return ResultInvalidArgument; + } + } + + Common::Input::MifareRequest request{}; + for (std::size_t i = 0; i < parameters.size(); i++) { + if (parameters[i].sector_key.command == MifareCmd::None) { + continue; + } + request.data[i].command = static_cast<u8>(parameters[i].sector_key.command); + request.data[i].sector = parameters[i].sector_number; + memcpy(request.data[i].key.data(), parameters[i].sector_key.sector_key.data(), + sizeof(KeyData)); + memcpy(request.data[i].data.data(), parameters[i].data.data(), sizeof(KeyData)); + } + + if (!npad_device->WriteMifareData(request)) { + return ResultMifareError288; + } + + return result; +} + +Result NfcDevice::SendCommandByPassThrough(const Time::Clock::TimeSpanType& timeout, + std::span<const u8> command_data, + std::span<u8> out_data) { + // Not implemented + return ResultSuccess; +} + +Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target_) { + if (device_state != DeviceState::TagFound) { + LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); + return ResultWrongDeviceState; + } + + if (!LoadAmiiboData()) { + LOG_ERROR(Service_NFP, "Not an amiibo"); + return ResultInvalidTagType; + } + + if (!NFP::AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) { + LOG_ERROR(Service_NFP, "Not an amiibo"); + return ResultInvalidTagType; + } + + // The loaded amiibo is not encrypted + if (is_plain_amiibo) { + std::vector<u8> data(sizeof(NFP::NTAG215File)); + memcpy(data.data(), &tag_data, sizeof(tag_data)); + WriteBackupData(tag_data.uid, data); + + device_state = DeviceState::TagMounted; + mount_target = mount_target_; + return ResultSuccess; + } + + if (!NFP::AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) { + bool has_backup = HasBackup(encrypted_tag_data.uuid).IsSuccess(); + LOG_ERROR(Service_NFP, "Can't decode amiibo, has_backup= {}", has_backup); + return has_backup ? ResultCorruptedDataWithBackup : ResultCorruptedData; + } + + std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File)); + memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data)); + WriteBackupData(encrypted_tag_data.uuid, data); + + device_state = DeviceState::TagMounted; + mount_target = mount_target_; + return ResultSuccess; +} + +Result NfcDevice::Unmount() { + if (device_state != DeviceState::TagMounted) { + LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); + if (device_state == DeviceState::TagRemoved) { + return ResultTagRemoved; + } + return ResultWrongDeviceState; + } + + // Save data before unloading the amiibo + if (is_data_moddified) { + Flush(); + } + + device_state = DeviceState::TagFound; + mount_target = NFP::MountTarget::None; + is_app_area_open = false; + + return ResultSuccess; +} + +Result NfcDevice::Flush() { + if (device_state != DeviceState::TagMounted) { + LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); + if (device_state == DeviceState::TagRemoved) { + return ResultTagRemoved; + } + return ResultWrongDeviceState; + } + + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { + LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); + return ResultWrongDeviceState; + } + + auto& settings = tag_data.settings; + + const auto& current_date = GetAmiiboDate(GetCurrentPosixTime()); + if (settings.write_date.raw_date != current_date.raw_date) { + settings.write_date = current_date; + UpdateSettingsCrc(); + } + + tag_data.write_counter++; + + const auto result = FlushWithBreak(NFP::BreakType::Normal); + + is_data_moddified = false; + + return result; +} + +Result NfcDevice::FlushDebug() { + if (device_state != DeviceState::TagMounted) { + LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); + if (device_state == DeviceState::TagRemoved) { + return ResultTagRemoved; + } + return ResultWrongDeviceState; + } + + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { + LOG_ERROR(Service_NFC, "Amiibo is read only", device_state); + return ResultWrongDeviceState; + } + + tag_data.write_counter++; + + const auto result = FlushWithBreak(NFP::BreakType::Normal); + + is_data_moddified = false; + + return result; +} + +Result NfcDevice::FlushWithBreak(NFP::BreakType break_type) { + if (break_type != NFP::BreakType::Normal) { + LOG_ERROR(Service_NFC, "Break type not implemented {}", break_type); + return ResultWrongDeviceState; + } + + if (is_write_protected) { + LOG_ERROR(Service_NFP, "No keys available skipping write request"); + return ResultSuccess; + } + + std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File)); + if (is_plain_amiibo) { + memcpy(data.data(), &tag_data, sizeof(tag_data)); + WriteBackupData(tag_data.uid, data); + } else { + if (!NFP::AmiiboCrypto::EncodeAmiibo(tag_data, encrypted_tag_data)) { + LOG_ERROR(Service_NFP, "Failed to encode data"); + return ResultWriteAmiiboFailed; + } + + memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data)); + WriteBackupData(encrypted_tag_data.uuid, data); + } + + if (!npad_device->WriteNfc(data)) { + LOG_ERROR(Service_NFP, "Error writing to file"); + return ResultWriteAmiiboFailed; + } + + return ResultSuccess; +} + +Result NfcDevice::Restore() { + if (device_state != DeviceState::TagFound) { + LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); + if (device_state == DeviceState::TagRemoved) { + return ResultTagRemoved; + } + return ResultWrongDeviceState; + } + + NFC::TagInfo tag_info{}; + std::array<u8, sizeof(NFP::EncryptedNTAG215File)> data{}; + Result result = GetTagInfo(tag_info); + + if (result.IsError()) { + return result; + } + + result = ReadBackupData(tag_info.uuid, tag_info.uuid_length, data); + + if (result.IsError()) { + return result; + } + + NFP::NTAG215File temporary_tag_data{}; + NFP::EncryptedNTAG215File temporary_encrypted_tag_data{}; + + // Fallback for encrypted amiibos without keys + if (is_write_protected) { + return ResultWriteAmiiboFailed; + } + + // Fallback for plain amiibos + if (is_plain_amiibo) { + LOG_INFO(Service_NFP, "Restoring backup of plain amiibo"); + memcpy(&temporary_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File)); + temporary_encrypted_tag_data = NFP::AmiiboCrypto::EncodedDataToNfcData(temporary_tag_data); + } + + if (!is_plain_amiibo) { + LOG_INFO(Service_NFP, "Restoring backup of encrypted amiibo"); + temporary_tag_data = {}; + memcpy(&temporary_encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File)); + } + + if (!NFP::AmiiboCrypto::IsAmiiboValid(temporary_encrypted_tag_data)) { + return ResultInvalidTagType; + } + + if (!is_plain_amiibo) { + if (!NFP::AmiiboCrypto::DecodeAmiibo(temporary_encrypted_tag_data, temporary_tag_data)) { + LOG_ERROR(Service_NFP, "Can't decode amiibo"); + return ResultCorruptedData; + } + } + + // Overwrite tag contents with backup and mount the tag + tag_data = temporary_tag_data; + encrypted_tag_data = temporary_encrypted_tag_data; + device_state = DeviceState::TagMounted; + mount_target = NFP::MountTarget::All; + is_data_moddified = true; + + return ResultSuccess; +} + +Result NfcDevice::GetCommonInfo(NFP::CommonInfo& common_info) const { + if (device_state != DeviceState::TagMounted) { + LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); + if (device_state == DeviceState::TagRemoved) { + return ResultTagRemoved; + } + return ResultWrongDeviceState; + } + + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { + LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); + return ResultWrongDeviceState; + } + + const auto& settings = tag_data.settings; + + // TODO: Validate this data + common_info = { + .last_write_date = settings.write_date.GetWriteDate(), + .write_counter = tag_data.application_write_counter, + .version = tag_data.amiibo_version, + .application_area_size = sizeof(NFP::ApplicationArea), + }; + return ResultSuccess; +} + +Result NfcDevice::GetModelInfo(NFP::ModelInfo& model_info) const { + if (device_state != DeviceState::TagMounted) { + LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); + if (device_state == DeviceState::TagRemoved) { + return ResultTagRemoved; + } + return ResultWrongDeviceState; + } + + const auto& model_info_data = encrypted_tag_data.user_memory.model_info; + + model_info = { + .character_id = model_info_data.character_id, + .character_variant = model_info_data.character_variant, + .amiibo_type = model_info_data.amiibo_type, + .model_number = model_info_data.model_number, + .series = model_info_data.series, + }; + return ResultSuccess; +} + +Result NfcDevice::GetRegisterInfo(NFP::RegisterInfo& register_info) const { + if (device_state != DeviceState::TagMounted) { + LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); + if (device_state == DeviceState::TagRemoved) { + return ResultTagRemoved; + } + return ResultWrongDeviceState; + } + + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { + LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); + return ResultWrongDeviceState; + } + + if (tag_data.settings.settings.amiibo_initialized == 0) { + return ResultRegistrationIsNotInitialized; + } + + Service::Mii::MiiManager manager; + const auto& settings = tag_data.settings; + + // TODO: Validate this data + register_info = { + .mii_char_info = manager.ConvertV3ToCharInfo(tag_data.owner_mii), + .creation_date = settings.init_date.GetWriteDate(), + .amiibo_name = GetAmiiboName(settings), + .font_region = settings.settings.font_region, + }; + + return ResultSuccess; +} + +Result NfcDevice::GetRegisterInfoPrivate(NFP::RegisterInfoPrivate& register_info) const { + if (device_state != DeviceState::TagMounted) { + LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); + if (device_state == DeviceState::TagRemoved) { + return ResultTagRemoved; + } + return ResultWrongDeviceState; + } + + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { + LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); + return ResultWrongDeviceState; + } + + if (tag_data.settings.settings.amiibo_initialized == 0) { + return ResultRegistrationIsNotInitialized; + } + + Service::Mii::MiiManager manager; + const auto& settings = tag_data.settings; + + // TODO: Validate and complete this data + register_info = { + .mii_store_data = {}, + .creation_date = settings.init_date.GetWriteDate(), + .amiibo_name = GetAmiiboName(settings), + .font_region = settings.settings.font_region, + }; + + return ResultSuccess; +} + +Result NfcDevice::GetAdminInfo(NFP::AdminInfo& admin_info) const { + if (device_state != DeviceState::TagMounted) { + LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); + if (device_state == DeviceState::TagRemoved) { + return ResultTagRemoved; + } + return ResultWrongDeviceState; + } + + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { + LOG_ERROR(Service_NFC, "Amiibo is read only", device_state); + return ResultWrongDeviceState; + } + + u8 flags = static_cast<u8>(tag_data.settings.settings.raw >> 0x4); + if (tag_data.settings.settings.amiibo_initialized == 0) { + flags = flags & 0xfe; + } + + u64 application_id = 0; + u32 application_area_id = 0; + NFP::AppAreaVersion app_area_version = NFP::AppAreaVersion::NotSet; + if (tag_data.settings.settings.appdata_initialized != 0) { + application_id = tag_data.application_id; + app_area_version = static_cast<NFP::AppAreaVersion>( + application_id >> NFP::application_id_version_offset & 0xf); + + // Restore application id to original value + if (application_id >> 0x38 != 0) { + const u8 application_byte = tag_data.application_id_byte & 0xf; + application_id = + RemoveVersionByte(application_id) | + (static_cast<u64>(application_byte) << NFP::application_id_version_offset); + } + + application_area_id = tag_data.application_area_id; + } + + // TODO: Validate this data + admin_info = { + .application_id = application_id, + .application_area_id = application_area_id, + .crc_change_counter = tag_data.settings.crc_counter, + .flags = flags, + .tag_type = PackedTagType::Type2, + .app_area_version = app_area_version, + }; + + return ResultSuccess; +} + +Result NfcDevice::DeleteRegisterInfo() { + if (device_state != DeviceState::TagMounted) { + LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); + if (device_state == DeviceState::TagRemoved) { + return ResultTagRemoved; + } + return ResultWrongDeviceState; + } + + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { + LOG_ERROR(Service_NFC, "Amiibo is read only", device_state); + return ResultWrongDeviceState; + } + + if (tag_data.settings.settings.amiibo_initialized == 0) { + return ResultRegistrationIsNotInitialized; + } + + Common::TinyMT rng{}; + rng.Initialize(static_cast<u32>(GetCurrentPosixTime())); + rng.GenerateRandomBytes(&tag_data.owner_mii, sizeof(tag_data.owner_mii)); + rng.GenerateRandomBytes(&tag_data.settings.amiibo_name, sizeof(tag_data.settings.amiibo_name)); + rng.GenerateRandomBytes(&tag_data.unknown, sizeof(u8)); + rng.GenerateRandomBytes(&tag_data.unknown2[0], sizeof(u32)); + rng.GenerateRandomBytes(&tag_data.unknown2[1], sizeof(u32)); + rng.GenerateRandomBytes(&tag_data.register_info_crc, sizeof(u32)); + rng.GenerateRandomBytes(&tag_data.settings.init_date, sizeof(u32)); + tag_data.settings.settings.font_region.Assign(0); + tag_data.settings.settings.amiibo_initialized.Assign(0); + + return Flush(); +} + +Result NfcDevice::SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& register_info) { + if (device_state != DeviceState::TagMounted) { + LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); + if (device_state == DeviceState::TagRemoved) { + return ResultTagRemoved; + } + return ResultWrongDeviceState; + } + + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { + LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); + return ResultWrongDeviceState; + } + + Service::Mii::MiiManager manager; + const auto mii = manager.BuildDefault(0); + auto& settings = tag_data.settings; + + if (tag_data.settings.settings.amiibo_initialized == 0) { + settings.init_date = GetAmiiboDate(GetCurrentPosixTime()); + settings.write_date.raw_date = 0; + } + + SetAmiiboName(settings, register_info.amiibo_name); + tag_data.owner_mii = manager.BuildFromStoreData(mii); + tag_data.mii_extension = manager.SetFromStoreData(mii); + tag_data.unknown = 0; + tag_data.unknown2 = {}; + settings.country_code_id = 0; + settings.settings.font_region.Assign(0); + settings.settings.amiibo_initialized.Assign(1); + + UpdateRegisterInfoCrc(); + + return Flush(); +} + +Result NfcDevice::RestoreAmiibo() { + if (device_state != DeviceState::TagMounted) { + LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); + if (device_state == DeviceState::TagRemoved) { + return ResultTagRemoved; + } + return ResultWrongDeviceState; + } + + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { + LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); + return ResultWrongDeviceState; + } + + // TODO: Load amiibo from backup on system + LOG_ERROR(Service_NFP, "Not Implemented"); + return ResultSuccess; +} + +Result NfcDevice::Format() { + auto result1 = DeleteApplicationArea(); + auto result2 = DeleteRegisterInfo(); + + if (result1.IsError()) { + return result1; + } + + if (result2.IsError()) { + return result2; + } + + return Flush(); +} + +Result NfcDevice::OpenApplicationArea(u32 access_id) { + if (device_state != DeviceState::TagMounted) { + LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); + if (device_state == DeviceState::TagRemoved) { + return ResultTagRemoved; + } + return ResultWrongDeviceState; + } + + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { + LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); + return ResultWrongDeviceState; + } + + if (tag_data.settings.settings.appdata_initialized.Value() == 0) { + LOG_WARNING(Service_NFP, "Application area is not initialized"); + return ResultApplicationAreaIsNotInitialized; + } + + if (tag_data.application_area_id != access_id) { + LOG_WARNING(Service_NFP, "Wrong application area id"); + return ResultWrongApplicationAreaId; + } + + is_app_area_open = true; + + return ResultSuccess; +} + +Result NfcDevice::GetApplicationAreaId(u32& application_area_id) const { + application_area_id = {}; + + if (device_state != DeviceState::TagMounted) { + LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); + if (device_state == DeviceState::TagRemoved) { + return ResultTagRemoved; + } + return ResultWrongDeviceState; + } + + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { + LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); + return ResultWrongDeviceState; + } + + if (tag_data.settings.settings.appdata_initialized.Value() == 0) { + LOG_WARNING(Service_NFP, "Application area is not initialized"); + return ResultApplicationAreaIsNotInitialized; + } + + application_area_id = tag_data.application_area_id; + + return ResultSuccess; +} + +Result NfcDevice::GetApplicationArea(std::span<u8> data) const { + if (device_state != DeviceState::TagMounted) { + LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); + if (device_state == DeviceState::TagRemoved) { + return ResultTagRemoved; + } + return ResultWrongDeviceState; + } + + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { + LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); + return ResultWrongDeviceState; + } + + if (!is_app_area_open) { + LOG_ERROR(Service_NFP, "Application area is not open"); + return ResultWrongDeviceState; + } + + if (tag_data.settings.settings.appdata_initialized.Value() == 0) { + LOG_ERROR(Service_NFP, "Application area is not initialized"); + return ResultApplicationAreaIsNotInitialized; + } + + memcpy(data.data(), tag_data.application_area.data(), + std::min(data.size(), sizeof(NFP::ApplicationArea))); + + return ResultSuccess; +} + +Result NfcDevice::SetApplicationArea(std::span<const u8> data) { + if (device_state != DeviceState::TagMounted) { + LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); + if (device_state == DeviceState::TagRemoved) { + return ResultTagRemoved; + } + return ResultWrongDeviceState; + } + + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { + LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); + return ResultWrongDeviceState; + } + + if (!is_app_area_open) { + LOG_ERROR(Service_NFP, "Application area is not open"); + return ResultWrongDeviceState; + } + + if (tag_data.settings.settings.appdata_initialized.Value() == 0) { + LOG_ERROR(Service_NFP, "Application area is not initialized"); + return ResultApplicationAreaIsNotInitialized; + } + + if (data.size() > sizeof(NFP::ApplicationArea)) { + LOG_ERROR(Service_NFP, "Wrong data size {}", data.size()); + return ResultUnknown; + } + + Common::TinyMT rng{}; + rng.Initialize(static_cast<u32>(GetCurrentPosixTime())); + std::memcpy(tag_data.application_area.data(), data.data(), data.size()); + // Fill remaining data with random numbers + rng.GenerateRandomBytes(tag_data.application_area.data() + data.size(), + sizeof(NFP::ApplicationArea) - data.size()); + + if (tag_data.application_write_counter != NFP::counter_limit) { + tag_data.application_write_counter++; + } + + is_data_moddified = true; + + return ResultSuccess; +} + +Result NfcDevice::CreateApplicationArea(u32 access_id, std::span<const u8> data) { + if (device_state != DeviceState::TagMounted) { + LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); + if (device_state == DeviceState::TagRemoved) { + return ResultTagRemoved; + } + return ResultWrongDeviceState; + } + + if (tag_data.settings.settings.appdata_initialized.Value() != 0) { + LOG_ERROR(Service_NFP, "Application area already exist"); + return ResultApplicationAreaExist; + } + + return RecreateApplicationArea(access_id, data); +} + +Result NfcDevice::RecreateApplicationArea(u32 access_id, std::span<const u8> data) { + if (device_state != DeviceState::TagMounted) { + LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); + if (device_state == DeviceState::TagRemoved) { + return ResultTagRemoved; + } + return ResultWrongDeviceState; + } + + if (is_app_area_open) { + LOG_ERROR(Service_NFP, "Application area is open"); + return ResultWrongDeviceState; + } + + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { + LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); + return ResultWrongDeviceState; + } + + if (data.size() > sizeof(NFP::ApplicationArea)) { + LOG_ERROR(Service_NFP, "Wrong data size {}", data.size()); + return ResultWrongApplicationAreaSize; + } + + Common::TinyMT rng{}; + rng.Initialize(static_cast<u32>(GetCurrentPosixTime())); + std::memcpy(tag_data.application_area.data(), data.data(), data.size()); + // Fill remaining data with random numbers + rng.GenerateRandomBytes(tag_data.application_area.data() + data.size(), + sizeof(NFP::ApplicationArea) - data.size()); + + if (tag_data.application_write_counter != NFP::counter_limit) { + tag_data.application_write_counter++; + } + + const u64 application_id = system.GetApplicationProcessProgramID(); + + tag_data.application_id_byte = + static_cast<u8>(application_id >> NFP::application_id_version_offset & 0xf); + tag_data.application_id = + RemoveVersionByte(application_id) | (static_cast<u64>(NFP::AppAreaVersion::NintendoSwitch) + << NFP::application_id_version_offset); + tag_data.settings.settings.appdata_initialized.Assign(1); + tag_data.application_area_id = access_id; + tag_data.unknown = {}; + tag_data.unknown2 = {}; + + UpdateRegisterInfoCrc(); + + return Flush(); +} + +Result NfcDevice::DeleteApplicationArea() { + if (device_state != DeviceState::TagMounted) { + LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); + if (device_state == DeviceState::TagRemoved) { + return ResultTagRemoved; + } + return ResultWrongDeviceState; + } + + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { + LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); + return ResultWrongDeviceState; + } + + if (tag_data.settings.settings.appdata_initialized == 0) { + return ResultApplicationAreaIsNotInitialized; + } + + if (tag_data.application_write_counter != NFP::counter_limit) { + tag_data.application_write_counter++; + } + + Common::TinyMT rng{}; + rng.Initialize(static_cast<u32>(GetCurrentPosixTime())); + rng.GenerateRandomBytes(tag_data.application_area.data(), sizeof(NFP::ApplicationArea)); + rng.GenerateRandomBytes(&tag_data.application_id, sizeof(u64)); + rng.GenerateRandomBytes(&tag_data.application_area_id, sizeof(u32)); + rng.GenerateRandomBytes(&tag_data.application_id_byte, sizeof(u8)); + tag_data.settings.settings.appdata_initialized.Assign(0); + tag_data.unknown = {}; + tag_data.unknown2 = {}; + is_app_area_open = false; + + UpdateRegisterInfoCrc(); + + return Flush(); +} + +Result NfcDevice::ExistsApplicationArea(bool& has_application_area) const { + if (device_state != DeviceState::TagMounted) { + LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); + if (device_state == DeviceState::TagRemoved) { + return ResultTagRemoved; + } + return ResultWrongDeviceState; + } + + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { + LOG_ERROR(Service_NFC, "Amiibo is read only", device_state); + return ResultWrongDeviceState; + } + + has_application_area = tag_data.settings.settings.appdata_initialized.Value() != 0; + + return ResultSuccess; +} + +Result NfcDevice::GetAll(NFP::NfpData& data) const { + if (device_state != DeviceState::TagMounted) { + LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); + if (device_state == DeviceState::TagRemoved) { + return ResultTagRemoved; + } + return ResultWrongDeviceState; + } + + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { + LOG_ERROR(Service_NFC, "Amiibo is read only", device_state); + return ResultWrongDeviceState; + } + + NFP::CommonInfo common_info{}; + const u64 application_id = tag_data.application_id; + + GetCommonInfo(common_info); + + data = { + .magic = tag_data.constant_value, + .write_counter = tag_data.write_counter, + .settings_crc = tag_data.settings.crc, + .common_info = common_info, + .mii_char_info = tag_data.owner_mii, + .mii_store_data_extension = tag_data.mii_extension, + .creation_date = tag_data.settings.init_date.GetWriteDate(), + .amiibo_name = tag_data.settings.amiibo_name, + .amiibo_name_null_terminated = 0, + .settings = tag_data.settings.settings, + .unknown1 = tag_data.unknown, + .register_info_crc = tag_data.register_info_crc, + .unknown2 = tag_data.unknown2, + .application_id = application_id, + .access_id = tag_data.application_area_id, + .settings_crc_counter = tag_data.settings.crc_counter, + .font_region = tag_data.settings.settings.font_region, + .tag_type = PackedTagType::Type2, + .console_type = static_cast<NFP::AppAreaVersion>( + application_id >> NFP::application_id_version_offset & 0xf), + .application_id_byte = tag_data.application_id_byte, + .application_area = tag_data.application_area, + }; + + return ResultSuccess; +} + +Result NfcDevice::SetAll(const NFP::NfpData& data) { + if (device_state != DeviceState::TagMounted) { + LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); + if (device_state == DeviceState::TagRemoved) { + return ResultTagRemoved; + } + return ResultWrongDeviceState; + } + + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { + LOG_ERROR(Service_NFC, "Amiibo is read only", device_state); + return ResultWrongDeviceState; + } + + tag_data.constant_value = data.magic; + tag_data.write_counter = data.write_counter; + tag_data.settings.crc = data.settings_crc; + tag_data.settings.write_date.SetWriteDate(data.common_info.last_write_date); + tag_data.write_counter = data.common_info.write_counter; + tag_data.amiibo_version = data.common_info.version; + tag_data.owner_mii = data.mii_char_info; + tag_data.mii_extension = data.mii_store_data_extension; + tag_data.settings.init_date.SetWriteDate(data.creation_date); + tag_data.settings.amiibo_name = data.amiibo_name; + tag_data.settings.settings = data.settings; + tag_data.unknown = data.unknown1; + tag_data.register_info_crc = data.register_info_crc; + tag_data.unknown2 = data.unknown2; + tag_data.application_id = data.application_id; + tag_data.application_area_id = data.access_id; + tag_data.settings.crc_counter = data.settings_crc_counter; + tag_data.settings.settings.font_region.Assign(data.font_region); + tag_data.application_id_byte = data.application_id_byte; + tag_data.application_area = data.application_area; + + return ResultSuccess; +} + +Result NfcDevice::BreakTag(NFP::BreakType break_type) { + if (device_state != DeviceState::TagMounted) { + LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); + if (device_state == DeviceState::TagRemoved) { + return ResultTagRemoved; + } + return ResultWrongDeviceState; + } + + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { + LOG_ERROR(Service_NFC, "Amiibo is read only", device_state); + return ResultWrongDeviceState; + } + + // TODO: Complete this implementation + + return FlushWithBreak(break_type); +} + +Result NfcDevice::HasBackup(const UniqueSerialNumber& uid, std::size_t uuid_size) const { + ASSERT_MSG(uuid_size < sizeof(UniqueSerialNumber), "Invalid UUID size"); + constexpr auto backup_dir = "backup"; + const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir); + const auto file_name = + fmt::format("{0:02x}.bin", fmt::join(uid.begin(), uid.begin() + uuid_size, "")); + + if (!Common::FS::Exists(yuzu_amiibo_dir / backup_dir / file_name)) { + return ResultUnableToAccessBackupFile; + } + + return ResultSuccess; +} + +Result NfcDevice::HasBackup(const NFP::TagUuid& tag_uid) const { + UniqueSerialNumber uuid{}; + memcpy(uuid.data(), &tag_uid, sizeof(NFP::TagUuid)); + return HasBackup(uuid, sizeof(NFP::TagUuid)); +} + +Result NfcDevice::ReadBackupData(const UniqueSerialNumber& uid, std::size_t uuid_size, + std::span<u8> data) const { + ASSERT_MSG(uuid_size < sizeof(UniqueSerialNumber), "Invalid UUID size"); + constexpr auto backup_dir = "backup"; + const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir); + const auto file_name = + fmt::format("{0:02x}.bin", fmt::join(uid.begin(), uid.begin() + uuid_size, "")); + + const Common::FS::IOFile keys_file{yuzu_amiibo_dir / backup_dir / file_name, + Common::FS::FileAccessMode::Read, + Common::FS::FileType::BinaryFile}; + + if (!keys_file.IsOpen()) { + LOG_ERROR(Service_NFP, "Failed to open amiibo backup"); + return ResultUnableToAccessBackupFile; + } + + if (keys_file.Read(data) != data.size()) { + LOG_ERROR(Service_NFP, "Failed to read amiibo backup"); + return ResultUnableToAccessBackupFile; + } + + return ResultSuccess; +} + +Result NfcDevice::ReadBackupData(const NFP::TagUuid& tag_uid, std::span<u8> data) const { + UniqueSerialNumber uuid{}; + memcpy(uuid.data(), &tag_uid, sizeof(NFP::TagUuid)); + return ReadBackupData(uuid, sizeof(NFP::TagUuid), data); +} + +Result NfcDevice::WriteBackupData(const UniqueSerialNumber& uid, std::size_t uuid_size, + std::span<const u8> data) { + ASSERT_MSG(uuid_size < sizeof(UniqueSerialNumber), "Invalid UUID size"); + constexpr auto backup_dir = "backup"; + const auto yuzu_amiibo_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::AmiiboDir); + const auto file_name = + fmt::format("{0:02x}.bin", fmt::join(uid.begin(), uid.begin() + uuid_size, "")); + + if (HasBackup(uid, uuid_size).IsError()) { + if (!Common::FS::CreateDir(yuzu_amiibo_dir / backup_dir)) { + return ResultBackupPathAlreadyExist; + } + + if (!Common::FS::NewFile(yuzu_amiibo_dir / backup_dir / file_name)) { + return ResultBackupPathAlreadyExist; + } + } + + const Common::FS::IOFile keys_file{yuzu_amiibo_dir / backup_dir / file_name, + Common::FS::FileAccessMode::ReadWrite, + Common::FS::FileType::BinaryFile}; + + if (!keys_file.IsOpen()) { + LOG_ERROR(Service_NFP, "Failed to open amiibo backup"); + return ResultUnableToAccessBackupFile; + } + + if (keys_file.Write(data) != data.size()) { + LOG_ERROR(Service_NFP, "Failed to write amiibo backup"); + return ResultUnableToAccessBackupFile; + } + + return ResultSuccess; +} + +Result NfcDevice::WriteBackupData(const NFP::TagUuid& tag_uid, std::span<const u8> data) { + UniqueSerialNumber uuid{}; + memcpy(uuid.data(), &tag_uid, sizeof(NFP::TagUuid)); + return WriteBackupData(uuid, sizeof(NFP::TagUuid), data); +} + +Result NfcDevice::WriteNtf(std::span<const u8> data) { + if (device_state != DeviceState::TagMounted) { + LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); + if (device_state == DeviceState::TagRemoved) { + return ResultTagRemoved; + } + return ResultWrongDeviceState; + } + + if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) { + LOG_ERROR(Service_NFC, "Amiibo is read only", device_state); + return ResultWrongDeviceState; + } + + // Not implemented + + return ResultSuccess; +} + +NFP::AmiiboName NfcDevice::GetAmiiboName(const NFP::AmiiboSettings& settings) const { + std::array<char16_t, NFP::amiibo_name_length> settings_amiibo_name{}; + NFP::AmiiboName amiibo_name{}; + + // Convert from big endian to little endian + for (std::size_t i = 0; i < NFP::amiibo_name_length; i++) { + settings_amiibo_name[i] = static_cast<u16>(settings.amiibo_name[i]); + } + + // Convert from utf16 to utf8 + const auto amiibo_name_utf8 = Common::UTF16ToUTF8(settings_amiibo_name.data()); + memcpy(amiibo_name.data(), amiibo_name_utf8.data(), amiibo_name_utf8.size()); + + return amiibo_name; +} + +void NfcDevice::SetAmiiboName(NFP::AmiiboSettings& settings, + const NFP::AmiiboName& amiibo_name) const { + std::array<char16_t, NFP::amiibo_name_length> settings_amiibo_name{}; + + // Convert from utf8 to utf16 + const auto amiibo_name_utf16 = Common::UTF8ToUTF16(amiibo_name.data()); + memcpy(settings_amiibo_name.data(), amiibo_name_utf16.data(), + amiibo_name_utf16.size() * sizeof(char16_t)); + + // Convert from little endian to big endian + for (std::size_t i = 0; i < NFP::amiibo_name_length; i++) { + settings.amiibo_name[i] = static_cast<u16_be>(settings_amiibo_name[i]); + } +} + +NFP::AmiiboDate NfcDevice::GetAmiiboDate(s64 posix_time) const { + const auto& time_zone_manager = + system.GetTimeManager().GetTimeZoneContentManager().GetTimeZoneManager(); + Time::TimeZone::CalendarInfo calendar_info{}; + NFP::AmiiboDate amiibo_date{}; + + amiibo_date.SetYear(2000); + amiibo_date.SetMonth(1); + amiibo_date.SetDay(1); + + if (time_zone_manager.ToCalendarTime({}, posix_time, calendar_info) == ResultSuccess) { + amiibo_date.SetYear(calendar_info.time.year); + amiibo_date.SetMonth(calendar_info.time.month); + amiibo_date.SetDay(calendar_info.time.day); + } + + return amiibo_date; +} + +u64 NfcDevice::GetCurrentPosixTime() const { + auto& standard_steady_clock{system.GetTimeManager().GetStandardSteadyClockCore()}; + return standard_steady_clock.GetCurrentTimePoint(system).time_point; +} + +u64 NfcDevice::RemoveVersionByte(u64 application_id) const { + return application_id & ~(0xfULL << NFP::application_id_version_offset); +} + +void NfcDevice::UpdateSettingsCrc() { + auto& settings = tag_data.settings; + + if (settings.crc_counter != NFP::counter_limit) { + settings.crc_counter++; + } + + // TODO: this reads data from a global, find what it is + std::array<u8, 8> unknown_input{}; + boost::crc_32_type crc; + crc.process_bytes(&unknown_input, sizeof(unknown_input)); + settings.crc = crc.checksum(); +} + +void NfcDevice::UpdateRegisterInfoCrc() { +#pragma pack(push, 1) + struct CrcData { + Mii::Ver3StoreData mii; + u8 application_id_byte; + u8 unknown; + Mii::NfpStoreDataExtension mii_extension; + std::array<u32, 0x5> unknown2; + }; + static_assert(sizeof(CrcData) == 0x7e, "CrcData is an invalid size"); +#pragma pack(pop) + + const CrcData crc_data{ + .mii = tag_data.owner_mii, + .application_id_byte = tag_data.application_id_byte, + .unknown = tag_data.unknown, + .mii_extension = tag_data.mii_extension, + .unknown2 = tag_data.unknown2, + }; + + boost::crc_32_type crc; + crc.process_bytes(&crc_data, sizeof(CrcData)); + tag_data.register_info_crc = crc.checksum(); +} + +void NfcDevice::BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data, + const NFP::EncryptedNTAG215File& encrypted_file) const { + Service::Mii::MiiManager manager; + auto& settings = stubbed_tag_data.settings; + + stubbed_tag_data = NFP::AmiiboCrypto::NfcDataToEncodedData(encrypted_file); + + // Common info + stubbed_tag_data.write_counter = 0; + stubbed_tag_data.amiibo_version = 0; + settings.write_date = GetAmiiboDate(GetCurrentPosixTime()); + + // Register info + SetAmiiboName(settings, {'y', 'u', 'z', 'u', 'A', 'm', 'i', 'i', 'b', 'o'}); + settings.settings.font_region.Assign(0); + settings.init_date = GetAmiiboDate(GetCurrentPosixTime()); + stubbed_tag_data.owner_mii = manager.BuildFromStoreData(manager.BuildDefault(0)); + + // Admin info + settings.settings.amiibo_initialized.Assign(1); + settings.settings.appdata_initialized.Assign(0); +} + +u64 NfcDevice::GetHandle() const { + // Generate a handle based of the npad id + return static_cast<u64>(npad_id); +} + +DeviceState NfcDevice::GetCurrentState() const { + return device_state; +} + +Result NfcDevice::GetNpadId(Core::HID::NpadIdType& out_npad_id) const { + out_npad_id = npad_id; + return ResultSuccess; +} + +} // namespace Service::NFC |