// Copyright 2022 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #include #include #include #include #include "common/file_util.h" #include "common/logging/log.h" #include "core/core.h" #include "core/hle/kernel/shared_page.h" #include "core/hle/service/nfc/amiibo_crypto.h" #include "core/hle/service/nfc/nfc_device.h" #include "core/hw/aes/key.h" #include "core/loader/loader.h" SERVICE_CONSTRUCT_IMPL(Service::NFC::NfcDevice) namespace Service::NFC { template void NfcDevice::serialize(Archive& ar, const unsigned int) { ar& tag_in_range_event; ar& tag_out_of_range_event; ar& is_initalized; ar& is_data_moddified; ar& is_app_area_open; ar& is_plain_amiibo; ar& is_write_protected; ar& is_tag_in_range; ar& allowed_protocols; ar& device_state; ar& connection_state; ar& communication_state; ar& amiibo_filename; ar& tag; ar& encrypted_tag; } SERIALIZE_IMPL(NfcDevice) NfcDevice::NfcDevice(Core::System& system_) : 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"); remove_amiibo_event = system.CoreTiming().RegisterEvent( "remove amiibo event", [this](std::uintptr_t user_data, s64 cycles_late) { UnloadAmiibo(); }); } NfcDevice::~NfcDevice() = default; bool NfcDevice::LoadAmiibo(std::string filename) { FileUtil::IOFile amiibo_file(filename, "rb"); if (!is_initalized) { LOG_ERROR(Service_NFC, "Not initialized"); return false; } if (device_state != DeviceState::SearchingForTag) { LOG_ERROR(Service_NFC, "Game is not looking for amiibos, current state {}", device_state); return false; } if (!amiibo_file.IsOpen()) { LOG_ERROR(Service_NFC, "Could not open amiibo file \"{}\"", filename); return false; } if (!amiibo_file.ReadBytes(&tag.file, sizeof(tag.file))) { LOG_ERROR(Service_NFC, "Could not read amiibo data from file \"{}\"", filename); tag.file = {}; return false; } // TODO: Filter by allowed_protocols here is_plain_amiibo = AmiiboCrypto::IsAmiiboValid(tag.file); is_write_protected = false; amiibo_filename = filename; device_state = DeviceState::TagFound; is_tag_in_range = true; tag_out_of_range_event->Clear(); tag_in_range_event->Signal(); RescheduleTagRemoveEvent(); // Fallback for plain amiibos if (is_plain_amiibo) { LOG_INFO(Service_NFC, "Using plain amiibo"); encrypted_tag.file = AmiiboCrypto::EncodedDataToNfcData(tag.file); return true; } // Fallback for encrypted amiibos without keys if (!HW::AES::NfcSecretsAvailable()) { LOG_INFO(Service_NFC, "Loading amiibo without keys"); memcpy(&encrypted_tag.raw, &tag.raw, sizeof(EncryptedNTAG215File)); tag.file = {}; BuildAmiiboWithoutKeys(); is_plain_amiibo = true; is_write_protected = true; return true; } LOG_INFO(Service_NFC, "Loading amiibo with keys"); memcpy(&encrypted_tag.raw, &tag.raw, sizeof(EncryptedNTAG215File)); tag.file = {}; return true; } void NfcDevice::UnloadAmiibo() { is_tag_in_range = false; amiibo_filename = ""; CloseAmiibo(); } void NfcDevice::CloseAmiibo() { if (device_state == DeviceState::NotInitialized || device_state == DeviceState::Initialized || device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) { return; } LOG_INFO(Service_NFC, "Amiibo removed"); if (device_state == DeviceState::TagMounted || device_state == DeviceState::TagPartiallyMounted) { ResetTagScanState(); } device_state = DeviceState::TagRemoved; encrypted_tag.file = {}; tag.file = {}; tag_in_range_event->Clear(); tag_out_of_range_event->Signal(); } std::shared_ptr NfcDevice::GetActivateEvent() const { return tag_in_range_event; } std::shared_ptr NfcDevice::GetDeactivateEvent() const { return tag_out_of_range_event; } void NfcDevice::Initialize() { device_state = DeviceState::Initialized; encrypted_tag.file = {}; tag.file = {}; is_initalized = true; } void NfcDevice::Finalize() { if (device_state == DeviceState::TagMounted) { ResetTagScanState(); } if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) { StopDetection(); } device_state = DeviceState::NotInitialized; connection_state = ConnectionState::Success; communication_state = CommunicationState::Idle; is_initalized = false; } Result NfcDevice::StartCommunication() { const auto connection_result = CheckConnectionState(); if (connection_result.IsError()) { return connection_result; } if (device_state != DeviceState::Initialized || communication_state != CommunicationState::Idle) { return ResultInvalidOperation; } communication_state = CommunicationState::SearchingForAdapter; // This is a hack. This mode needs to change when the tag reader has completed the initalization communication_state = CommunicationState::Initialized; return ResultSuccess; } Result NfcDevice::StopCommunication() { const auto connection_result = CheckConnectionState(); if (connection_result.IsError()) { return connection_result; } if (communication_state == CommunicationState::Idle) { return ResultInvalidOperation; } if (device_state == DeviceState::TagMounted || device_state == DeviceState::TagPartiallyMounted) { ResetTagScanState(); } device_state = DeviceState::Initialized; communication_state = CommunicationState::Idle; return ResultSuccess; } Result NfcDevice::StartDetection(TagProtocol allowed_protocol) { const auto connection_result = CheckConnectionState(); if (connection_result.IsError()) { return connection_result; } if (device_state != DeviceState::Initialized && device_state != DeviceState::TagRemoved && communication_state != CommunicationState::Initialized) { return ResultInvalidOperation; } // Ensure external device is active if (communication_state == CommunicationState::Idle) { StartCommunication(); } device_state = DeviceState::SearchingForTag; allowed_protocols = allowed_protocol; if (is_tag_in_range) { LoadAmiibo(amiibo_filename); } return ResultSuccess; } Result NfcDevice::StopDetection() { const auto connection_result = CheckConnectionState(); if (connection_result.IsError()) { return connection_result; } if (device_state == DeviceState::TagMounted || device_state == DeviceState::TagPartiallyMounted) { ResetTagScanState(); } if (device_state == DeviceState::TagFound || device_state == DeviceState::SearchingForTag) { CloseAmiibo(); } if (device_state == DeviceState::TagFound || device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) { // TODO: Stop console search mode here device_state = DeviceState::Initialized; connection_state = ConnectionState::Success; return ResultSuccess; } LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); return ResultInvalidOperation; } Result NfcDevice::Flush() { if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); const auto connection_result = CheckConnectionState(); if (connection_result.IsSuccess()) { return ResultInvalidOperation; } return connection_result; } // Ensure the tag will not be removed in the middle of a write RescheduleTagRemoveEvent(); auto& settings = tag.file.settings; const auto& current_date = GetAmiiboDate(); if (settings.write_date.raw_date != current_date.raw_date) { settings.write_date = current_date; UpdateSettingsCrc(); } tag.file.write_counter++; if (is_write_protected) { LOG_ERROR(Service_NFC, "No keys available skipping write request"); return ResultSuccess; } if (!is_plain_amiibo) { if (!AmiiboCrypto::EncodeAmiibo(tag.file, encrypted_tag.file)) { LOG_ERROR(Service_NFC, "Failed to encode data"); return ResultOperationFailed; } if (amiibo_filename.empty()) { LOG_ERROR(Service_NFC, "Tried to use UpdateStoredAmiiboData on a nonexistant file."); return ResultOperationFailed; } } FileUtil::IOFile amiibo_file(amiibo_filename, "wb"); bool write_failed = false; if (!amiibo_file.IsOpen()) { LOG_ERROR(Service_NFC, "Could not open amiibo file \"{}\"", amiibo_filename); write_failed = true; } if (!is_plain_amiibo) { if (!write_failed && !amiibo_file.WriteBytes(&encrypted_tag.file, sizeof(EncryptedNTAG215File))) { LOG_ERROR(Service_NFC, "Could not write to amiibo file \"{}\"", amiibo_filename); write_failed = true; } } else { if (!write_failed && !amiibo_file.WriteBytes(&tag.file, sizeof(EncryptedNTAG215File))) { LOG_ERROR(Service_NFC, "Could not write to amiibo file \"{}\"", amiibo_filename); write_failed = true; } } amiibo_file.Close(); if (write_failed) { return ResultOperationFailed; } is_data_moddified = false; return ResultSuccess; } Result NfcDevice::Mount() { if (device_state != DeviceState::TagFound) { LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); const auto connection_result = CheckConnectionState(); if (connection_result.IsSuccess()) { return ResultInvalidOperation; } return connection_result; } if (!AmiiboCrypto::IsAmiiboValid(encrypted_tag.file)) { LOG_ERROR(Service_NFC, "Not an amiibo"); return ResultNotSupported; } // The loaded amiibo is not encrypted if (is_plain_amiibo) { device_state = DeviceState::TagMounted; return ResultSuccess; } if (!AmiiboCrypto::DecodeAmiibo(encrypted_tag.file, tag.file)) { LOG_ERROR(Service_NFC, "Can't decode amiibo {}", device_state); return ResultNeedFormat; } device_state = DeviceState::TagMounted; return ResultSuccess; } Result NfcDevice::MountAmiibo() { TagInfo tag_info{}; const auto result = GetTagInfo(tag_info); if (result.IsError()) { return result; } if (tag_info.tag_type != PackedTagType::Type2) { return ResultNotSupported; } return Mount(); } Result NfcDevice::PartiallyMount() { if (device_state != DeviceState::TagFound) { LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); const auto connection_result = CheckConnectionState(); if (connection_result.IsSuccess()) { return ResultInvalidOperation; } return connection_result; } if (!AmiiboCrypto::IsAmiiboValid(encrypted_tag.file)) { LOG_ERROR(Service_NFC, "Not an amiibo"); return ResultNotSupported; } // The loaded amiibo is not encrypted if (is_plain_amiibo) { device_state = DeviceState::TagPartiallyMounted; return ResultSuccess; } if (!AmiiboCrypto::DecodeAmiibo(encrypted_tag.file, tag.file)) { LOG_ERROR(Service_NFC, "Can't decode amiibo {}", device_state); return ResultNeedFormat; } device_state = DeviceState::TagPartiallyMounted; return ResultSuccess; } Result NfcDevice::PartiallyMountAmiibo() { TagInfo tag_info{}; const auto result = GetTagInfo(tag_info); if (result.IsError()) { return result; } if (tag_info.tag_type != PackedTagType::Type2) { return ResultNotSupported; } return PartiallyMount(); } Result NfcDevice::ResetTagScanState() { if (device_state != DeviceState::TagMounted && device_state != DeviceState::TagPartiallyMounted) { LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); const auto connection_result = CheckConnectionState(); if (connection_result.IsSuccess()) { return ResultInvalidOperation; } return connection_result; } device_state = DeviceState::TagFound; is_app_area_open = false; return ResultSuccess; } Result NfcDevice::GetTagInfo2(TagInfo2& tag_info) const { tag_info = { .uuid_length = static_cast(encrypted_tag.file.uuid.uid.size()), .tag_type = PackedTagType::Type2, .uuid = encrypted_tag.file.uuid.uid, .extra_data = {}, // Used on non amiibo tags .protocol = TagProtocol::None, .extra_data2 = {}, // Used on non amiibo tags }; return ResultSuccess; } Result NfcDevice::GetTagInfo(TagInfo& tag_info) const { if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted && device_state != DeviceState::TagPartiallyMounted) { LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); const auto connection_result = CheckConnectionState(); if (connection_result.IsSuccess()) { return ResultInvalidOperation; } return connection_result; } tag_info = { .uuid_length = static_cast(encrypted_tag.file.uuid.uid.size()), .protocol = PackedTagProtocol::None, .tag_type = PackedTagType::Type2, .uuid = encrypted_tag.file.uuid.uid, .extra_data = {}, // Used on non amiibo tags }; return ResultSuccess; } Result NfcDevice::GetCommonInfo(CommonInfo& common_info) const { if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); const auto connection_result = CheckConnectionState(); if (connection_result.IsSuccess()) { return ResultInvalidOperation; } return connection_result; } const auto& settings = tag.file.settings; const auto& model_info_data = tag.file.model_info; // TODO: Validate this data common_info = { .last_write_date = settings.write_date.GetWriteDate(), .application_write_counter = tag.file.application_write_counter, .character_id = model_info_data.character_id, .character_variant = model_info_data.character_variant, .series = model_info_data.series, .model_number = model_info_data.model_number, .amiibo_type = model_info_data.amiibo_type, .version = tag.file.amiibo_version, .application_area_size = sizeof(ApplicationArea), }; return ResultSuccess; } Result NfcDevice::GetModelInfo(ModelInfo& model_info) const { if (device_state != DeviceState::TagMounted && device_state != DeviceState::TagPartiallyMounted) { LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); const auto connection_result = CheckConnectionState(); if (connection_result.IsSuccess()) { return ResultInvalidOperation; } return connection_result; } const auto& model_info_data = encrypted_tag.file.user_memory.model_info; model_info = { .character_id = model_info_data.character_id, .character_variant = model_info_data.character_variant, .series = model_info_data.series, .model_number = model_info_data.model_number, .amiibo_type = model_info_data.amiibo_type, }; return ResultSuccess; } Result NfcDevice::GetRegisterInfo(RegisterInfo& register_info) const { if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); const auto connection_result = CheckConnectionState(); if (connection_result.IsSuccess()) { return ResultInvalidOperation; } return connection_result; } if (tag.file.settings.settings.amiibo_initialized == 0) { return ResultNeedRegister; } const auto& settings = tag.file.settings; // TODO: Validate this data register_info = { .mii_data = tag.file.owner_mii, .amiibo_name = settings.amiibo_name, .flags = static_cast(settings.settings.raw & 0xf), .font_region = settings.country_code_id, .creation_date = settings.init_date.GetWriteDate(), }; return ResultSuccess; } Result NfcDevice::GetAdminInfo(AdminInfo& admin_info) const { if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); const auto connection_result = CheckConnectionState(); if (connection_result.IsSuccess()) { return ResultInvalidOperation; } return connection_result; } u8 flags = static_cast(tag.file.settings.settings.raw >> 0x4); if (tag.file.settings.settings.amiibo_initialized == 0) { flags = flags & 0xfe; } u64 application_id = 0; u32 application_area_id = 0; AppAreaVersion app_area_version = AppAreaVersion::NotSet; if (tag.file.settings.settings.appdata_initialized != 0) { application_id = tag.file.application_id; app_area_version = static_cast(application_id >> application_id_version_offset & 0xf); switch (app_area_version) { case AppAreaVersion::Nintendo3DS: case AppAreaVersion::Nintendo3DSv2: app_area_version = AppAreaVersion::Nintendo3DS; break; case AppAreaVersion::NintendoWiiU: app_area_version = AppAreaVersion::NintendoWiiU; break; default: app_area_version = AppAreaVersion::NotSet; break; } // Restore application id to original value if (application_id >> 0x38 != 0) { const u8 application_byte = tag.file.application_id_byte & 0xf; application_id = RemoveVersionByte(application_id) | (static_cast(application_byte) << application_id_version_offset); } application_area_id = tag.file.application_area_id; } // TODO: Validate this data admin_info = { .application_id = application_id, .application_area_id = application_area_id, .crc_counter = tag.file.settings.crc_counter, .flags = flags, .tag_type = PackedTagType::Type2, .app_area_version = app_area_version, }; return ResultSuccess; } Result NfcDevice::DeleteRegisterInfo() { // This is a hack to get around a HW issue if (device_state == DeviceState::TagFound) { Mount(); } if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); const auto connection_result = CheckConnectionState(); if (connection_result.IsSuccess()) { return ResultInvalidOperation; } return connection_result; } if (tag.file.settings.settings.amiibo_initialized == 0) { return ResultNeedRegister; } CryptoPP::AutoSeededRandomPool rng; const std::size_t mii_data_size = sizeof(tag.file.owner_mii); std::array buffer{}; rng.GenerateBlock(buffer.data(), mii_data_size); memcpy(&tag.file.owner_mii, buffer.data(), mii_data_size); memcpy(&tag.file.settings.amiibo_name, buffer.data(), sizeof(tag.file.settings.amiibo_name)); tag.file.unknown = rng.GenerateByte(); tag.file.unknown2[0] = rng.GenerateWord32(); tag.file.unknown2[1] = rng.GenerateWord32(); tag.file.register_info_crc = rng.GenerateWord32(); tag.file.settings.init_date.raw_date = static_cast(rng.GenerateWord32()); tag.file.settings.settings.font_region.Assign(0); tag.file.settings.settings.amiibo_initialized.Assign(0); tag.file.settings.country_code_id = 0; return Flush(); } Result NfcDevice::SetRegisterInfoPrivate(const RegisterInfoPrivate& register_info) { if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); const auto connection_result = CheckConnectionState(); if (connection_result.IsSuccess()) { return ResultInvalidOperation; } return connection_result; } auto& settings = tag.file.settings; if (tag.file.settings.settings.amiibo_initialized == 0) { settings.init_date = GetAmiiboDate(); settings.write_date = GetAmiiboDate(); } settings.amiibo_name = register_info.amiibo_name; tag.file.owner_mii = register_info.mii_data; tag.file.owner_mii.FixChecksum(); tag.file.mii_extension = {}; tag.file.unknown = 0; tag.file.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_NFC, "Wrong device state {}", device_state); const auto connection_result = CheckConnectionState(); if (connection_result.IsSuccess()) { return ResultInvalidOperation; } return connection_result; } // TODO: Load amiibo from backup on system LOG_ERROR(Service_NFC, "Not Implemented"); return ResultSuccess; } Result NfcDevice::Format() { auto Result = DeleteApplicationArea(); auto Result2 = DeleteRegisterInfo(); if (Result.IsError()) { return Result; } if (Result2.IsError()) { return Result2; } return ResultSuccess; } Result NfcDevice::OpenApplicationArea(u32 access_id) { if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); const auto connection_result = CheckConnectionState(); if (connection_result.IsSuccess()) { return ResultInvalidOperation; } return connection_result; } if (tag.file.settings.settings.appdata_initialized.Value() == 0) { LOG_WARNING(Service_NFC, "Application area is not initialized"); return ResultNeedCreate; } if (tag.file.application_area_id != access_id) { LOG_WARNING(Service_NFC, "Wrong application area id"); return ResultAccessIdMisMatch; } 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_NFC, "Wrong device state {}", device_state); const auto connection_result = CheckConnectionState(); if (connection_result.IsSuccess()) { return ResultInvalidOperation; } return connection_result; } if (tag.file.settings.settings.appdata_initialized.Value() == 0) { LOG_WARNING(Service_NFC, "Application area is not initialized"); return ResultNeedCreate; } application_area_id = tag.file.application_area_id; return ResultSuccess; } Result NfcDevice::GetApplicationArea(std::vector& data) const { if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); const auto connection_result = CheckConnectionState(); if (connection_result.IsSuccess()) { return ResultInvalidOperation; } return connection_result; } if (!is_app_area_open) { LOG_ERROR(Service_NFC, "Application area is not open"); return ResultInvalidOperation; } if (tag.file.settings.settings.appdata_initialized.Value() == 0) { LOG_ERROR(Service_NFC, "Application area is not initialized"); return ResultNeedCreate; } if (data.size() > sizeof(ApplicationArea)) { data.resize(sizeof(ApplicationArea)); } memcpy(data.data(), tag.file.application_area.data(), data.size()); return ResultSuccess; } Result NfcDevice::SetApplicationArea(std::span data) { if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); const auto connection_result = CheckConnectionState(); if (connection_result.IsSuccess()) { return ResultInvalidOperation; } return connection_result; } if (data.size() == 0) { LOG_ERROR(Service_NFC, "Data buffer is null"); return ResultInvalidArgument; } if (data.size() > sizeof(ApplicationArea)) { LOG_ERROR(Service_NFC, "Wrong data size {}", data.size()); return ResultInvalidArgumentValue; } if (!is_app_area_open) { LOG_ERROR(Service_NFC, "Application area is not open"); return ResultInvalidOperation; } if (tag.file.settings.settings.appdata_initialized.Value() == 0) { LOG_ERROR(Service_NFC, "Application area is not initialized"); return ResultNeedCreate; } std::memcpy(tag.file.application_area.data(), data.data(), data.size()); // Fill remaining data with random numbers CryptoPP::AutoSeededRandomPool rng; const std::size_t data_size = sizeof(ApplicationArea) - data.size(); std::vector buffer(data_size); rng.GenerateBlock(buffer.data(), data_size); memcpy(tag.file.application_area.data() + data.size(), buffer.data(), data_size); if (tag.file.application_write_counter != counter_limit) { tag.file.application_write_counter++; } is_data_moddified = true; return ResultSuccess; } Result NfcDevice::CreateApplicationArea(u32 access_id, std::span data) { if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); const auto connection_result = CheckConnectionState(); if (connection_result.IsSuccess()) { return ResultInvalidOperation; } return connection_result; } if (tag.file.settings.settings.appdata_initialized.Value() != 0) { LOG_ERROR(Service_NFC, "Application area already exist"); return ResultAlreadyCreated; } return RecreateApplicationArea(access_id, data); } Result NfcDevice::RecreateApplicationArea(u32 access_id, std::span data) { if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); const auto connection_result = CheckConnectionState(); if (connection_result.IsSuccess()) { return ResultInvalidOperation; } return connection_result; } if (data.size() > sizeof(ApplicationArea)) { LOG_ERROR(Service_NFC, "Wrong data size {}", data.size()); return ResultInvalidArgumentValue; } if (is_app_area_open) { LOG_ERROR(Service_NFC, "Application area is open"); return ResultInvalidOperation; } std::memcpy(tag.file.application_area.data(), data.data(), data.size()); // Fill remaining data with random numbers CryptoPP::AutoSeededRandomPool rng; const std::size_t data_size = sizeof(ApplicationArea) - data.size(); std::vector buffer(data_size); rng.GenerateBlock(buffer.data(), data_size); memcpy(tag.file.application_area.data() + data.size(), buffer.data(), data_size); if (tag.file.application_write_counter != counter_limit) { tag.file.application_write_counter++; } u64 application_id{}; if (system.GetAppLoader().ReadProgramId(application_id) == Loader::ResultStatus::Success) { tag.file.application_id_byte = static_cast(application_id >> application_id_version_offset & 0xf); tag.file.application_id = RemoveVersionByte(application_id) | (static_cast(AppAreaVersion::Nintendo3DSv2) << application_id_version_offset); } tag.file.settings.settings.appdata_initialized.Assign(1); tag.file.application_area_id = access_id; tag.file.unknown = {}; tag.file.unknown2 = {}; UpdateRegisterInfoCrc(); return Flush(); } Result NfcDevice::DeleteApplicationArea() { // This is a hack to get around a HW issue if (device_state == DeviceState::TagFound) { Mount(); } if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); const auto connection_result = CheckConnectionState(); if (connection_result.IsSuccess()) { return ResultInvalidOperation; } return connection_result; } if (tag.file.settings.settings.appdata_initialized == 0) { return ResultNeedCreate; } CryptoPP::AutoSeededRandomPool rng; constexpr std::size_t data_size = sizeof(ApplicationArea); std::array buffer{}; rng.GenerateBlock(buffer.data(), data_size); if (tag.file.application_write_counter != counter_limit) { tag.file.application_write_counter++; } // Reset data with random bytes memcpy(tag.file.application_area.data(), buffer.data(), data_size); // memcpy(&tag.file.application_id, buffer.data(), sizeof(u64)); tag.file.application_area_id = rng.GenerateWord32(); tag.file.application_id_byte = rng.GenerateByte(); tag.file.settings.settings.appdata_initialized.Assign(0); tag.file.unknown = {}; tag.file.unknown2 = {}; is_app_area_open = false; UpdateRegisterInfoCrc(); return Flush(); } Result NfcDevice::ApplicationAreaExist(bool& has_application_area) { if (device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); const auto connection_result = CheckConnectionState(); if (connection_result.IsSuccess()) { return ResultInvalidOperation; } return connection_result; } has_application_area = tag.file.settings.settings.appdata_initialized.Value() != 0; return ResultSuccess; } constexpr u32 NfcDevice::GetApplicationAreaSize() const { return sizeof(ApplicationArea); } DeviceState NfcDevice::GetCurrentState() const { return device_state; } Result NfcDevice::GetCommunicationStatus(CommunicationState& status) const { if (communication_state == CommunicationState::Idle || communication_state == CommunicationState::SearchingForAdapter) { status = communication_state; return ResultSuccess; } if (communication_state == CommunicationState::Initialized || communication_state == CommunicationState::Active) { status = CommunicationState::Initialized; return ResultSuccess; } return ResultInvalidOperation; } Result NfcDevice::CheckConnectionState() const { if (connection_state == ConnectionState::Lost) { return ResultSleep; } if (connection_state == ConnectionState::NoAdapter) { return ResultWifiOff; } return ResultSuccess; } void NfcDevice::SetAmiiboName(AmiiboSettings& settings, const AmiiboName& amiibo_name) { // Convert from little endian to big endian for (std::size_t i = 0; i < amiibo_name_length; i++) { settings.amiibo_name[i] = static_cast(amiibo_name[i]); } } time_t NfcDevice::GetCurrentTime() const { auto& share_page = system.Kernel().GetSharedPageHandler(); const auto console_time = share_page.GetSharedPage().date_time_1.date_time / 1000; // 3DS console time uses Jan 1 1900 as internal epoch, // so we use the seconds between 1900 and 2000 as base console time constexpr u64 START_DATE = 3155673600ULL; // 3DS system does't allow user to set a time before Jan 1 2000, // so we use it as an auxiliary epoch to calculate the console time. std::tm epoch_tm; epoch_tm.tm_sec = 0; epoch_tm.tm_min = 0; epoch_tm.tm_hour = 0; epoch_tm.tm_mday = 1; epoch_tm.tm_mon = 0; epoch_tm.tm_year = 100; epoch_tm.tm_isdst = 0; s64 epoch = std::mktime(&epoch_tm); return static_cast(console_time - START_DATE + epoch); } AmiiboDate NfcDevice::GetAmiiboDate() const { auto now = GetCurrentTime(); tm local_tm = *localtime(&now); AmiiboDate amiibo_date{}; amiibo_date.SetYear(static_cast(local_tm.tm_year + 1900)); amiibo_date.SetMonth(static_cast(local_tm.tm_mon + 1)); amiibo_date.SetDay(static_cast(local_tm.tm_mday)); return amiibo_date; } u64 NfcDevice::RemoveVersionByte(u64 application_id) const { return application_id & ~(0xfULL << application_id_version_offset); } void NfcDevice::UpdateSettingsCrc() { auto& settings = tag.file.settings; if (settings.crc_counter != counter_limit) { settings.crc_counter++; } // TODO: this reads data from a global, find what it is std::array 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::ChecksummedMiiData mii; u8 application_id_byte; u8 unknown; u64 mii_extension; std::array unknown2; }; static_assert(sizeof(CrcData) == 0x7e, "CrcData is an invalid size"); #pragma pack(pop) const CrcData crc_data{ .mii = tag.file.owner_mii, .application_id_byte = tag.file.application_id_byte, .unknown = tag.file.unknown, .mii_extension = tag.file.mii_extension, .unknown2 = tag.file.unknown2, }; boost::crc_32_type crc; crc.process_bytes(&crc_data, sizeof(CrcData)); tag.file.register_info_crc = crc.checksum(); } void NfcDevice::BuildAmiiboWithoutKeys() { auto& settings = tag.file.settings; const auto default_mii = HLE::Applets::MiiSelector::GetStandardMiiResult(); tag.file = AmiiboCrypto::NfcDataToEncodedData(encrypted_tag.file); // Common info tag.file.write_counter = 0; tag.file.amiibo_version = 0; settings.write_date = GetAmiiboDate(); // Register info SetAmiiboName(settings, {'c', 'i', 't', 'r', 'A', 'm', 'i', 'i', 'b', 'o'}); settings.settings.font_region.Assign(0); settings.init_date = GetAmiiboDate(); tag.file.owner_mii = default_mii.selected_mii_data; // Admin info settings.settings.amiibo_initialized.Assign(1); settings.settings.appdata_initialized.Assign(0); } void NfcDevice::RescheduleTagRemoveEvent() { /// The interval at which the amiibo will be removed automatically 3s static constexpr u64 amiibo_removal_interval = msToCycles(3 * 1000); system.CoreTiming().UnscheduleEvent(remove_amiibo_event, 0); if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted && device_state != DeviceState::TagPartiallyMounted) { return; } system.CoreTiming().ScheduleEvent(amiibo_removal_interval, remove_amiibo_event); } } // namespace Service::NFC