diff options
Diffstat (limited to 'src/core/hle')
23 files changed, 618 insertions, 203 deletions
diff --git a/src/core/hle/kernel/k_thread.cpp b/src/core/hle/kernel/k_thread.cpp index 70480b725..908811e2c 100644 --- a/src/core/hle/kernel/k_thread.cpp +++ b/src/core/hle/kernel/k_thread.cpp @@ -4,6 +4,8 @@ #include <algorithm> #include <atomic> #include <cinttypes> +#include <condition_variable> +#include <mutex> #include <optional> #include <vector> @@ -1313,7 +1315,8 @@ void KThread::RequestDummyThreadWait() { ASSERT(this->IsDummyThread()); // We will block when the scheduler lock is released. - m_dummy_thread_runnable.store(false); + std::scoped_lock lock{m_dummy_thread_mutex}; + m_dummy_thread_runnable = false; } void KThread::DummyThreadBeginWait() { @@ -1323,7 +1326,8 @@ void KThread::DummyThreadBeginWait() { } // Block until runnable is no longer false. - m_dummy_thread_runnable.wait(false); + std::unique_lock lock{m_dummy_thread_mutex}; + m_dummy_thread_cv.wait(lock, [this] { return m_dummy_thread_runnable; }); } void KThread::DummyThreadEndWait() { @@ -1331,8 +1335,11 @@ void KThread::DummyThreadEndWait() { ASSERT(this->IsDummyThread()); // Wake up the waiting thread. - m_dummy_thread_runnable.store(true); - m_dummy_thread_runnable.notify_one(); + { + std::scoped_lock lock{m_dummy_thread_mutex}; + m_dummy_thread_runnable = true; + } + m_dummy_thread_cv.notify_one(); } void KThread::BeginWait(KThreadQueue* queue) { diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h index f9814ac8f..37fe5db77 100644 --- a/src/core/hle/kernel/k_thread.h +++ b/src/core/hle/kernel/k_thread.h @@ -892,7 +892,9 @@ private: std::shared_ptr<Common::Fiber> m_host_context{}; ThreadType m_thread_type{}; StepState m_step_state{}; - std::atomic<bool> m_dummy_thread_runnable{true}; + bool m_dummy_thread_runnable{true}; + std::mutex m_dummy_thread_mutex{}; + std::condition_variable m_dummy_thread_cv{}; // For debugging std::vector<KSynchronizationObject*> m_wait_objects_for_debugging{}; diff --git a/src/core/hle/service/nfc/common/amiibo_crypto.cpp b/src/core/hle/service/nfc/common/amiibo_crypto.cpp index b2bcb68c3..bc232c334 100644 --- a/src/core/hle/service/nfc/common/amiibo_crypto.cpp +++ b/src/core/hle/service/nfc/common/amiibo_crypto.cpp @@ -36,12 +36,12 @@ bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) { // 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]) { + if ((CT ^ ntag_file.uuid.part1[0] ^ ntag_file.uuid.part1[1] ^ ntag_file.uuid.part1[2]) != + ntag_file.uuid.crc_check1) { 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]) { + if ((ntag_file.uuid.part2[0] ^ ntag_file.uuid.part2[1] ^ ntag_file.uuid.part2[2] ^ + ntag_file.uuid.nintendo_id) != ntag_file.uuid_crc_check2) { return false; } @@ -74,8 +74,9 @@ bool IsAmiiboValid(const NTAG215File& 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.uid = nfc_data.uuid; + encoded_data.uid_crc_check2 = nfc_data.uuid_crc_check2; + encoded_data.internal_number = nfc_data.internal_number; 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; @@ -94,7 +95,6 @@ NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) { 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; @@ -108,9 +108,9 @@ NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_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.uuid = encoded_data.uid; + nfc_data.uuid_crc_check2 = encoded_data.uid_crc_check2; + nfc_data.internal_number = encoded_data.internal_number; 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; @@ -139,23 +139,12 @@ EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) { return nfc_data; } -u32 GetTagPassword(const TagUuid& uuid) { - // Verify that the generated password is correct - u32 password = 0xAA ^ (uuid.uid[1] ^ uuid.uid[3]); - password &= (0x55 ^ (uuid.uid[2] ^ uuid.uid[4])) << 8; - password &= (0xAA ^ (uuid.uid[3] ^ uuid.uid[5])) << 16; - password &= (0x55 ^ (uuid.uid[4] ^ uuid.uid[6])) << 24; - return password; -} - HashSeed GetSeed(const NTAG215File& data) { HashSeed seed{ .magic = data.write_counter, .padding = {}, .uid_1 = data.uid, - .nintendo_id_1 = data.nintendo_id, .uid_2 = data.uid, - .nintendo_id_2 = data.nintendo_id, .keygen_salt = data.keygen_salt, }; @@ -177,10 +166,11 @@ std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed 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); + std::array<u8, sizeof(NFP::TagUuid)> seed_uuid{}; + memcpy(seed_uuid.data(), &seed.uid_1, sizeof(NFP::TagUuid)); + output.insert(output.end(), seed_uuid.begin(), seed_uuid.end()); + memcpy(seed_uuid.data(), &seed.uid_2, sizeof(NFP::TagUuid)); + output.insert(output.end(), seed_uuid.begin(), seed_uuid.end()); 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])); @@ -264,8 +254,8 @@ void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& ou // 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.uid_crc_check2 = in_data.uid_crc_check2; + out_data.internal_number = in_data.internal_number; out_data.static_lock = in_data.static_lock; out_data.compability_container = in_data.compability_container; diff --git a/src/core/hle/service/nfc/common/amiibo_crypto.h b/src/core/hle/service/nfc/common/amiibo_crypto.h index bf3044ed9..6a3e0841e 100644 --- a/src/core/hle/service/nfc/common/amiibo_crypto.h +++ b/src/core/hle/service/nfc/common/amiibo_crypto.h @@ -24,10 +24,8 @@ using DrgbOutput = std::array<u8, 0x20>; struct HashSeed { u16_be magic; std::array<u8, 0xE> padding; - NFC::UniqueSerialNumber uid_1; - u8 nintendo_id_1; - NFC::UniqueSerialNumber uid_2; - u8 nintendo_id_2; + TagUuid uid_1; + TagUuid uid_2; std::array<u8, 0x20> keygen_salt; }; static_assert(sizeof(HashSeed) == 0x40, "HashSeed is an invalid size"); @@ -69,9 +67,6 @@ NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data); /// Converts from encoded file format to encrypted file format EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data); -/// Returns password needed to allow write access to protected memory -u32 GetTagPassword(const TagUuid& uuid); - // Generates Seed needed for key derivation HashSeed GetSeed(const NTAG215File& data); diff --git a/src/core/hle/service/nfc/common/device.cpp b/src/core/hle/service/nfc/common/device.cpp index 0bd7900e1..f4b180b06 100644 --- a/src/core/hle/service/nfc/common/device.cpp +++ b/src/core/hle/service/nfc/common/device.cpp @@ -12,6 +12,11 @@ #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" @@ -136,7 +141,7 @@ bool NfcDevice::LoadNfcTag(std::span<const u8> data) { if (!NFP::AmiiboCrypto::IsKeyAvailable()) { LOG_INFO(Service_NFC, "Loading amiibo without keys"); memcpy(&encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File)); - BuildAmiiboWithoutKeys(); + BuildAmiiboWithoutKeys(tag_data, encrypted_tag_data); is_plain_amiibo = true; is_write_protected = true; return true; @@ -237,34 +242,39 @@ Result NfcDevice::GetTagInfo(NFP::TagInfo& tag_info, bool is_mifare) const { return ResultWrongDeviceState; } - UniqueSerialNumber uuid = encrypted_tag_data.uuid.uid; - - // Generate random UUID to bypass amiibo load limits - if (Settings::values.random_amiibo_id) { - Common::TinyMT rng{}; - rng.Initialize(static_cast<u32>(GetCurrentPosixTime())); - rng.GenerateRandomBytes(uuid.data(), sizeof(UniqueSerialNumber)); - uuid[3] = 0x88 ^ uuid[0] ^ uuid[1] ^ uuid[2]; - } + UniqueSerialNumber uuid{}; + u8 uuid_length{}; + NfcProtocol protocol{NfcProtocol::TypeA}; + TagType tag_type{TagType::Type2}; if (is_mifare) { - tag_info = { - .uuid = uuid, - .uuid_extension = {}, - .uuid_length = static_cast<u8>(uuid.size()), - .protocol = NfcProtocol::TypeA, - .tag_type = TagType::Type4, + tag_type = TagType::Mifare; + uuid_length = sizeof(NFP::NtagTagUuid); + memcpy(uuid.data(), mifare_data.data(), uuid_length); + } else { + tag_type = TagType::Type2; + uuid_length = sizeof(NFP::NtagTagUuid); + NFP::NtagTagUuid nUuid{ + .part1 = encrypted_tag_data.uuid.part1, + .part2 = encrypted_tag_data.uuid.part2, + .nintendo_id = encrypted_tag_data.uuid.nintendo_id, }; - return ResultSuccess; + memcpy(uuid.data(), &nUuid, uuid_length); + + // Generate random UUID to bypass amiibo load limits + if (Settings::values.random_amiibo_id) { + Common::TinyMT rng{}; + rng.Initialize(static_cast<u32>(GetCurrentPosixTime())); + rng.GenerateRandomBytes(uuid.data(), uuid_length); + } } // Protocol and tag type may change here tag_info = { .uuid = uuid, - .uuid_extension = {}, - .uuid_length = static_cast<u8>(uuid.size()), - .protocol = NfcProtocol::TypeA, - .tag_type = TagType::Type2, + .uuid_length = uuid_length, + .protocol = protocol, + .tag_type = tag_type, }; return ResultSuccess; @@ -272,8 +282,38 @@ Result NfcDevice::GetTagInfo(NFP::TagInfo& tag_info, bool is_mifare) const { 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, true); + + 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; + } + } + for (std::size_t i = 0; i < parameters.size(); i++) { result = ReadMifare(parameters[i], read_block_data[i]); if (result.IsError()) { @@ -288,17 +328,8 @@ Result NfcDevice::ReadMifare(const MifareReadBlockParameter& parameter, MifareReadBlockData& read_block_data) const { const std::size_t sector_index = parameter.sector_number * sizeof(DataBlock); read_block_data.sector_number = parameter.sector_number; - - 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; - } - if (mifare_data.size() < sector_index + sizeof(DataBlock)) { - return Mifare::ResultReadError; + return ResultMifareError288; } // TODO: Use parameter.sector_key to read encrypted data @@ -310,6 +341,28 @@ Result NfcDevice::ReadMifare(const MifareReadBlockParameter& parameter, Result NfcDevice::WriteMifare(std::span<const MifareWriteBlockParameter> parameters) { Result result = ResultSuccess; + TagInfo tag_info{}; + result = GetTagInfo(tag_info, true); + + 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; + } + } + for (std::size_t i = 0; i < parameters.size(); i++) { result = WriteMifare(parameters[i]); if (result.IsError()) { @@ -319,7 +372,7 @@ Result NfcDevice::WriteMifare(std::span<const MifareWriteBlockParameter> paramet if (!npad_device->WriteNfc(mifare_data)) { LOG_ERROR(Service_NFP, "Error writing to file"); - return Mifare::ResultReadError; + return ResultMifareError288; } return result; @@ -337,7 +390,7 @@ Result NfcDevice::WriteMifare(const MifareWriteBlockParameter& parameter) { } if (mifare_data.size() < sector_index + sizeof(DataBlock)) { - return Mifare::ResultReadError; + return ResultMifareError288; } // TODO: Use parameter.sector_key to encrypt the data @@ -361,21 +414,30 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target if (!NFP::AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) { LOG_ERROR(Service_NFP, "Not an amiibo"); - return ResultNotAnAmiibo; + 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)) { - LOG_ERROR(Service_NFP, "Can't decode amiibo {}", device_state); - return ResultCorruptedData; + 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; @@ -470,6 +532,7 @@ Result NfcDevice::FlushWithBreak(NFP::BreakType break_type) { 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"); @@ -477,6 +540,7 @@ Result NfcDevice::FlushWithBreak(NFP::BreakType break_type) { } memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data)); + WriteBackupData(encrypted_tag_data.uuid, data); } if (!npad_device->WriteNfc(data)) { @@ -488,7 +552,7 @@ Result NfcDevice::FlushWithBreak(NFP::BreakType break_type) { } Result NfcDevice::Restore() { - if (device_state != DeviceState::TagMounted) { + if (device_state != DeviceState::TagFound) { LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); if (device_state == DeviceState::TagRemoved) { return ResultTagRemoved; @@ -496,13 +560,59 @@ Result NfcDevice::Restore() { 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; + NFC::TagInfo tag_info{}; + std::array<u8, sizeof(NFP::EncryptedNTAG215File)> data{}; + Result result = GetTagInfo(tag_info, false); + + if (result.IsError()) { + return result; } - // TODO: Load amiibo from backup on system - LOG_ERROR(Service_NFP, "Not Implemented"); + 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; } @@ -1132,16 +1242,98 @@ Result NfcDevice::BreakTag(NFP::BreakType break_type) { return FlushWithBreak(break_type); } -Result NfcDevice::ReadBackupData(std::span<u8> data) const { - // Not implemented +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::WriteBackupData(std::span<const u8> data) { - // Not implemented +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); @@ -1177,7 +1369,8 @@ NFP::AmiiboName NfcDevice::GetAmiiboName(const NFP::AmiiboSettings& settings) co return amiibo_name; } -void NfcDevice::SetAmiiboName(NFP::AmiiboSettings& settings, const NFP::AmiiboName& 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 @@ -1258,22 +1451,23 @@ void NfcDevice::UpdateRegisterInfoCrc() { tag_data.register_info_crc = crc.checksum(); } -void NfcDevice::BuildAmiiboWithoutKeys() { +void NfcDevice::BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data, + const NFP::EncryptedNTAG215File& encrypted_file) const { Service::Mii::MiiManager manager; - auto& settings = tag_data.settings; + auto& settings = stubbed_tag_data.settings; - tag_data = NFP::AmiiboCrypto::NfcDataToEncodedData(encrypted_tag_data); + stubbed_tag_data = NFP::AmiiboCrypto::NfcDataToEncodedData(encrypted_file); // Common info - tag_data.write_counter = 0; - tag_data.amiibo_version = 0; + 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()); - tag_data.owner_mii = manager.BuildFromStoreData(manager.BuildDefault(0)); + stubbed_tag_data.owner_mii = manager.BuildFromStoreData(manager.BuildDefault(0)); // Admin info settings.settings.amiibo_initialized.Assign(1); diff --git a/src/core/hle/service/nfc/common/device.h b/src/core/hle/service/nfc/common/device.h index 6a37e8458..7560210d6 100644 --- a/src/core/hle/service/nfc/common/device.h +++ b/src/core/hle/service/nfc/common/device.h @@ -86,8 +86,14 @@ public: Result GetAll(NFP::NfpData& data) const; Result SetAll(const NFP::NfpData& data); Result BreakTag(NFP::BreakType break_type); - Result ReadBackupData(std::span<u8> data) const; - Result WriteBackupData(std::span<const u8> data); + Result HasBackup(const UniqueSerialNumber& uid, std::size_t uuid_size) const; + Result HasBackup(const NFP::TagUuid& tag_uid) const; + Result ReadBackupData(const UniqueSerialNumber& uid, std::size_t uuid_size, + std::span<u8> data) const; + Result ReadBackupData(const NFP::TagUuid& tag_uid, std::span<u8> data) const; + Result WriteBackupData(const UniqueSerialNumber& uid, std::size_t uuid_size, + std::span<const u8> data); + Result WriteBackupData(const NFP::TagUuid& tag_uid, std::span<const u8> data); Result WriteNtf(std::span<const u8> data); u64 GetHandle() const; @@ -103,14 +109,15 @@ private: void CloseNfcTag(); NFP::AmiiboName GetAmiiboName(const NFP::AmiiboSettings& settings) const; - void SetAmiiboName(NFP::AmiiboSettings& settings, const NFP::AmiiboName& amiibo_name); + void SetAmiiboName(NFP::AmiiboSettings& settings, const NFP::AmiiboName& amiibo_name) const; NFP::AmiiboDate GetAmiiboDate(s64 posix_time) const; u64 GetCurrentPosixTime() const; u64 RemoveVersionByte(u64 application_id) const; void UpdateSettingsCrc(); void UpdateRegisterInfoCrc(); - void BuildAmiiboWithoutKeys(); + void BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data, + const NFP::EncryptedNTAG215File& encrypted_file) const; bool is_controller_set{}; int callback_key; diff --git a/src/core/hle/service/nfc/common/device_manager.cpp b/src/core/hle/service/nfc/common/device_manager.cpp index d5deaaf27..b0456508e 100644 --- a/src/core/hle/service/nfc/common/device_manager.cpp +++ b/src/core/hle/service/nfc/common/device_manager.cpp @@ -543,9 +543,14 @@ Result DeviceManager::ReadBackupData(u64 device_handle, std::span<u8> data) cons std::shared_ptr<NfcDevice> device = nullptr; auto result = GetDeviceHandle(device_handle, device); + NFC::TagInfo tag_info{}; if (result.IsSuccess()) { - result = device->ReadBackupData(data); + result = device->GetTagInfo(tag_info, false); + } + + if (result.IsSuccess()) { + result = device->ReadBackupData(tag_info.uuid, tag_info.uuid_length, data); result = VerifyDeviceResult(device, result); } @@ -557,9 +562,14 @@ Result DeviceManager::WriteBackupData(u64 device_handle, std::span<const u8> dat std::shared_ptr<NfcDevice> device = nullptr; auto result = GetDeviceHandle(device_handle, device); + NFC::TagInfo tag_info{}; + + if (result.IsSuccess()) { + result = device->GetTagInfo(tag_info, false); + } if (result.IsSuccess()) { - result = device->WriteBackupData(data); + result = device->WriteBackupData(tag_info.uuid, tag_info.uuid_length, data); result = VerifyDeviceResult(device, result); } diff --git a/src/core/hle/service/nfc/mifare_result.h b/src/core/hle/service/nfc/mifare_result.h index 4b60048a5..16a9171e6 100644 --- a/src/core/hle/service/nfc/mifare_result.h +++ b/src/core/hle/service/nfc/mifare_result.h @@ -12,6 +12,6 @@ constexpr Result ResultInvalidArgument(ErrorModule::NFCMifare, 65); constexpr Result ResultWrongDeviceState(ErrorModule::NFCMifare, 73); constexpr Result ResultNfcDisabled(ErrorModule::NFCMifare, 80); constexpr Result ResultTagRemoved(ErrorModule::NFCMifare, 97); -constexpr Result ResultReadError(ErrorModule::NFCMifare, 288); +constexpr Result ResultNotAMifare(ErrorModule::NFCMifare, 288); } // namespace Service::NFC::Mifare diff --git a/src/core/hle/service/nfc/nfc_interface.cpp b/src/core/hle/service/nfc/nfc_interface.cpp index 0fa29d398..130fb7f78 100644 --- a/src/core/hle/service/nfc/nfc_interface.cpp +++ b/src/core/hle/service/nfc/nfc_interface.cpp @@ -142,9 +142,13 @@ void NfcInterface::AttachAvailabilityChangeEvent(HLERequestContext& ctx) { void NfcInterface::StartDetection(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto device_handle{rp.Pop<u64>()}; - const auto tag_protocol{rp.PopEnum<NfcProtocol>()}; - LOG_INFO(Service_NFC, "called, device_handle={}, nfp_protocol={}", device_handle, tag_protocol); + auto tag_protocol{NfcProtocol::All}; + + if (backend_type == BackendType::Nfc) { + tag_protocol = rp.PopEnum<NfcProtocol>(); + } + LOG_INFO(Service_NFC, "called, device_handle={}, nfp_protocol={}", device_handle, tag_protocol); auto result = GetManager()->StartDetection(device_handle, tag_protocol); result = TranslateResultToServiceError(result); @@ -302,7 +306,7 @@ Result NfcInterface::TranslateResultToServiceError(Result result) const { return TranslateResultToNfp(result); } default: - if (result != ResultUnknown216) { + if (result != ResultBackupPathAlreadyExist) { return result; } return ResultUnknown74; @@ -343,6 +347,9 @@ Result NfcInterface::TranslateResultToNfp(Result result) const { if (result == ResultApplicationAreaIsNotInitialized) { return NFP::ResultApplicationAreaIsNotInitialized; } + if (result == ResultCorruptedDataWithBackup) { + return NFP::ResultCorruptedDataWithBackup; + } if (result == ResultCorruptedData) { return NFP::ResultCorruptedData; } @@ -352,9 +359,12 @@ Result NfcInterface::TranslateResultToNfp(Result result) const { if (result == ResultApplicationAreaExist) { return NFP::ResultApplicationAreaExist; } - if (result == ResultNotAnAmiibo) { + if (result == ResultInvalidTagType) { return NFP::ResultNotAnAmiibo; } + if (result == ResultUnableToAccessBackupFile) { + return NFP::ResultUnableToAccessBackupFile; + } LOG_WARNING(Service_NFC, "Result conversion not handled"); return result; } @@ -375,6 +385,9 @@ Result NfcInterface::TranslateResultToMifare(Result result) const { if (result == ResultTagRemoved) { return Mifare::ResultTagRemoved; } + if (result == ResultInvalidTagType) { + return Mifare::ResultNotAMifare; + } LOG_WARNING(Service_NFC, "Result conversion not handled"); return result; } diff --git a/src/core/hle/service/nfc/nfc_result.h b/src/core/hle/service/nfc/nfc_result.h index 917d79ef8..715c0e80c 100644 --- a/src/core/hle/service/nfc/nfc_result.h +++ b/src/core/hle/service/nfc/nfc_result.h @@ -9,20 +9,23 @@ namespace Service::NFC { constexpr Result ResultDeviceNotFound(ErrorModule::NFC, 64); constexpr Result ResultInvalidArgument(ErrorModule::NFC, 65); -constexpr Result ResultWrongApplicationAreaSize(ErrorModule::NFP, 68); +constexpr Result ResultWrongApplicationAreaSize(ErrorModule::NFC, 68); constexpr Result ResultWrongDeviceState(ErrorModule::NFC, 73); constexpr Result ResultUnknown74(ErrorModule::NFC, 74); constexpr Result ResultUnknown76(ErrorModule::NFC, 76); constexpr Result ResultNfcNotInitialized(ErrorModule::NFC, 77); constexpr Result ResultNfcDisabled(ErrorModule::NFC, 80); -constexpr Result ResultWriteAmiiboFailed(ErrorModule::NFP, 88); +constexpr Result ResultWriteAmiiboFailed(ErrorModule::NFC, 88); constexpr Result ResultTagRemoved(ErrorModule::NFC, 97); -constexpr Result ResultRegistrationIsNotInitialized(ErrorModule::NFP, 120); -constexpr Result ResultApplicationAreaIsNotInitialized(ErrorModule::NFP, 128); -constexpr Result ResultCorruptedData(ErrorModule::NFP, 144); -constexpr Result ResultWrongApplicationAreaId(ErrorModule::NFP, 152); -constexpr Result ResultApplicationAreaExist(ErrorModule::NFP, 168); -constexpr Result ResultNotAnAmiibo(ErrorModule::NFP, 178); -constexpr Result ResultUnknown216(ErrorModule::NFC, 216); +constexpr Result ResultUnableToAccessBackupFile(ErrorModule::NFC, 113); +constexpr Result ResultRegistrationIsNotInitialized(ErrorModule::NFC, 120); +constexpr Result ResultApplicationAreaIsNotInitialized(ErrorModule::NFC, 128); +constexpr Result ResultCorruptedDataWithBackup(ErrorModule::NFC, 136); +constexpr Result ResultCorruptedData(ErrorModule::NFC, 144); +constexpr Result ResultWrongApplicationAreaId(ErrorModule::NFC, 152); +constexpr Result ResultApplicationAreaExist(ErrorModule::NFC, 168); +constexpr Result ResultInvalidTagType(ErrorModule::NFC, 178); +constexpr Result ResultBackupPathAlreadyExist(ErrorModule::NFC, 216); +constexpr Result ResultMifareError288(ErrorModule::NFC, 288); } // namespace Service::NFC diff --git a/src/core/hle/service/nfc/nfc_types.h b/src/core/hle/service/nfc/nfc_types.h index c7ebd1fdb..68e724442 100644 --- a/src/core/hle/service/nfc/nfc_types.h +++ b/src/core/hle/service/nfc/nfc_types.h @@ -35,32 +35,35 @@ enum class State : u32 { // This is nn::nfc::TagType 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 + None = 0, + Type1 = 1U << 0, // ISO14443A RW. Topaz + Type2 = 1U << 1, // ISO14443A RW. Ultralight, NTAGX, ST25TN + Type3 = 1U << 2, // ISO14443A RW/RO. Sony FeliCa + Type4A = 1U << 3, // ISO14443A RW/RO. DESFire + Type4B = 1U << 4, // ISO14443B RW/RO. DESFire + Type5 = 1U << 5, // ISO15693 RW/RO. SLI, SLIX, ST25TV + Mifare = 1U << 6, // Mifare classic. Skylanders + All = 0xFFFFFFFF, }; 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 + None = 0, + Type1 = 1U << 0, // ISO14443A RW. Topaz + Type2 = 1U << 1, // ISO14443A RW. Ultralight, NTAGX, ST25TN + Type3 = 1U << 2, // ISO14443A RW/RO. Sony FeliCa + Type4A = 1U << 3, // ISO14443A RW/RO. DESFire + Type4B = 1U << 4, // ISO14443B RW/RO. DESFire + Type5 = 1U << 5, // ISO15693 RW/RO. SLI, SLIX, ST25TV + Mifare = 1U << 6, // Mifare classic. Skylanders + All = 0xFF, }; // This is nn::nfc::NfcProtocol -// Verify this enum. It might be completely wrong default protocol is 0x48 enum class NfcProtocol : u32 { None, TypeA = 1U << 0, // ISO14443A TypeB = 1U << 1, // ISO14443B TypeF = 1U << 2, // Sony FeliCa - Unknown1 = 1U << 3, - Unknown2 = 1U << 5, All = 0xFFFFFFFFU, }; @@ -69,8 +72,7 @@ enum class TestWaveType : u32 { Unknown, }; -using UniqueSerialNumber = std::array<u8, 7>; -using UniqueSerialNumberExtension = std::array<u8, 3>; +using UniqueSerialNumber = std::array<u8, 10>; // This is nn::nfc::DeviceHandle using DeviceHandle = u64; @@ -78,7 +80,6 @@ using DeviceHandle = u64; // This is nn::nfc::TagInfo struct TagInfo { UniqueSerialNumber uuid; - UniqueSerialNumberExtension uuid_extension; u8 uuid_length; INSERT_PADDING_BYTES(0x15); NfcProtocol protocol; diff --git a/src/core/hle/service/nfp/nfp_interface.cpp b/src/core/hle/service/nfp/nfp_interface.cpp index 21d159154..34ef9d82d 100644 --- a/src/core/hle/service/nfp/nfp_interface.cpp +++ b/src/core/hle/service/nfp/nfp_interface.cpp @@ -126,7 +126,7 @@ void Interface::Flush(HLERequestContext& ctx) { void Interface::Restore(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto device_handle{rp.Pop<u64>()}; - LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle); + LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); auto result = GetManager()->Restore(device_handle); result = TranslateResultToServiceError(result); @@ -394,7 +394,7 @@ void Interface::BreakTag(HLERequestContext& ctx) { void Interface::ReadBackupData(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto device_handle{rp.Pop<u64>()}; - LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle); + LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); std::vector<u8> backup_data{}; auto result = GetManager()->ReadBackupData(device_handle, backup_data); @@ -412,7 +412,7 @@ void Interface::WriteBackupData(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto device_handle{rp.Pop<u64>()}; const auto backup_data_buffer{ctx.ReadBuffer()}; - LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle); + LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); auto result = GetManager()->WriteBackupData(device_handle, backup_data_buffer); result = TranslateResultToServiceError(result); diff --git a/src/core/hle/service/nfp/nfp_result.h b/src/core/hle/service/nfp/nfp_result.h index 4c126cd81..618533843 100644 --- a/src/core/hle/service/nfp/nfp_result.h +++ b/src/core/hle/service/nfp/nfp_result.h @@ -17,9 +17,11 @@ constexpr Result ResultWriteAmiiboFailed(ErrorModule::NFP, 88); constexpr Result ResultTagRemoved(ErrorModule::NFP, 97); constexpr Result ResultRegistrationIsNotInitialized(ErrorModule::NFP, 120); constexpr Result ResultApplicationAreaIsNotInitialized(ErrorModule::NFP, 128); +constexpr Result ResultCorruptedDataWithBackup(ErrorModule::NFP, 136); constexpr Result ResultCorruptedData(ErrorModule::NFP, 144); constexpr Result ResultWrongApplicationAreaId(ErrorModule::NFP, 152); constexpr Result ResultApplicationAreaExist(ErrorModule::NFP, 168); constexpr Result ResultNotAnAmiibo(ErrorModule::NFP, 178); +constexpr Result ResultUnableToAccessBackupFile(ErrorModule::NFP, 200); } // namespace Service::NFP diff --git a/src/core/hle/service/nfp/nfp_types.h b/src/core/hle/service/nfp/nfp_types.h index 7d36d5ee6..aed12a7f8 100644 --- a/src/core/hle/service/nfp/nfp_types.h +++ b/src/core/hle/service/nfp/nfp_types.h @@ -85,7 +85,7 @@ enum class CabinetMode : u8 { StartFormatter, }; -using LockBytes = std::array<u8, 2>; +using UuidPart = std::array<u8, 3>; using HashData = std::array<u8, 0x20>; using ApplicationArea = std::array<u8, 0xD8>; using AmiiboName = std::array<char, (amiibo_name_length * 4) + 1>; @@ -93,12 +93,20 @@ using AmiiboName = std::array<char, (amiibo_name_length * 4) + 1>; // This is nn::nfp::TagInfo using TagInfo = NFC::TagInfo; +struct NtagTagUuid { + UuidPart part1; + UuidPart part2; + u8 nintendo_id; +}; +static_assert(sizeof(NtagTagUuid) == 7, "NtagTagUuid is an invalid size"); + struct TagUuid { - NFC::UniqueSerialNumber uid; + UuidPart part1; + u8 crc_check1; + UuidPart part2; u8 nintendo_id; - LockBytes lock_bytes; }; -static_assert(sizeof(TagUuid) == 10, "TagUuid is an invalid size"); +static_assert(sizeof(TagUuid) == 8, "TagUuid is an invalid size"); struct WriteDate { u16 year; @@ -231,7 +239,8 @@ struct EncryptedAmiiboFile { static_assert(sizeof(EncryptedAmiiboFile) == 0x1F8, "AmiiboFile is an invalid size"); struct NTAG215File { - LockBytes lock_bytes; // Tag UUID + u8 uid_crc_check2; + u8 internal_number; u16 static_lock; // Set defined pages as read only u32 compability_container; // Defines available memory HashData hmac_data; // Hash @@ -250,8 +259,7 @@ struct NTAG215File { u32_be register_info_crc; ApplicationArea application_area; // Encrypted Game data HashData hmac_tag; // Hash - NFC::UniqueSerialNumber uid; // Unique serial number - u8 nintendo_id; // Tag UUID + TagUuid uid; AmiiboModelInfo model_info; HashData keygen_salt; // Salt u32 dynamic_lock; // Dynamic lock @@ -264,7 +272,9 @@ static_assert(std::is_trivially_copyable_v<NTAG215File>, "NTAG215File must be tr #pragma pack() struct EncryptedNTAG215File { - TagUuid uuid; // Unique serial number + TagUuid uuid; + u8 uuid_crc_check2; + u8 internal_number; u16 static_lock; // Set defined pages as read only u32 compability_container; // Defines available memory EncryptedAmiiboFile user_memory; // Writable data diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp index 0c7aee1b8..dc45169ad 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp @@ -69,7 +69,7 @@ NvResult nvhost_nvdec::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> in void nvhost_nvdec::OnOpen(DeviceFD fd) { LOG_INFO(Service_NVDRV, "NVDEC video stream started"); - system.AudioCore().SetNVDECActive(true); + system.SetNVDECActive(true); } void nvhost_nvdec::OnClose(DeviceFD fd) { @@ -79,7 +79,7 @@ void nvhost_nvdec::OnClose(DeviceFD fd) { if (iter != host1x_file.fd_to_id.end()) { system.GPU().ClearCdmaInstance(iter->second); } - system.AudioCore().SetNVDECActive(false); + system.SetNVDECActive(false); } } // namespace Service::Nvidia::Devices diff --git a/src/core/hle/service/nvnflinger/nvnflinger.cpp b/src/core/hle/service/nvnflinger/nvnflinger.cpp index 4988e6e17..da2d5890f 100644 --- a/src/core/hle/service/nvnflinger/nvnflinger.cpp +++ b/src/core/hle/service/nvnflinger/nvnflinger.cpp @@ -324,6 +324,10 @@ s64 Nvnflinger::GetNextTicks() const { speed_scale = 0.01f; } } + if (system.GetNVDECActive() && settings.use_video_framerate.GetValue()) { + // Run at intended presentation rate during video playback. + speed_scale = 1.f; + } // As an extension, treat nonpositive swap interval as framerate multiplier. const f32 effective_fps = swap_interval <= 0 ? 120.f * static_cast<f32>(1 - swap_interval) diff --git a/src/core/hle/service/time/time_manager.cpp b/src/core/hle/service/time/time_manager.cpp index 28667710e..fa0fd0531 100644 --- a/src/core/hle/service/time/time_manager.cpp +++ b/src/core/hle/service/time/time_manager.cpp @@ -22,10 +22,6 @@ s64 GetSecondsSinceEpoch() { return std::chrono::duration_cast<std::chrono::seconds>(time_since_epoch).count() + Settings::values.custom_rtc_differential; } - -s64 GetExternalRtcValue() { - return GetSecondsSinceEpoch() + TimeManager::GetExternalTimeZoneOffset(); -} } // Anonymous namespace struct TimeManager::Impl final { @@ -43,7 +39,7 @@ struct TimeManager::Impl final { std::make_shared<Clock::EphemeralNetworkSystemClockContextWriter>()}, time_zone_content_manager{system} { - const auto system_time{Clock::TimeSpanType::FromSeconds(GetExternalRtcValue())}; + const auto system_time{Clock::TimeSpanType::FromSeconds(GetSecondsSinceEpoch())}; SetupStandardSteadyClock(system, Common::UUID::MakeRandom(), system_time, {}, {}); SetupStandardLocalSystemClock(system, {}, system_time.ToSeconds()); @@ -107,7 +103,7 @@ struct TimeManager::Impl final { void SetupTimeZoneManager(std::string location_name, Clock::SteadyClockTimePoint time_zone_updated_time_point, - std::size_t total_location_name_count, u128 time_zone_rule_version, + std::vector<std::string> location_names, u128 time_zone_rule_version, FileSys::VirtualFile& vfs_file) { if (time_zone_content_manager.GetTimeZoneManager().SetDeviceLocationNameWithTimeZoneRule( location_name, vfs_file) != ResultSuccess) { @@ -117,20 +113,13 @@ struct TimeManager::Impl final { time_zone_content_manager.GetTimeZoneManager().SetUpdatedTime(time_zone_updated_time_point); time_zone_content_manager.GetTimeZoneManager().SetTotalLocationNameCount( - total_location_name_count); + location_names.size()); + time_zone_content_manager.GetTimeZoneManager().SetLocationNames(location_names); time_zone_content_manager.GetTimeZoneManager().SetTimeZoneRuleVersion( time_zone_rule_version); time_zone_content_manager.GetTimeZoneManager().MarkAsInitialized(); } - static s64 GetExternalTimeZoneOffset() { - // With "auto" timezone setting, we use the external system's timezone offset - if (Settings::GetTimeZoneString() == "auto") { - return Common::TimeZone::GetCurrentOffsetSeconds().count(); - } - return 0; - } - void SetupStandardSteadyClock(Core::System& system_, Common::UUID clock_source_id, Clock::TimeSpanType setup_value, Clock::TimeSpanType internal_offset, bool is_rtc_reset_detected) { @@ -295,19 +284,10 @@ void TimeManager::UpdateLocalSystemClockTime(s64 posix_time) { void TimeManager::SetupTimeZoneManager(std::string location_name, Clock::SteadyClockTimePoint time_zone_updated_time_point, - std::size_t total_location_name_count, + std::vector<std::string> location_names, u128 time_zone_rule_version, FileSys::VirtualFile& vfs_file) { - impl->SetupTimeZoneManager(location_name, time_zone_updated_time_point, - total_location_name_count, time_zone_rule_version, vfs_file); + impl->SetupTimeZoneManager(location_name, time_zone_updated_time_point, location_names, + time_zone_rule_version, vfs_file); } - -/*static*/ s64 TimeManager::GetExternalTimeZoneOffset() { - // With "auto" timezone setting, we use the external system's timezone offset - if (Settings::GetTimeZoneString() == "auto") { - return Common::TimeZone::GetCurrentOffsetSeconds().count(); - } - return 0; -} - } // namespace Service::Time diff --git a/src/core/hle/service/time/time_manager.h b/src/core/hle/service/time/time_manager.h index 4f046f266..84572dbfa 100644 --- a/src/core/hle/service/time/time_manager.h +++ b/src/core/hle/service/time/time_manager.h @@ -61,11 +61,9 @@ public: void SetupTimeZoneManager(std::string location_name, Clock::SteadyClockTimePoint time_zone_updated_time_point, - std::size_t total_location_name_count, u128 time_zone_rule_version, + std::vector<std::string> location_names, u128 time_zone_rule_version, FileSys::VirtualFile& vfs_file); - static s64 GetExternalTimeZoneOffset(); - private: Core::System& system; diff --git a/src/core/hle/service/time/time_zone_content_manager.cpp b/src/core/hle/service/time/time_zone_content_manager.cpp index afbfe9715..5d60be67a 100644 --- a/src/core/hle/service/time/time_zone_content_manager.cpp +++ b/src/core/hle/service/time/time_zone_content_manager.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include <chrono> #include <sstream> #include "common/logging/log.h" @@ -12,7 +13,11 @@ #include "core/file_sys/registered_cache.h" #include "core/file_sys/romfs.h" #include "core/file_sys/system_archive/system_archive.h" +#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs_types.h" +#include "core/hle/result.h" #include "core/hle/service/filesystem/filesystem.h" +#include "core/hle/service/time/errors.h" #include "core/hle/service/time/time_manager.h" #include "core/hle/service/time/time_zone_content_manager.h" @@ -71,19 +76,13 @@ TimeZoneContentManager::TimeZoneContentManager(Core::System& system_) : system{system_}, location_name_cache{BuildLocationNameCache(system)} {} void TimeZoneContentManager::Initialize(TimeManager& time_manager) { - std::string location_name; const auto timezone_setting = Settings::GetTimeZoneString(); - if (timezone_setting == "auto" || timezone_setting == "default") { - location_name = Common::TimeZone::GetDefaultTimeZone(); - } else { - location_name = timezone_setting; - } if (FileSys::VirtualFile vfs_file; - GetTimeZoneInfoFile(location_name, vfs_file) == ResultSuccess) { + GetTimeZoneInfoFile(timezone_setting, vfs_file) == ResultSuccess) { const auto time_point{ time_manager.GetStandardSteadyClockCore().GetCurrentTimePoint(system)}; - time_manager.SetupTimeZoneManager(location_name, time_point, location_name_cache.size(), {}, + time_manager.SetupTimeZoneManager(timezone_setting, time_point, location_name_cache, {}, vfs_file); } else { time_zone_manager.MarkAsInitialized(); @@ -126,8 +125,15 @@ Result TimeZoneContentManager::GetTimeZoneInfoFile(const std::string& location_n vfs_file = zoneinfo_dir->GetFileRelative(location_name); if (!vfs_file) { - LOG_ERROR(Service_Time, "{:016X} has no file \"{}\"! Using default timezone.", - time_zone_binary_titleid, location_name); + LOG_WARNING(Service_Time, "{:016X} has no file \"{}\"! Using system timezone.", + time_zone_binary_titleid, location_name); + const std::string system_time_zone{Common::TimeZone::FindSystemTimeZone()}; + vfs_file = zoneinfo_dir->GetFile(system_time_zone); + } + + if (!vfs_file) { + LOG_WARNING(Service_Time, "{:016X} has no file \"{}\"! Using default timezone.", + time_zone_binary_titleid, location_name); vfs_file = zoneinfo_dir->GetFile(Common::TimeZone::GetDefaultTimeZone()); } diff --git a/src/core/hle/service/time/time_zone_manager.cpp b/src/core/hle/service/time/time_zone_manager.cpp index 973f7837a..63aacd19f 100644 --- a/src/core/hle/service/time/time_zone_manager.cpp +++ b/src/core/hle/service/time/time_zone_manager.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include <climits> +#include <limits> #include "common/assert.h" #include "common/logging/log.h" @@ -9,6 +10,7 @@ #include "core/file_sys/nca_metadata.h" #include "core/file_sys/registered_cache.h" #include "core/hle/service/time/time_zone_manager.h" +#include "core/hle/service/time/time_zone_types.h" namespace Service::Time::TimeZone { @@ -128,10 +130,10 @@ static constexpr int GetQZName(const char* name, int offset, char delimiter) { } static constexpr int GetTZName(const char* name, int offset) { - for (char value{name[offset]}; - value != '\0' && !IsDigit(value) && value != ',' && value != '-' && value != '+'; - offset++) { - value = name[offset]; + char c; + + while ((c = name[offset]) != '\0' && !IsDigit(c) && c != ',' && c != '-' && c != '+') { + ++offset; } return offset; } @@ -147,6 +149,7 @@ static constexpr bool GetInteger(const char* name, int& offset, int& value, int if (value > max) { return {}; } + offset++; temp = name[offset]; } while (IsDigit(temp)); @@ -471,6 +474,13 @@ static bool ParsePosixName(const char* name, TimeZoneRule& rule) { their_std_offset = their_offset; } } + + if (rule.time_count > 0) { + UNIMPLEMENTED(); + // TODO (lat9nq): Implement eggert/tz/localtime.c:tzparse:1329 + // Seems to be unused in yuzu for now: I never hit the UNIMPLEMENTED in testing + } + rule.ttis[0].gmt_offset = -std_offset; rule.ttis[0].is_dst = false; rule.ttis[0].abbreviation_list_index = 0; @@ -514,6 +524,7 @@ static bool ParseTimeZoneBinary(TimeZoneRule& time_zone_rule, FileSys::VirtualFi constexpr s32 time_zone_max_leaps{50}; constexpr s32 time_zone_max_chars{50}; + constexpr s32 time_zone_max_times{1000}; if (!(0 <= header.leap_count && header.leap_count < time_zone_max_leaps && 0 < header.type_count && header.type_count < s32(time_zone_rule.ttis.size()) && 0 <= header.time_count && header.time_count < s32(time_zone_rule.ats.size()) && @@ -546,7 +557,7 @@ static bool ParseTimeZoneBinary(TimeZoneRule& time_zone_rule, FileSys::VirtualFi for (int index{}; index < time_zone_rule.time_count; ++index) { const u8 type{*vfs_file->ReadByte(read_offset)}; read_offset += sizeof(u8); - if (time_zone_rule.time_count <= type) { + if (time_zone_rule.type_count <= type) { return {}; } if (time_zone_rule.types[index] != 0) { @@ -624,16 +635,109 @@ static bool ParseTimeZoneBinary(TimeZoneRule& time_zone_rule, FileSys::VirtualFi std::array<char, time_zone_name_max> name{}; std::memcpy(name.data(), temp_name.data() + 1, std::size_t(bytes_read - 1)); + // Fill in computed transition times with temp rule TimeZoneRule temp_rule; if (ParsePosixName(name.data(), temp_rule)) { - UNIMPLEMENTED(); + int have_abbreviation = 0; + int char_count = time_zone_rule.char_count; + + for (int i = 0; i < temp_rule.type_count; i++) { + char* temp_abbreviation = + temp_rule.chars.data() + temp_rule.ttis[i].abbreviation_list_index; + int j; + for (j = 0; j < char_count; j++) { + if (std::strcmp(time_zone_rule.chars.data() + j, temp_abbreviation) == 0) { + temp_rule.ttis[i].abbreviation_list_index = j; + have_abbreviation++; + break; + } + } + if (j >= char_count) { + int temp_abbreviation_length = static_cast<int>(std::strlen(temp_abbreviation)); + if (j + temp_abbreviation_length < time_zone_max_chars) { + std::strcpy(time_zone_rule.chars.data() + j, temp_abbreviation); + char_count = j + temp_abbreviation_length + 1; + temp_rule.ttis[i].abbreviation_list_index = j; + have_abbreviation++; + } + } + } + + if (have_abbreviation == temp_rule.type_count) { + time_zone_rule.char_count = char_count; + + // Original comment: + /* Ignore any trailing, no-op transitions generated + by zic as they don't help here and can run afoul + of bugs in zic 2016j or earlier. */ + // This is possibly unnecessary for yuzu, since Nintendo doesn't run zic + while (1 < time_zone_rule.time_count && + (time_zone_rule.types[time_zone_rule.time_count - 1] == + time_zone_rule.types[time_zone_rule.time_count - 2])) { + time_zone_rule.time_count--; + } + + for (int i = 0; + i < temp_rule.time_count && time_zone_rule.time_count < time_zone_max_times; + i++) { + const s64 transition_time = temp_rule.ats[i]; + if (0 < time_zone_rule.time_count && + transition_time <= time_zone_rule.ats[time_zone_rule.time_count - 1]) { + continue; + } + + time_zone_rule.ats[time_zone_rule.time_count] = transition_time; + time_zone_rule.types[time_zone_rule.time_count] = + static_cast<s8>(time_zone_rule.type_count + temp_rule.types[i]); + time_zone_rule.time_count++; + } + for (int i = 0; i < temp_rule.type_count; i++) { + time_zone_rule.ttis[time_zone_rule.type_count++] = temp_rule.ttis[i]; + } + } } } + + const auto typesequiv = [](TimeZoneRule& rule, int a, int b) -> bool { + if (a < 0 || a >= rule.type_count || b < 0 || b >= rule.type_count) { + return {}; + } + + const struct TimeTypeInfo* ap = &rule.ttis[a]; + const struct TimeTypeInfo* bp = &rule.ttis[b]; + + return (ap->gmt_offset == bp->gmt_offset && ap->is_dst == bp->is_dst && + (std::strcmp(&rule.chars[ap->abbreviation_list_index], + &rule.chars[bp->abbreviation_list_index]) == 0)); + }; + if (time_zone_rule.type_count == 0) { return {}; } if (time_zone_rule.time_count > 1) { - UNIMPLEMENTED(); + if (time_zone_rule.ats[0] <= std::numeric_limits<s64>::max() - seconds_per_repeat) { + s64 repeatat = time_zone_rule.ats[0] + seconds_per_repeat; + int repeatattype = time_zone_rule.types[0]; + for (int i = 1; i < time_zone_rule.time_count; ++i) { + if (time_zone_rule.ats[i] == repeatat && + typesequiv(time_zone_rule, time_zone_rule.types[i], repeatattype)) { + time_zone_rule.go_back = true; + break; + } + } + } + if (std::numeric_limits<s64>::min() + seconds_per_repeat <= + time_zone_rule.ats[time_zone_rule.time_count - 1]) { + s64 repeatat = time_zone_rule.ats[time_zone_rule.time_count - 1] - seconds_per_repeat; + int repeatattype = time_zone_rule.types[time_zone_rule.time_count - 1]; + for (int i = time_zone_rule.time_count; i >= 0; --i) { + if (time_zone_rule.ats[i] == repeatat && + typesequiv(time_zone_rule, time_zone_rule.types[i], repeatattype)) { + time_zone_rule.go_ahead = true; + break; + } + } + } } s32 default_type{}; @@ -745,8 +849,9 @@ static Result CreateCalendarTime(s64 time, int gmt_offset, CalendarTimeInternal& static Result ToCalendarTimeInternal(const TimeZoneRule& rules, s64 time, CalendarTimeInternal& calendar_time, CalendarAdditionalInfo& calendar_additional_info) { - if ((rules.go_ahead && time < rules.ats[0]) || - (rules.go_back && time > rules.ats[rules.time_count - 1])) { + ASSERT(rules.go_ahead ? rules.time_count > 0 : true); + if ((rules.go_back && time < rules.ats[0]) || + (rules.go_ahead && time > rules.ats[rules.time_count - 1])) { s64 seconds{}; if (time < rules.ats[0]) { seconds = rules.ats[0] - time; @@ -1038,4 +1143,36 @@ Result TimeZoneManager::GetDeviceLocationName(LocationName& value) const { return ResultSuccess; } +Result TimeZoneManager::GetTotalLocationNameCount(s32& count) const { + if (!is_initialized) { + return ERROR_UNINITIALIZED_CLOCK; + } + count = static_cast<u32>(total_location_name_count); + + return ResultSuccess; +} + +Result TimeZoneManager::GetTimeZoneRuleVersion(u128& version) const { + if (!is_initialized) { + return ERROR_UNINITIALIZED_CLOCK; + } + version = time_zone_rule_version; + + return ResultSuccess; +} + +Result TimeZoneManager::LoadLocationNameList(std::vector<LocationName>& values) const { + if (!is_initialized) { + return ERROR_UNINITIALIZED_CLOCK; + } + + for (const auto& name : total_location_names) { + LocationName entry{}; + std::memcpy(entry.data(), name.c_str(), name.size()); + values.push_back(entry); + } + + return ResultSuccess; +} + } // namespace Service::Time::TimeZone diff --git a/src/core/hle/service/time/time_zone_manager.h b/src/core/hle/service/time/time_zone_manager.h index 5ebd4035e..8664f28d1 100644 --- a/src/core/hle/service/time/time_zone_manager.h +++ b/src/core/hle/service/time/time_zone_manager.h @@ -21,6 +21,10 @@ public: total_location_name_count = value; } + void SetLocationNames(std::vector<std::string> location_names) { + total_location_names = location_names; + } + void SetTimeZoneRuleVersion(const u128& value) { time_zone_rule_version = value; } @@ -33,6 +37,9 @@ public: FileSys::VirtualFile& vfs_file); Result SetUpdatedTime(const Clock::SteadyClockTimePoint& value); Result GetDeviceLocationName(TimeZone::LocationName& value) const; + Result GetTotalLocationNameCount(s32& count) const; + Result GetTimeZoneRuleVersion(u128& version) const; + Result LoadLocationNameList(std::vector<TimeZone::LocationName>& values) const; Result ToCalendarTime(const TimeZoneRule& rules, s64 time, CalendarInfo& calendar) const; Result ToCalendarTimeWithMyRules(s64 time, CalendarInfo& calendar) const; Result ParseTimeZoneRuleBinary(TimeZoneRule& rules, FileSys::VirtualFile& vfs_file) const; @@ -46,6 +53,7 @@ private: std::string device_location_name{"GMT"}; u128 time_zone_rule_version{}; std::size_t total_location_name_count{}; + std::vector<std::string> total_location_names{}; Clock::SteadyClockTimePoint time_zone_update_time_point{ Clock::SteadyClockTimePoint::GetRandom()}; }; diff --git a/src/core/hle/service/time/time_zone_service.cpp b/src/core/hle/service/time/time_zone_service.cpp index cda8d8343..8171c82a5 100644 --- a/src/core/hle/service/time/time_zone_service.cpp +++ b/src/core/hle/service/time/time_zone_service.cpp @@ -15,10 +15,10 @@ ITimeZoneService::ITimeZoneService(Core::System& system_, static const FunctionInfo functions[] = { {0, &ITimeZoneService::GetDeviceLocationName, "GetDeviceLocationName"}, {1, nullptr, "SetDeviceLocationName"}, - {2, nullptr, "GetTotalLocationNameCount"}, - {3, nullptr, "LoadLocationNameList"}, + {2, &ITimeZoneService::GetTotalLocationNameCount, "GetTotalLocationNameCount"}, + {3, &ITimeZoneService::LoadLocationNameList, "LoadLocationNameList"}, {4, &ITimeZoneService::LoadTimeZoneRule, "LoadTimeZoneRule"}, - {5, nullptr, "GetTimeZoneRuleVersion"}, + {5, &ITimeZoneService::GetTimeZoneRuleVersion, "GetTimeZoneRuleVersion"}, {6, nullptr, "GetDeviceLocationNameAndUpdatedTime"}, {100, &ITimeZoneService::ToCalendarTime, "ToCalendarTime"}, {101, &ITimeZoneService::ToCalendarTimeWithMyRule, "ToCalendarTimeWithMyRule"}, @@ -45,6 +45,57 @@ void ITimeZoneService::GetDeviceLocationName(HLERequestContext& ctx) { rb.PushRaw(location_name); } +void ITimeZoneService::GetTotalLocationNameCount(HLERequestContext& ctx) { + LOG_DEBUG(Service_Time, "called"); + + s32 count{}; + if (const Result result{ + time_zone_content_manager.GetTimeZoneManager().GetTotalLocationNameCount(count)}; + result != ResultSuccess) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(count); +} + +void ITimeZoneService::LoadLocationNameList(HLERequestContext& ctx) { + LOG_DEBUG(Service_Time, "called"); + + std::vector<TimeZone::LocationName> location_names{}; + if (const Result result{ + time_zone_content_manager.GetTimeZoneManager().LoadLocationNameList(location_names)}; + result != ResultSuccess) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + + ctx.WriteBuffer(location_names); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(static_cast<s32>(location_names.size())); +} +void ITimeZoneService::GetTimeZoneRuleVersion(HLERequestContext& ctx) { + LOG_DEBUG(Service_Time, "called"); + + u128 rule_version{}; + if (const Result result{ + time_zone_content_manager.GetTimeZoneManager().GetTimeZoneRuleVersion(rule_version)}; + result != ResultSuccess) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(ResultSuccess); + rb.PushRaw(rule_version); +} + void ITimeZoneService::LoadTimeZoneRule(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto raw_location_name{rp.PopRaw<std::array<u8, 0x24>>()}; @@ -61,20 +112,14 @@ void ITimeZoneService::LoadTimeZoneRule(HLERequestContext& ctx) { LOG_DEBUG(Service_Time, "called, location_name={}", location_name); TimeZone::TimeZoneRule time_zone_rule{}; - if (const Result result{ - time_zone_content_manager.LoadTimeZoneRule(time_zone_rule, location_name)}; - result != ResultSuccess) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result); - return; - } + const Result result{time_zone_content_manager.LoadTimeZoneRule(time_zone_rule, location_name)}; std::vector<u8> time_zone_rule_outbuffer(sizeof(TimeZone::TimeZoneRule)); std::memcpy(time_zone_rule_outbuffer.data(), &time_zone_rule, sizeof(TimeZone::TimeZoneRule)); ctx.WriteBuffer(time_zone_rule_outbuffer); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); + rb.Push(result); } void ITimeZoneService::ToCalendarTime(HLERequestContext& ctx) { diff --git a/src/core/hle/service/time/time_zone_service.h b/src/core/hle/service/time/time_zone_service.h index ea83b5714..952fcb0e2 100644 --- a/src/core/hle/service/time/time_zone_service.h +++ b/src/core/hle/service/time/time_zone_service.h @@ -22,6 +22,9 @@ public: private: void GetDeviceLocationName(HLERequestContext& ctx); + void GetTotalLocationNameCount(HLERequestContext& ctx); + void LoadLocationNameList(HLERequestContext& ctx); + void GetTimeZoneRuleVersion(HLERequestContext& ctx); void LoadTimeZoneRule(HLERequestContext& ctx); void ToCalendarTime(HLERequestContext& ctx); void ToCalendarTimeWithMyRule(HLERequestContext& ctx); |