diff options
Diffstat (limited to 'src/core')
224 files changed, 17768 insertions, 4561 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 4b7395be8..d0f76e57e 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -37,6 +37,49 @@ add_library(core STATIC debugger/gdbstub.h device_memory.cpp device_memory.h + file_sys/fssystem/fs_i_storage.h + file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp + file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h + file_sys/fssystem/fssystem_aes_ctr_storage.cpp + file_sys/fssystem/fssystem_aes_ctr_storage.h + file_sys/fssystem/fssystem_aes_xts_storage.cpp + file_sys/fssystem/fssystem_aes_xts_storage.h + file_sys/fssystem/fssystem_alignment_matching_storage.h + file_sys/fssystem/fssystem_alignment_matching_storage_impl.cpp + file_sys/fssystem/fssystem_alignment_matching_storage_impl.h + file_sys/fssystem/fssystem_bucket_tree.cpp + file_sys/fssystem/fssystem_bucket_tree.h + file_sys/fssystem/fssystem_bucket_tree_utils.h + file_sys/fssystem/fssystem_compressed_storage.h + file_sys/fssystem/fssystem_compression_common.h + file_sys/fssystem/fssystem_compression_configuration.cpp + file_sys/fssystem/fssystem_compression_configuration.h + file_sys/fssystem/fssystem_crypto_configuration.cpp + file_sys/fssystem/fssystem_crypto_configuration.h + file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp + file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h + file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp + file_sys/fssystem/fssystem_hierarchical_sha256_storage.h + file_sys/fssystem/fssystem_indirect_storage.cpp + file_sys/fssystem/fssystem_indirect_storage.h + file_sys/fssystem/fssystem_integrity_romfs_storage.cpp + file_sys/fssystem/fssystem_integrity_romfs_storage.h + file_sys/fssystem/fssystem_integrity_verification_storage.cpp + file_sys/fssystem/fssystem_integrity_verification_storage.h + file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h + file_sys/fssystem/fssystem_nca_file_system_driver.cpp + file_sys/fssystem/fssystem_nca_file_system_driver.h + file_sys/fssystem/fssystem_nca_header.cpp + file_sys/fssystem/fssystem_nca_header.h + file_sys/fssystem/fssystem_nca_reader.cpp + file_sys/fssystem/fssystem_pooled_buffer.cpp + file_sys/fssystem/fssystem_pooled_buffer.h + file_sys/fssystem/fssystem_sparse_storage.cpp + file_sys/fssystem/fssystem_sparse_storage.h + file_sys/fssystem/fssystem_switch_storage.h + file_sys/fssystem/fssystem_utility.cpp + file_sys/fssystem/fssystem_utility.h + file_sys/fssystem/fs_types.h file_sys/bis_factory.cpp file_sys/bis_factory.h file_sys/card_image.cpp @@ -57,8 +100,6 @@ add_library(core STATIC file_sys/mode.h file_sys/nca_metadata.cpp file_sys/nca_metadata.h - file_sys/nca_patch.cpp - file_sys/nca_patch.h file_sys/partition_filesystem.cpp file_sys/partition_filesystem.h file_sys/patch_manager.cpp @@ -543,13 +584,27 @@ add_library(core STATIC hle/service/lm/lm.h hle/service/mig/mig.cpp hle/service/mig/mig.h + hle/service/mii/types/char_info.cpp + hle/service/mii/types/char_info.h + hle/service/mii/types/core_data.cpp + hle/service/mii/types/core_data.h + hle/service/mii/types/raw_data.cpp + hle/service/mii/types/raw_data.h + hle/service/mii/types/store_data.cpp + hle/service/mii/types/store_data.h + hle/service/mii/types/ver3_store_data.cpp + hle/service/mii/types/ver3_store_data.h hle/service/mii/mii.cpp hle/service/mii/mii.h + hle/service/mii/mii_database.cpp + hle/service/mii/mii_database.h + hle/service/mii/mii_database_manager.cpp + hle/service/mii/mii_database_manager.h hle/service/mii/mii_manager.cpp hle/service/mii/mii_manager.h - hle/service/mii/raw_data.cpp - hle/service/mii/raw_data.h - hle/service/mii/types.h + hle/service/mii/mii_result.h + hle/service/mii/mii_types.h + hle/service/mii/mii_util.h hle/service/mm/mm_u.cpp hle/service/mm/mm_u.h hle/service/mnpp/mnpp_app.cpp @@ -576,8 +631,8 @@ add_library(core STATIC hle/service/nfp/nfp_interface.h hle/service/nfp/nfp_result.h hle/service/nfp/nfp_types.h - hle/service/ngct/ngct.cpp - hle/service/ngct/ngct.h + hle/service/ngc/ngc.cpp + hle/service/ngc/ngc.h hle/service/nifm/nifm.cpp hle/service/nifm/nifm.h hle/service/nim/nim.cpp @@ -813,6 +868,8 @@ add_library(core STATIC telemetry_session.h tools/freezer.cpp tools/freezer.h + tools/renderdoc.cpp + tools/renderdoc.h ) if (MSVC) @@ -828,6 +885,7 @@ else() -Werror=conversion -Wno-sign-conversion + -Wno-cast-function-type $<$<CXX_COMPILER_ID:Clang>:-fsized-deallocation> ) @@ -836,7 +894,7 @@ endif() create_target_directory_groups(core) target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core nx_tzdb) -target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::opus) +target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls RenderDoc::API) if (MINGW) target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY}) endif() diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp index c97158a71..44a297cdc 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp @@ -287,7 +287,7 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable* } } else { // Unsafe optimizations - if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Unsafe) { + if (Settings::values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Unsafe) { config.unsafe_optimizations = true; if (Settings::values.cpuopt_unsafe_unfuse_fma) { config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA; @@ -307,7 +307,7 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable* } // Curated optimizations - if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Auto) { + if (Settings::values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Auto) { config.unsafe_optimizations = true; config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA; config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreStandardFPCRValue; @@ -316,7 +316,7 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable* } // Paranoia mode for debugging optimizations - if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Paranoid) { + if (Settings::values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Paranoid) { config.unsafe_optimizations = false; config.optimizations = Dynarmic::no_optimizations; } diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp index 791d466ca..2e3674b6d 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp @@ -347,7 +347,7 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable* } } else { // Unsafe optimizations - if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Unsafe) { + if (Settings::values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Unsafe) { config.unsafe_optimizations = true; if (Settings::values.cpuopt_unsafe_unfuse_fma) { config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA; @@ -367,7 +367,7 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable* } // Curated optimizations - if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Auto) { + if (Settings::values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Auto) { config.unsafe_optimizations = true; config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA; config.fastmem_address_space_bits = 64; @@ -375,7 +375,7 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable* } // Paranoia mode for debugging optimizations - if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Paranoid) { + if (Settings::values.cpu_accuracy.GetValue() == Settings::CpuAccuracy::Paranoid) { config.unsafe_optimizations = false; config.optimizations = Dynarmic::no_optimizations; } diff --git a/src/core/core.cpp b/src/core/core.cpp index 48233d7c8..e8300cd05 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -12,6 +12,7 @@ #include "common/logging/log.h" #include "common/microprofile.h" #include "common/settings.h" +#include "common/settings_enums.h" #include "common/string_util.h" #include "core/arm/exclusive_monitor.h" #include "core/core.h" @@ -50,6 +51,7 @@ #include "core/reporter.h" #include "core/telemetry_session.h" #include "core/tools/freezer.h" +#include "core/tools/renderdoc.h" #include "network/network.h" #include "video_core/host1x/host1x.h" #include "video_core/renderer_base.h" @@ -140,16 +142,13 @@ struct System::Impl { device_memory = std::make_unique<Core::DeviceMemory>(); is_multicore = Settings::values.use_multi_core.GetValue(); - extended_memory_layout = Settings::values.use_unsafe_extended_memory_layout.GetValue(); + extended_memory_layout = + Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb; core_timing.SetMulticore(is_multicore); core_timing.Initialize([&system]() { system.RegisterHostThread(); }); - const auto posix_time = std::chrono::system_clock::now().time_since_epoch(); - const auto current_time = - std::chrono::duration_cast<std::chrono::seconds>(posix_time).count(); - Settings::values.custom_rtc_differential = - Settings::values.custom_rtc.value_or(current_time) - current_time; + RefreshTime(); // Create a default fs if one doesn't already exist. if (virtual_filesystem == nullptr) { @@ -172,7 +171,8 @@ struct System::Impl { void ReinitializeIfNecessary(System& system) { const bool must_reinitialize = is_multicore != Settings::values.use_multi_core.GetValue() || - extended_memory_layout != Settings::values.use_unsafe_extended_memory_layout.GetValue(); + extended_memory_layout != (Settings::values.memory_layout_mode.GetValue() != + Settings::MemoryLayout::Memory_4Gb); if (!must_reinitialize) { return; @@ -181,11 +181,22 @@ struct System::Impl { LOG_DEBUG(Kernel, "Re-initializing"); is_multicore = Settings::values.use_multi_core.GetValue(); - extended_memory_layout = Settings::values.use_unsafe_extended_memory_layout.GetValue(); + extended_memory_layout = + Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb; Initialize(system); } + void RefreshTime() { + const auto posix_time = std::chrono::system_clock::now().time_since_epoch(); + const auto current_time = + std::chrono::duration_cast<std::chrono::seconds>(posix_time).count(); + Settings::values.custom_rtc_differential = + (Settings::values.custom_rtc_enabled ? Settings::values.custom_rtc.GetValue() + : current_time) - + current_time; + } + void Run() { std::unique_lock<std::mutex> lk(suspend_guard); @@ -263,13 +274,18 @@ struct System::Impl { time_manager.Initialize(); is_powered_on = true; - exit_lock = false; + exit_locked = false; + exit_requested = false; microprofile_cpu[0] = MICROPROFILE_TOKEN(ARM_CPU0); microprofile_cpu[1] = MICROPROFILE_TOKEN(ARM_CPU1); microprofile_cpu[2] = MICROPROFILE_TOKEN(ARM_CPU2); microprofile_cpu[3] = MICROPROFILE_TOKEN(ARM_CPU3); + if (Settings::values.enable_renderdoc_hotkey) { + renderdoc_api = std::make_unique<Tools::RenderdocAPI>(); + } + LOG_DEBUG(Core, "Initialized OK"); return SystemResultStatus::Success; @@ -388,12 +404,14 @@ struct System::Impl { } is_powered_on = false; - exit_lock = false; + exit_locked = false; + exit_requested = false; if (gpu_core != nullptr) { gpu_core->NotifyShutdown(); } + Network::CancelPendingSocketOperations(); kernel.SuspendApplication(true); if (services) { services->KillNVNFlinger(); @@ -415,6 +433,7 @@ struct System::Impl { debugger.reset(); kernel.Shutdown(); memory.Reset(); + Network::RestartSocketOperations(); if (auto room_member = room_network.GetRoomMember().lock()) { Network::GameInfo game_info{}; @@ -497,7 +516,8 @@ struct System::Impl { CpuManager cpu_manager; std::atomic_bool is_powered_on{}; - bool exit_lock = false; + bool exit_locked = false; + bool exit_requested = false; bool nvdec_active{}; @@ -506,6 +526,8 @@ struct System::Impl { std::unique_ptr<Tools::Freezer> memory_freezer; std::array<u8, 0x20> build_id{}; + std::unique_ptr<Tools::RenderdocAPI> renderdoc_api; + /// Frontend applets Service::AM::Applets::AppletManager applet_manager; @@ -549,6 +571,8 @@ struct System::Impl { std::array<Core::GPUDirtyMemoryManager, Core::Hardware::NUM_CPU_CORES> gpu_dirty_memory_write_manager{}; + + std::deque<std::vector<u8>> user_channel; }; System::System() : impl{std::make_unique<Impl>(*this)} {} @@ -933,12 +957,20 @@ const Service::Time::TimeManager& System::GetTimeManager() const { return impl->time_manager; } -void System::SetExitLock(bool locked) { - impl->exit_lock = locked; +void System::SetExitLocked(bool locked) { + impl->exit_locked = locked; +} + +bool System::GetExitLocked() const { + return impl->exit_locked; +} + +void System::SetExitRequested(bool requested) { + impl->exit_requested = requested; } -bool System::GetExitLock() const { - return impl->exit_lock; +bool System::GetExitRequested() const { + return impl->exit_requested; } void System::SetApplicationProcessBuildID(const CurrentBuildProcessID& id) { @@ -999,6 +1031,10 @@ const Network::RoomNetwork& System::GetRoomNetwork() const { return impl->room_network; } +Tools::RenderdocAPI& System::GetRenderdocAPI() { + return *impl->renderdoc_api; +} + void System::RunServer(std::unique_ptr<Service::ServerManager>&& server_manager) { return impl->kernel.RunServer(std::move(server_manager)); } @@ -1015,6 +1051,10 @@ void System::ExecuteProgram(std::size_t program_index) { } } +std::deque<std::vector<u8>>& System::GetUserChannel() { + return impl->user_channel; +} + void System::RegisterExitCallback(ExitCallback&& callback) { impl->exit_callback = std::move(callback); } @@ -1028,6 +1068,8 @@ void System::Exit() { } void System::ApplySettings() { + impl->RefreshTime(); + if (IsPoweredOn()) { Renderer().RefreshBaseSettings(); } diff --git a/src/core/core.h b/src/core/core.h index c70ea1965..df20f26f3 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -4,6 +4,7 @@ #pragma once #include <cstddef> +#include <deque> #include <functional> #include <memory> #include <mutex> @@ -101,6 +102,10 @@ namespace Network { class RoomNetwork; } +namespace Tools { +class RenderdocAPI; +} + namespace Core { class ARM_Interface; @@ -412,8 +417,13 @@ public: /// Gets an immutable reference to the Room Network. [[nodiscard]] const Network::RoomNetwork& GetRoomNetwork() const; - void SetExitLock(bool locked); - [[nodiscard]] bool GetExitLock() const; + [[nodiscard]] Tools::RenderdocAPI& GetRenderdocAPI(); + + void SetExitLocked(bool locked); + bool GetExitLocked() const; + + void SetExitRequested(bool requested); + bool GetExitRequested() const; void SetApplicationProcessBuildID(const CurrentBuildProcessID& id); [[nodiscard]] const CurrentBuildProcessID& GetApplicationProcessBuildID() const; @@ -456,6 +466,12 @@ public: */ void ExecuteProgram(std::size_t program_index); + /** + * Gets a reference to the user channel stack. + * It is used to transfer data between programs. + */ + [[nodiscard]] std::deque<std::vector<u8>>& GetUserChannel(); + /// Type used for the frontend to designate a callback for System to exit the application. using ExitCallback = std::function<void()>; diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index 4ff2c50e5..43a3c5ffd 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -35,7 +35,6 @@ namespace Core::Crypto { namespace { constexpr u64 CURRENT_CRYPTO_REVISION = 0x5; -constexpr u64 FULL_TICKET_SIZE = 0x400; using Common::AsArray; @@ -156,6 +155,10 @@ u64 GetSignatureTypePaddingSize(SignatureType type) { UNREACHABLE(); } +bool Ticket::IsValid() const { + return !std::holds_alternative<std::monostate>(data); +} + SignatureType Ticket::GetSignatureType() const { if (const auto* ticket = std::get_if<RSA4096Ticket>(&data)) { return ticket->sig_type; @@ -210,6 +213,54 @@ Ticket Ticket::SynthesizeCommon(Key128 title_key, const std::array<u8, 16>& righ return Ticket{out}; } +Ticket Ticket::Read(const FileSys::VirtualFile& file) { + // Attempt to read up to the largest ticket size, and make sure we read at least a signature + // type. + std::array<u8, sizeof(RSA4096Ticket)> raw_data{}; + auto read_size = file->Read(raw_data.data(), raw_data.size(), 0); + if (read_size < sizeof(SignatureType)) { + LOG_WARNING(Crypto, "Attempted to read ticket file with invalid size {}.", read_size); + return Ticket{std::monostate()}; + } + return Read(std::span{raw_data}); +} + +Ticket Ticket::Read(std::span<const u8> raw_data) { + // Some tools read only 0x180 bytes of ticket data instead of 0x2C0, so + // just make sure we have at least the bare minimum of data to work with. + SignatureType sig_type; + if (raw_data.size() < sizeof(SignatureType)) { + LOG_WARNING(Crypto, "Attempted to parse ticket buffer with invalid size {}.", + raw_data.size()); + return Ticket{std::monostate()}; + } + std::memcpy(&sig_type, raw_data.data(), sizeof(sig_type)); + + switch (sig_type) { + case SignatureType::RSA_4096_SHA1: + case SignatureType::RSA_4096_SHA256: { + RSA4096Ticket ticket{}; + std::memcpy(&ticket, raw_data.data(), sizeof(ticket)); + return Ticket{ticket}; + } + case SignatureType::RSA_2048_SHA1: + case SignatureType::RSA_2048_SHA256: { + RSA2048Ticket ticket{}; + std::memcpy(&ticket, raw_data.data(), sizeof(ticket)); + return Ticket{ticket}; + } + case SignatureType::ECDSA_SHA1: + case SignatureType::ECDSA_SHA256: { + ECDSATicket ticket{}; + std::memcpy(&ticket, raw_data.data(), sizeof(ticket)); + return Ticket{ticket}; + } + default: + LOG_WARNING(Crypto, "Attempted to parse ticket buffer with invalid type {}.", sig_type); + return Ticket{std::monostate()}; + } +} + Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) { Key128 out{}; @@ -290,9 +341,9 @@ void KeyManager::DeriveGeneralPurposeKeys(std::size_t crypto_revision) { } } -RSAKeyPair<2048> KeyManager::GetETicketRSAKey() const { +void KeyManager::DeriveETicketRSAKey() { if (IsAllZeroArray(eticket_extended_kek) || !HasKey(S128KeyType::ETicketRSAKek)) { - return {}; + return; } const auto eticket_final = GetKey(S128KeyType::ETicketRSAKek); @@ -304,12 +355,12 @@ RSAKeyPair<2048> KeyManager::GetETicketRSAKey() const { rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10, extended_dec.data(), Op::Decrypt); - RSAKeyPair<2048> rsa_key{}; - std::memcpy(rsa_key.decryption_key.data(), extended_dec.data(), rsa_key.decryption_key.size()); - std::memcpy(rsa_key.modulus.data(), extended_dec.data() + 0x100, rsa_key.modulus.size()); - std::memcpy(rsa_key.exponent.data(), extended_dec.data() + 0x200, rsa_key.exponent.size()); - - return rsa_key; + std::memcpy(eticket_rsa_keypair.decryption_key.data(), extended_dec.data(), + eticket_rsa_keypair.decryption_key.size()); + std::memcpy(eticket_rsa_keypair.modulus.data(), extended_dec.data() + 0x100, + eticket_rsa_keypair.modulus.size()); + std::memcpy(eticket_rsa_keypair.exponent.data(), extended_dec.data() + 0x200, + eticket_rsa_keypair.exponent.size()); } Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) { @@ -447,10 +498,12 @@ std::vector<Ticket> GetTicketblob(const Common::FS::IOFile& ticket_save) { for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) { if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 && buffer[offset + 3] == 0x0) { - out.emplace_back(); - auto& next = out.back(); - std::memcpy(&next, buffer.data() + offset, sizeof(Ticket)); - offset += FULL_TICKET_SIZE; + // NOTE: Assumes ticket blob will only contain RSA-2048 tickets. + auto ticket = Ticket::Read(std::span{buffer.data() + offset, sizeof(RSA2048Ticket)}); + offset += sizeof(RSA2048Ticket); + if (ticket.IsValid()) { + out.push_back(ticket); + } } } @@ -503,25 +556,36 @@ static std::optional<u64> FindTicketOffset(const std::array<u8, size>& data) { return offset; } -std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket, - const RSAKeyPair<2048>& key) { +std::optional<Key128> KeyManager::ParseTicketTitleKey(const Ticket& ticket) { + if (!ticket.IsValid()) { + LOG_WARNING(Crypto, "Attempted to parse title key of invalid ticket."); + return std::nullopt; + } + + if (ticket.GetData().rights_id == Key128{}) { + LOG_WARNING(Crypto, "Attempted to parse title key of ticket with no rights ID."); + return std::nullopt; + } + const auto issuer = ticket.GetData().issuer; if (IsAllZeroArray(issuer)) { + LOG_WARNING(Crypto, "Attempted to parse title key of ticket with invalid issuer."); return std::nullopt; } + if (issuer[0] != 'R' || issuer[1] != 'o' || issuer[2] != 'o' || issuer[3] != 't') { - LOG_INFO(Crypto, "Attempting to parse ticket with non-standard certificate authority."); + LOG_WARNING(Crypto, "Parsing ticket with non-standard certificate authority."); } - Key128 rights_id = ticket.GetData().rights_id; - - if (rights_id == Key128{}) { - return std::nullopt; + if (ticket.GetData().type == TitleKeyType::Common) { + return ticket.GetData().title_key_common; } - if (!std::any_of(ticket.GetData().title_key_common_pad.begin(), - ticket.GetData().title_key_common_pad.end(), [](u8 b) { return b != 0; })) { - return std::make_pair(rights_id, ticket.GetData().title_key_common); + if (eticket_rsa_keypair == RSAKeyPair<2048>{}) { + LOG_WARNING( + Crypto, + "Skipping personalized ticket title key parsing due to missing ETicket RSA key-pair."); + return std::nullopt; } mbedtls_mpi D; // RSA Private Exponent @@ -534,9 +598,12 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket, mbedtls_mpi_init(&S); mbedtls_mpi_init(&M); - mbedtls_mpi_read_binary(&D, key.decryption_key.data(), key.decryption_key.size()); - mbedtls_mpi_read_binary(&N, key.modulus.data(), key.modulus.size()); - mbedtls_mpi_read_binary(&S, ticket.GetData().title_key_block.data(), 0x100); + const auto& title_key_block = ticket.GetData().title_key_block; + mbedtls_mpi_read_binary(&D, eticket_rsa_keypair.decryption_key.data(), + eticket_rsa_keypair.decryption_key.size()); + mbedtls_mpi_read_binary(&N, eticket_rsa_keypair.modulus.data(), + eticket_rsa_keypair.modulus.size()); + mbedtls_mpi_read_binary(&S, title_key_block.data(), title_key_block.size()); mbedtls_mpi_exp_mod(&M, &S, &D, &N, nullptr); @@ -564,8 +631,7 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket, Key128 key_temp{}; std::memcpy(key_temp.data(), m_2.data() + *offset, key_temp.size()); - - return std::make_pair(rights_id, key_temp); + return key_temp; } KeyManager::KeyManager() { @@ -658,17 +724,25 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti continue; } - const auto index = std::stoul(out[0].substr(8, 2), nullptr, 16); + const auto index = std::strtoul(out[0].substr(8, 2).c_str(), nullptr, 16); keyblobs[index] = Common::HexStringToArray<0x90>(out[1]); } else if (out[0].compare(0, 18, "encrypted_keyblob_") == 0) { if (!ValidCryptoRevisionString(out[0], 18, 2)) { continue; } - const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16); + const auto index = std::strtoul(out[0].substr(18, 2).c_str(), nullptr, 16); encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]); } else if (out[0].compare(0, 20, "eticket_extended_kek") == 0) { eticket_extended_kek = Common::HexStringToArray<576>(out[1]); + } else if (out[0].compare(0, 19, "eticket_rsa_keypair") == 0) { + const auto key_data = Common::HexStringToArray<528>(out[1]); + std::memcpy(eticket_rsa_keypair.decryption_key.data(), key_data.data(), + eticket_rsa_keypair.decryption_key.size()); + std::memcpy(eticket_rsa_keypair.modulus.data(), key_data.data() + 0x100, + eticket_rsa_keypair.modulus.size()); + std::memcpy(eticket_rsa_keypair.exponent.data(), key_data.data() + 0x200, + eticket_rsa_keypair.exponent.size()); } else { for (const auto& kv : KEYS_VARIABLE_LENGTH) { if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2)) { @@ -676,7 +750,7 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti } if (out[0].compare(0, kv.second.size(), kv.second) == 0) { const auto index = - std::stoul(out[0].substr(kv.second.size(), 2), nullptr, 16); + std::strtoul(out[0].substr(kv.second.size(), 2).c_str(), nullptr, 16); const auto sub = kv.first.second; if (sub == 0) { s128_keys[{kv.first.first, index, 0}] = @@ -696,7 +770,7 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti const auto& match = kak_names[j]; if (out[0].compare(0, std::strlen(match), match) == 0) { const auto index = - std::stoul(out[0].substr(std::strlen(match), 2), nullptr, 16); + std::strtoul(out[0].substr(std::strlen(match), 2).c_str(), nullptr, 16); s128_keys[{S128KeyType::KeyArea, index, j}] = Common::HexStringToArray<16>(out[1]); } @@ -1110,56 +1184,38 @@ void KeyManager::DeriveETicket(PartitionDataManager& data, eticket_extended_kek = data.GetETicketExtendedKek(); WriteKeyToFile(KeyCategory::Console, "eticket_extended_kek", eticket_extended_kek); + DeriveETicketRSAKey(); PopulateTickets(); } void KeyManager::PopulateTickets() { - const auto rsa_key = GetETicketRSAKey(); - - if (rsa_key == RSAKeyPair<2048>{}) { + if (ticket_databases_loaded) { return; } + ticket_databases_loaded = true; - if (!common_tickets.empty() && !personal_tickets.empty()) { - return; - } + std::vector<Ticket> tickets; const auto system_save_e1_path = Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/80000000000000e1"; - - const Common::FS::IOFile save_e1{system_save_e1_path, Common::FS::FileAccessMode::Read, - Common::FS::FileType::BinaryFile}; + if (Common::FS::Exists(system_save_e1_path)) { + const Common::FS::IOFile save_e1{system_save_e1_path, Common::FS::FileAccessMode::Read, + Common::FS::FileType::BinaryFile}; + const auto blob1 = GetTicketblob(save_e1); + tickets.insert(tickets.end(), blob1.begin(), blob1.end()); + } const auto system_save_e2_path = Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/80000000000000e2"; + if (Common::FS::Exists(system_save_e2_path)) { + const Common::FS::IOFile save_e2{system_save_e2_path, Common::FS::FileAccessMode::Read, + Common::FS::FileType::BinaryFile}; + const auto blob2 = GetTicketblob(save_e2); + tickets.insert(tickets.end(), blob2.begin(), blob2.end()); + } - const Common::FS::IOFile save_e2{system_save_e2_path, Common::FS::FileAccessMode::Read, - Common::FS::FileType::BinaryFile}; - - const auto blob2 = GetTicketblob(save_e2); - auto res = GetTicketblob(save_e1); - - const auto idx = res.size(); - res.insert(res.end(), blob2.begin(), blob2.end()); - - for (std::size_t i = 0; i < res.size(); ++i) { - const auto common = i < idx; - const auto pair = ParseTicket(res[i], rsa_key); - if (!pair) { - continue; - } - - const auto& [rid, key] = *pair; - u128 rights_id; - std::memcpy(rights_id.data(), rid.data(), rid.size()); - - if (common) { - common_tickets[rights_id] = res[i]; - } else { - personal_tickets[rights_id] = res[i]; - } - - SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); + for (const auto& ticket : tickets) { + AddTicket(ticket); } } @@ -1291,41 +1347,33 @@ const std::map<u128, Ticket>& KeyManager::GetPersonalizedTickets() const { return personal_tickets; } -bool KeyManager::AddTicketCommon(Ticket raw) { - const auto rsa_key = GetETicketRSAKey(); - if (rsa_key == RSAKeyPair<2048>{}) { - return false; - } - - const auto pair = ParseTicket(raw, rsa_key); - if (!pair) { +bool KeyManager::AddTicket(const Ticket& ticket) { + if (!ticket.IsValid()) { + LOG_WARNING(Crypto, "Attempted to add invalid ticket."); return false; } - const auto& [rid, key] = *pair; + const auto& rid = ticket.GetData().rights_id; u128 rights_id; std::memcpy(rights_id.data(), rid.data(), rid.size()); - common_tickets[rights_id] = raw; - SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); - return true; -} + if (ticket.GetData().type == Core::Crypto::TitleKeyType::Common) { + common_tickets[rights_id] = ticket; + } else { + personal_tickets[rights_id] = ticket; + } -bool KeyManager::AddTicketPersonalized(Ticket raw) { - const auto rsa_key = GetETicketRSAKey(); - if (rsa_key == RSAKeyPair<2048>{}) { - return false; + if (HasKey(S128KeyType::Titlekey, rights_id[1], rights_id[0])) { + LOG_DEBUG(Crypto, + "Skipping parsing title key from ticket for known rights ID {:016X}{:016X}.", + rights_id[1], rights_id[0]); + return true; } - const auto pair = ParseTicket(raw, rsa_key); - if (!pair) { + const auto key = ParseTicketTitleKey(ticket); + if (!key) { return false; } - - const auto& [rid, key] = *pair; - u128 rights_id; - std::memcpy(rights_id.data(), rid.data(), rid.size()); - common_tickets[rights_id] = raw; - SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); + SetKey(S128KeyType::Titlekey, key.value(), rights_id[1], rights_id[0]); return true; } } // namespace Core::Crypto diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h index 8c864503b..2250eccec 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h @@ -7,6 +7,7 @@ #include <filesystem> #include <map> #include <optional> +#include <span> #include <string> #include <variant> @@ -29,8 +30,6 @@ enum class ResultStatus : u16; namespace Core::Crypto { -constexpr u64 TICKET_FILE_TITLEKEY_OFFSET = 0x180; - using Key128 = std::array<u8, 0x10>; using Key256 = std::array<u8, 0x20>; using SHA256Hash = std::array<u8, 0x20>; @@ -82,6 +81,7 @@ struct RSA4096Ticket { INSERT_PADDING_BYTES(0x3C); TicketData data; }; +static_assert(sizeof(RSA4096Ticket) == 0x500, "RSA4096Ticket has incorrect size."); struct RSA2048Ticket { SignatureType sig_type; @@ -89,6 +89,7 @@ struct RSA2048Ticket { INSERT_PADDING_BYTES(0x3C); TicketData data; }; +static_assert(sizeof(RSA2048Ticket) == 0x400, "RSA2048Ticket has incorrect size."); struct ECDSATicket { SignatureType sig_type; @@ -96,16 +97,41 @@ struct ECDSATicket { INSERT_PADDING_BYTES(0x40); TicketData data; }; +static_assert(sizeof(ECDSATicket) == 0x340, "ECDSATicket has incorrect size."); struct Ticket { - std::variant<RSA4096Ticket, RSA2048Ticket, ECDSATicket> data; - - SignatureType GetSignatureType() const; - TicketData& GetData(); - const TicketData& GetData() const; - u64 GetSize() const; - + std::variant<std::monostate, RSA4096Ticket, RSA2048Ticket, ECDSATicket> data; + + [[nodiscard]] bool IsValid() const; + [[nodiscard]] SignatureType GetSignatureType() const; + [[nodiscard]] TicketData& GetData(); + [[nodiscard]] const TicketData& GetData() const; + [[nodiscard]] u64 GetSize() const; + + /** + * Synthesizes a common ticket given a title key and rights ID. + * + * @param title_key Title key to store in the ticket. + * @param rights_id Rights ID the ticket is for. + * @return The synthesized common ticket. + */ static Ticket SynthesizeCommon(Key128 title_key, const std::array<u8, 0x10>& rights_id); + + /** + * Reads a ticket from a file. + * + * @param file File to read the ticket from. + * @return The read ticket. If the ticket data is invalid, Ticket::IsValid() will be false. + */ + static Ticket Read(const FileSys::VirtualFile& file); + + /** + * Reads a ticket from a memory buffer. + * + * @param raw_data Buffer to read the ticket from. + * @return The read ticket. If the ticket data is invalid, Ticket::IsValid() will be false. + */ + static Ticket Read(std::span<const u8> raw_data); }; static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big."); @@ -264,8 +290,7 @@ public: const std::map<u128, Ticket>& GetCommonTickets() const; const std::map<u128, Ticket>& GetPersonalizedTickets() const; - bool AddTicketCommon(Ticket raw); - bool AddTicketPersonalized(Ticket raw); + bool AddTicket(const Ticket& ticket); void ReloadKeys(); bool AreKeysLoaded() const; @@ -279,10 +304,12 @@ private: // Map from rights ID to ticket std::map<u128, Ticket> common_tickets; std::map<u128, Ticket> personal_tickets; + bool ticket_databases_loaded = false; std::array<std::array<u8, 0xB0>, 0x20> encrypted_keyblobs{}; std::array<std::array<u8, 0x90>, 0x20> keyblobs{}; std::array<u8, 576> eticket_extended_kek{}; + RSAKeyPair<2048> eticket_rsa_keypair{}; bool dev_mode; void LoadFromFile(const std::filesystem::path& file_path, bool is_title_keys); @@ -293,10 +320,13 @@ private: void DeriveGeneralPurposeKeys(std::size_t crypto_revision); - RSAKeyPair<2048> GetETicketRSAKey() const; + void DeriveETicketRSAKey(); void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0); void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0); + + /// Parses the title key section of a ticket. + std::optional<Key128> ParseTicketTitleKey(const Ticket& ticket); }; Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed); @@ -311,9 +341,4 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& ke std::vector<Ticket> GetTicketblob(const Common::FS::IOFile& ticket_save); -// Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority -// (offset 0x140-0x144 is zero) -std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket, - const RSAKeyPair<2048>& eticket_extended_key); - } // namespace Core::Crypto diff --git a/src/core/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp index 0f839d5b4..e55831f27 100644 --- a/src/core/debugger/gdbstub.cpp +++ b/src/core/debugger/gdbstub.cpp @@ -263,6 +263,23 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction std::vector<u8> mem(size); if (system.ApplicationMemory().ReadBlock(addr, mem.data(), size)) { + // Restore any bytes belonging to replaced instructions. + auto it = replaced_instructions.lower_bound(addr); + for (; it != replaced_instructions.end() && it->first < addr + size; it++) { + // Get the bytes of the instruction we previously replaced. + const u32 original_bytes = it->second; + + // Calculate where to start writing to the output buffer. + const size_t output_offset = it->first - addr; + + // Calculate how many bytes to write. + // The loop condition ensures output_offset < size. + const size_t n = std::min<size_t>(size - output_offset, sizeof(u32)); + + // Write the bytes to the output buffer. + std::memcpy(mem.data() + output_offset, &original_bytes, n); + } + SendReply(Common::HexToString(mem)); } else { SendReply(GDB_STUB_REPLY_ERR); diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp index 5d02865f4..8b9a4fc5a 100644 --- a/src/core/file_sys/card_image.cpp +++ b/src/core/file_sys/card_image.cpp @@ -31,13 +31,9 @@ XCI::XCI(VirtualFile file_, u64 program_id, size_t program_index) : file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA}, partitions(partition_names.size()), partitions_raw(partition_names.size()), keys{Core::Crypto::KeyManager::Instance()} { - if (file->ReadObject(&header) != sizeof(GamecardHeader)) { - status = Loader::ResultStatus::ErrorBadXCIHeader; - return; - } - - if (header.magic != Common::MakeMagic('H', 'E', 'A', 'D')) { - status = Loader::ResultStatus::ErrorBadXCIHeader; + const auto header_status = TryReadHeader(); + if (header_status != Loader::ResultStatus::Success) { + status = header_status; return; } @@ -183,9 +179,9 @@ u32 XCI::GetSystemUpdateVersion() { } for (const auto& update_file : update->GetFiles()) { - NCA nca{update_file, nullptr, 0}; + NCA nca{update_file}; - if (nca.GetStatus() != Loader::ResultStatus::Success) { + if (nca.GetStatus() != Loader::ResultStatus::Success || nca.GetSubdirectories().empty()) { continue; } @@ -296,7 +292,7 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) { continue; } - auto nca = std::make_shared<NCA>(partition_file, nullptr, 0); + auto nca = std::make_shared<NCA>(partition_file); if (nca->IsUpdate()) { continue; } @@ -316,6 +312,44 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) { return Loader::ResultStatus::Success; } +Loader::ResultStatus XCI::TryReadHeader() { + constexpr size_t CardInitialDataRegionSize = 0x1000; + + // Define the function we'll use to determine if we read a valid header. + const auto ReadCardHeader = [&]() { + // Ensure we can read the entire header. If we can't, we can't read the card image. + if (file->ReadObject(&header) != sizeof(GamecardHeader)) { + return Loader::ResultStatus::ErrorBadXCIHeader; + } + + // Ensure the header magic matches. If it doesn't, this isn't a card image header. + if (header.magic != Common::MakeMagic('H', 'E', 'A', 'D')) { + return Loader::ResultStatus::ErrorBadXCIHeader; + } + + // We read a card image header. + return Loader::ResultStatus::Success; + }; + + // Try to read the header directly. + if (ReadCardHeader() == Loader::ResultStatus::Success) { + return Loader::ResultStatus::Success; + } + + // Get the size of the file. + const size_t card_image_size = file->GetSize(); + + // If we are large enough to have a key area, offset past the key area and retry. + if (card_image_size >= CardInitialDataRegionSize) { + file = std::make_shared<OffsetVfsFile>(file, card_image_size - CardInitialDataRegionSize, + CardInitialDataRegionSize); + return ReadCardHeader(); + } + + // We had no header and aren't large enough to have a key area, so this can't be parsed. + return Loader::ResultStatus::ErrorBadXCIHeader; +} + u8 XCI::GetFormatVersion() { return GetLogoPartition() == nullptr ? 0x1 : 0x2; } diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h index 1283f8216..9886123e7 100644 --- a/src/core/file_sys/card_image.h +++ b/src/core/file_sys/card_image.h @@ -128,6 +128,7 @@ public: private: Loader::ResultStatus AddNCAFromPartition(XCIPartition part); + Loader::ResultStatus TryReadHeader(); VirtualFile file; GamecardHeader header{}; diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index 06efab46d..7d2f0abb8 100644 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp @@ -12,545 +12,118 @@ #include "core/crypto/ctr_encryption_layer.h" #include "core/crypto/key_manager.h" #include "core/file_sys/content_archive.h" -#include "core/file_sys/nca_patch.h" #include "core/file_sys/partition_filesystem.h" #include "core/file_sys/vfs_offset.h" #include "core/loader/loader.h" +#include "core/file_sys/fssystem/fssystem_compression_configuration.h" +#include "core/file_sys/fssystem/fssystem_crypto_configuration.h" +#include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h" + namespace FileSys { -// Media offsets in headers are stored divided by 512. Mult. by this to get real offset. -constexpr u64 MEDIA_OFFSET_MULTIPLIER = 0x200; - -constexpr u64 SECTION_HEADER_SIZE = 0x200; -constexpr u64 SECTION_HEADER_OFFSET = 0x400; - -constexpr u32 IVFC_MAX_LEVEL = 6; - -enum class NCASectionFilesystemType : u8 { - PFS0 = 0x2, - ROMFS = 0x3, -}; - -struct IVFCLevel { - u64_le offset; - u64_le size; - u32_le block_size; - u32_le reserved; -}; -static_assert(sizeof(IVFCLevel) == 0x18, "IVFCLevel has incorrect size."); - -struct IVFCHeader { - u32_le magic; - u32_le magic_number; - INSERT_PADDING_BYTES_NOINIT(8); - std::array<IVFCLevel, 6> levels; - INSERT_PADDING_BYTES_NOINIT(64); -}; -static_assert(sizeof(IVFCHeader) == 0xE0, "IVFCHeader has incorrect size."); - -struct NCASectionHeaderBlock { - INSERT_PADDING_BYTES_NOINIT(3); - NCASectionFilesystemType filesystem_type; - NCASectionCryptoType crypto_type; - INSERT_PADDING_BYTES_NOINIT(3); -}; -static_assert(sizeof(NCASectionHeaderBlock) == 0x8, "NCASectionHeaderBlock has incorrect size."); - -struct NCABucketInfo { - u64 table_offset; - u64 table_size; - std::array<u8, 0x10> table_header; -}; -static_assert(sizeof(NCABucketInfo) == 0x20, "NCABucketInfo has incorrect size."); - -struct NCASparseInfo { - NCABucketInfo bucket; - u64 physical_offset; - u16 generation; - INSERT_PADDING_BYTES_NOINIT(0x6); -}; -static_assert(sizeof(NCASparseInfo) == 0x30, "NCASparseInfo has incorrect size."); - -struct NCACompressionInfo { - NCABucketInfo bucket; - INSERT_PADDING_BYTES_NOINIT(0x8); -}; -static_assert(sizeof(NCACompressionInfo) == 0x28, "NCACompressionInfo has incorrect size."); - -struct NCASectionRaw { - NCASectionHeaderBlock header; - std::array<u8, 0x138> block_data; - std::array<u8, 0x8> section_ctr; - NCASparseInfo sparse_info; - NCACompressionInfo compression_info; - INSERT_PADDING_BYTES_NOINIT(0x60); -}; -static_assert(sizeof(NCASectionRaw) == 0x200, "NCASectionRaw has incorrect size."); - -struct PFS0Superblock { - NCASectionHeaderBlock header_block; - std::array<u8, 0x20> hash; - u32_le size; - INSERT_PADDING_BYTES_NOINIT(4); - u64_le hash_table_offset; - u64_le hash_table_size; - u64_le pfs0_header_offset; - u64_le pfs0_size; - INSERT_PADDING_BYTES_NOINIT(0x1B0); -}; -static_assert(sizeof(PFS0Superblock) == 0x200, "PFS0Superblock has incorrect size."); - -struct RomFSSuperblock { - NCASectionHeaderBlock header_block; - IVFCHeader ivfc; - INSERT_PADDING_BYTES_NOINIT(0x118); -}; -static_assert(sizeof(RomFSSuperblock) == 0x200, "RomFSSuperblock has incorrect size."); - -struct BKTRHeader { - u64_le offset; - u64_le size; - u32_le magic; - INSERT_PADDING_BYTES_NOINIT(0x4); - u32_le number_entries; - INSERT_PADDING_BYTES_NOINIT(0x4); -}; -static_assert(sizeof(BKTRHeader) == 0x20, "BKTRHeader has incorrect size."); - -struct BKTRSuperblock { - NCASectionHeaderBlock header_block; - IVFCHeader ivfc; - INSERT_PADDING_BYTES_NOINIT(0x18); - BKTRHeader relocation; - BKTRHeader subsection; - INSERT_PADDING_BYTES_NOINIT(0xC0); -}; -static_assert(sizeof(BKTRSuperblock) == 0x200, "BKTRSuperblock has incorrect size."); - -union NCASectionHeader { - NCASectionRaw raw{}; - PFS0Superblock pfs0; - RomFSSuperblock romfs; - BKTRSuperblock bktr; -}; -static_assert(sizeof(NCASectionHeader) == 0x200, "NCASectionHeader has incorrect size."); - -static bool IsValidNCA(const NCAHeader& header) { - // TODO(DarkLordZach): Add NCA2/NCA0 support. - return header.magic == Common::MakeMagic('N', 'C', 'A', '3'); +static u8 MasterKeyIdForKeyGeneration(u8 key_generation) { + return std::max<u8>(key_generation, 1) - 1; } -NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset) - : file(std::move(file_)), - bktr_base_romfs(std::move(bktr_base_romfs_)), keys{Core::Crypto::KeyManager::Instance()} { +NCA::NCA(VirtualFile file_, const NCA* base_nca) + : file(std::move(file_)), keys{Core::Crypto::KeyManager::Instance()} { if (file == nullptr) { status = Loader::ResultStatus::ErrorNullFile; return; } - if (sizeof(NCAHeader) != file->ReadObject(&header)) { - LOG_ERROR(Loader, "File reader errored out during header read."); + reader = std::make_shared<NcaReader>(); + if (Result rc = + reader->Initialize(file, GetCryptoConfiguration(), GetNcaCompressionConfiguration()); + R_FAILED(rc)) { + if (rc != ResultInvalidNcaSignature) { + LOG_ERROR(Loader, "File reader errored out during header read: {:#x}", + rc.GetInnerValue()); + } status = Loader::ResultStatus::ErrorBadNCAHeader; return; } - if (!HandlePotentialHeaderDecryption()) { - return; - } - - has_rights_id = std::ranges::any_of(header.rights_id, [](char c) { return c != '\0'; }); - - const std::vector<NCASectionHeader> sections = ReadSectionHeaders(); - is_update = std::ranges::any_of(sections, [](const NCASectionHeader& nca_header) { - return nca_header.raw.header.crypto_type == NCASectionCryptoType::BKTR; - }); - - if (!ReadSections(sections, bktr_base_ivfc_offset)) { + // Ensure we have the proper key area keys to continue. + const u8 master_key_id = MasterKeyIdForKeyGeneration(reader->GetKeyGeneration()); + if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, reader->GetKeyIndex())) { + status = Loader::ResultStatus::ErrorMissingKeyAreaKey; return; } - status = Loader::ResultStatus::Success; -} - -NCA::~NCA() = default; - -bool NCA::CheckSupportedNCA(const NCAHeader& nca_header) { - if (nca_header.magic == Common::MakeMagic('N', 'C', 'A', '2')) { - status = Loader::ResultStatus::ErrorNCA2; - return false; - } + RightsId rights_id{}; + reader->GetRightsId(rights_id.data(), rights_id.size()); + if (rights_id != RightsId{}) { + // External decryption key required; provide it here. + u128 rights_id_u128; + std::memcpy(rights_id_u128.data(), rights_id.data(), sizeof(rights_id)); - if (nca_header.magic == Common::MakeMagic('N', 'C', 'A', '0')) { - status = Loader::ResultStatus::ErrorNCA0; - return false; - } - - return true; -} - -bool NCA::HandlePotentialHeaderDecryption() { - if (IsValidNCA(header)) { - return true; - } - - if (!CheckSupportedNCA(header)) { - return false; - } - - NCAHeader dec_header{}; - Core::Crypto::AESCipher<Core::Crypto::Key256> cipher( - keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS); - cipher.XTSTranscode(&header, sizeof(NCAHeader), &dec_header, 0, 0x200, - Core::Crypto::Op::Decrypt); - if (IsValidNCA(dec_header)) { - header = dec_header; - encrypted = true; - } else { - if (!CheckSupportedNCA(dec_header)) { - return false; + auto titlekey = + keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id_u128[1], rights_id_u128[0]); + if (titlekey == Core::Crypto::Key128{}) { + status = Loader::ResultStatus::ErrorMissingTitlekey; + return; } - if (keys.HasKey(Core::Crypto::S256KeyType::Header)) { - status = Loader::ResultStatus::ErrorIncorrectHeaderKey; - } else { - status = Loader::ResultStatus::ErrorMissingHeaderKey; + if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, master_key_id)) { + status = Loader::ResultStatus::ErrorMissingTitlekek; + return; } - return false; - } - return true; -} + auto titlekek = keys.GetKey(Core::Crypto::S128KeyType::Titlekek, master_key_id); + Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(titlekek, Core::Crypto::Mode::ECB); + cipher.Transcode(titlekey.data(), titlekey.size(), titlekey.data(), + Core::Crypto::Op::Decrypt); -std::vector<NCASectionHeader> NCA::ReadSectionHeaders() const { - const std::ptrdiff_t number_sections = - std::ranges::count_if(header.section_tables, [](const NCASectionTableEntry& entry) { - return entry.media_offset > 0; - }); - - std::vector<NCASectionHeader> sections(number_sections); - const auto length_sections = SECTION_HEADER_SIZE * number_sections; - - if (encrypted) { - auto raw = file->ReadBytes(length_sections, SECTION_HEADER_OFFSET); - Core::Crypto::AESCipher<Core::Crypto::Key256> cipher( - keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS); - cipher.XTSTranscode(raw.data(), length_sections, sections.data(), 2, SECTION_HEADER_SIZE, - Core::Crypto::Op::Decrypt); - } else { - file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET); - } - - return sections; -} - -bool NCA::ReadSections(const std::vector<NCASectionHeader>& sections, u64 bktr_base_ivfc_offset) { - for (std::size_t i = 0; i < sections.size(); ++i) { - const auto& section = sections[i]; - - if (section.raw.sparse_info.bucket.table_offset != 0 && - section.raw.sparse_info.bucket.table_size != 0) { - LOG_ERROR(Loader, "Sparse NCAs are not supported."); - status = Loader::ResultStatus::ErrorSparseNCA; - return false; - } - - if (section.raw.compression_info.bucket.table_offset != 0 && - section.raw.compression_info.bucket.table_size != 0) { - LOG_ERROR(Loader, "Compressed NCAs are not supported."); - status = Loader::ResultStatus::ErrorCompressedNCA; - return false; - } - - if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) { - if (!ReadRomFSSection(section, header.section_tables[i], bktr_base_ivfc_offset)) { - return false; - } - } else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) { - if (!ReadPFS0Section(section, header.section_tables[i])) { - return false; - } - } - } - - return true; -} - -bool NCA::ReadRomFSSection(const NCASectionHeader& section, const NCASectionTableEntry& entry, - u64 bktr_base_ivfc_offset) { - const std::size_t base_offset = entry.media_offset * MEDIA_OFFSET_MULTIPLIER; - ivfc_offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset; - const std::size_t romfs_offset = base_offset + ivfc_offset; - const std::size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size; - auto raw = std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset); - auto dec = Decrypt(section, raw, romfs_offset); - - if (dec == nullptr) { - if (status != Loader::ResultStatus::Success) - return false; - if (has_rights_id) - status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; - else - status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; - return false; + reader->SetExternalDecryptionKey(titlekey.data(), titlekey.size()); } - if (section.raw.header.crypto_type == NCASectionCryptoType::BKTR) { - if (section.bktr.relocation.magic != Common::MakeMagic('B', 'K', 'T', 'R') || - section.bktr.subsection.magic != Common::MakeMagic('B', 'K', 'T', 'R')) { - status = Loader::ResultStatus::ErrorBadBKTRHeader; - return false; - } - - if (section.bktr.relocation.offset + section.bktr.relocation.size != - section.bktr.subsection.offset) { - status = Loader::ResultStatus::ErrorBKTRSubsectionNotAfterRelocation; - return false; - } - - const u64 size = MEDIA_OFFSET_MULTIPLIER * (entry.media_end_offset - entry.media_offset); - if (section.bktr.subsection.offset + section.bktr.subsection.size != size) { - status = Loader::ResultStatus::ErrorBKTRSubsectionNotAtEnd; - return false; - } - - const u64 offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset; - RelocationBlock relocation_block{}; - if (dec->ReadObject(&relocation_block, section.bktr.relocation.offset - offset) != - sizeof(RelocationBlock)) { - status = Loader::ResultStatus::ErrorBadRelocationBlock; - return false; - } - SubsectionBlock subsection_block{}; - if (dec->ReadObject(&subsection_block, section.bktr.subsection.offset - offset) != - sizeof(RelocationBlock)) { - status = Loader::ResultStatus::ErrorBadSubsectionBlock; - return false; - } - - std::vector<RelocationBucketRaw> relocation_buckets_raw( - (section.bktr.relocation.size - sizeof(RelocationBlock)) / sizeof(RelocationBucketRaw)); - if (dec->ReadBytes(relocation_buckets_raw.data(), - section.bktr.relocation.size - sizeof(RelocationBlock), - section.bktr.relocation.offset + sizeof(RelocationBlock) - offset) != - section.bktr.relocation.size - sizeof(RelocationBlock)) { - status = Loader::ResultStatus::ErrorBadRelocationBuckets; - return false; + const s32 fs_count = reader->GetFsCount(); + NcaFileSystemDriver fs(base_nca ? base_nca->reader : nullptr, reader); + std::vector<VirtualFile> filesystems(fs_count); + for (s32 i = 0; i < fs_count; i++) { + NcaFsHeaderReader header_reader; + const Result rc = fs.OpenStorage(&filesystems[i], &header_reader, i); + if (R_FAILED(rc)) { + LOG_ERROR(Loader, "File reader errored out during read of section {}: {:#x}", i, + rc.GetInnerValue()); + status = Loader::ResultStatus::ErrorBadNCAHeader; + return; } - std::vector<SubsectionBucketRaw> subsection_buckets_raw( - (section.bktr.subsection.size - sizeof(SubsectionBlock)) / sizeof(SubsectionBucketRaw)); - if (dec->ReadBytes(subsection_buckets_raw.data(), - section.bktr.subsection.size - sizeof(SubsectionBlock), - section.bktr.subsection.offset + sizeof(SubsectionBlock) - offset) != - section.bktr.subsection.size - sizeof(SubsectionBlock)) { - status = Loader::ResultStatus::ErrorBadSubsectionBuckets; - return false; + if (header_reader.GetFsType() == NcaFsHeader::FsType::RomFs) { + files.push_back(filesystems[i]); + romfs = files.back(); } - std::vector<RelocationBucket> relocation_buckets(relocation_buckets_raw.size()); - std::ranges::transform(relocation_buckets_raw, relocation_buckets.begin(), - &ConvertRelocationBucketRaw); - std::vector<SubsectionBucket> subsection_buckets(subsection_buckets_raw.size()); - std::ranges::transform(subsection_buckets_raw, subsection_buckets.begin(), - &ConvertSubsectionBucketRaw); - - u32 ctr_low; - std::memcpy(&ctr_low, section.raw.section_ctr.data(), sizeof(ctr_low)); - subsection_buckets.back().entries.push_back({section.bktr.relocation.offset, {0}, ctr_low}); - subsection_buckets.back().entries.push_back({size, {0}, 0}); - - std::optional<Core::Crypto::Key128> key; - if (encrypted) { - if (has_rights_id) { - status = Loader::ResultStatus::Success; - key = GetTitlekey(); - if (!key) { - status = Loader::ResultStatus::ErrorMissingTitlekey; - return false; - } - } else { - key = GetKeyAreaKey(NCASectionCryptoType::BKTR); - if (!key) { - status = Loader::ResultStatus::ErrorMissingKeyAreaKey; - return false; + if (header_reader.GetFsType() == NcaFsHeader::FsType::PartitionFs) { + auto npfs = std::make_shared<PartitionFilesystem>(filesystems[i]); + if (npfs->GetStatus() == Loader::ResultStatus::Success) { + dirs.push_back(npfs); + if (IsDirectoryExeFS(npfs)) { + exefs = dirs.back(); + } else if (IsDirectoryLogoPartition(npfs)) { + logo = dirs.back(); + } else { + continue; } } } - if (bktr_base_romfs == nullptr) { - status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS; - return false; + if (header_reader.GetEncryptionType() == NcaFsHeader::EncryptionType::AesCtrEx) { + is_update = true; } - - auto bktr = std::make_shared<BKTR>( - bktr_base_romfs, std::make_shared<OffsetVfsFile>(file, romfs_size, base_offset), - relocation_block, relocation_buckets, subsection_block, subsection_buckets, encrypted, - encrypted ? *key : Core::Crypto::Key128{}, base_offset, bktr_base_ivfc_offset, - section.raw.section_ctr); - - // BKTR applies to entire IVFC, so make an offset version to level 6 - files.push_back(std::make_shared<OffsetVfsFile>( - bktr, romfs_size, section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset)); - } else { - files.push_back(std::move(dec)); } - romfs = files.back(); - return true; -} - -bool NCA::ReadPFS0Section(const NCASectionHeader& section, const NCASectionTableEntry& entry) { - const u64 offset = (static_cast<u64>(entry.media_offset) * MEDIA_OFFSET_MULTIPLIER) + - section.pfs0.pfs0_header_offset; - const u64 size = MEDIA_OFFSET_MULTIPLIER * (entry.media_end_offset - entry.media_offset); - - auto dec = Decrypt(section, std::make_shared<OffsetVfsFile>(file, size, offset), offset); - if (dec != nullptr) { - auto npfs = std::make_shared<PartitionFilesystem>(std::move(dec)); - - if (npfs->GetStatus() == Loader::ResultStatus::Success) { - dirs.push_back(std::move(npfs)); - if (IsDirectoryExeFS(dirs.back())) - exefs = dirs.back(); - else if (IsDirectoryLogoPartition(dirs.back())) - logo = dirs.back(); - } else { - if (has_rights_id) - status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; - else - status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; - return false; - } + if (is_update && base_nca == nullptr) { + status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS; } else { - if (status != Loader::ResultStatus::Success) - return false; - if (has_rights_id) - status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek; - else - status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; - return false; + status = Loader::ResultStatus::Success; } - - return true; -} - -u8 NCA::GetCryptoRevision() const { - u8 master_key_id = header.crypto_type; - if (header.crypto_type_2 > master_key_id) - master_key_id = header.crypto_type_2; - if (master_key_id > 0) - --master_key_id; - return master_key_id; -} - -std::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type) const { - const auto master_key_id = GetCryptoRevision(); - - if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index)) { - return std::nullopt; - } - - std::vector<u8> key_area(header.key_area.begin(), header.key_area.end()); - Core::Crypto::AESCipher<Core::Crypto::Key128> cipher( - keys.GetKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index), - Core::Crypto::Mode::ECB); - cipher.Transcode(key_area.data(), key_area.size(), key_area.data(), Core::Crypto::Op::Decrypt); - - Core::Crypto::Key128 out{}; - if (type == NCASectionCryptoType::XTS) { - std::copy(key_area.begin(), key_area.begin() + 0x10, out.begin()); - } else if (type == NCASectionCryptoType::CTR || type == NCASectionCryptoType::BKTR) { - std::copy(key_area.begin() + 0x20, key_area.begin() + 0x30, out.begin()); - } else { - LOG_CRITICAL(Crypto, "Called GetKeyAreaKey on invalid NCASectionCryptoType type={:02X}", - type); - } - - u128 out_128{}; - std::memcpy(out_128.data(), out.data(), sizeof(u128)); - LOG_TRACE(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}", - master_key_id, header.key_index, out_128[1], out_128[0]); - - return out; } -std::optional<Core::Crypto::Key128> NCA::GetTitlekey() { - const auto master_key_id = GetCryptoRevision(); - - u128 rights_id{}; - memcpy(rights_id.data(), header.rights_id.data(), 16); - if (rights_id == u128{}) { - status = Loader::ResultStatus::ErrorInvalidRightsID; - return std::nullopt; - } - - auto titlekey = keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id[1], rights_id[0]); - if (titlekey == Core::Crypto::Key128{}) { - status = Loader::ResultStatus::ErrorMissingTitlekey; - return std::nullopt; - } - - if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, master_key_id)) { - status = Loader::ResultStatus::ErrorMissingTitlekek; - return std::nullopt; - } - - Core::Crypto::AESCipher<Core::Crypto::Key128> cipher( - keys.GetKey(Core::Crypto::S128KeyType::Titlekek, master_key_id), Core::Crypto::Mode::ECB); - cipher.Transcode(titlekey.data(), titlekey.size(), titlekey.data(), Core::Crypto::Op::Decrypt); - - return titlekey; -} - -VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 starting_offset) { - if (!encrypted) - return in; - - switch (s_header.raw.header.crypto_type) { - case NCASectionCryptoType::NONE: - LOG_TRACE(Crypto, "called with mode=NONE"); - return in; - case NCASectionCryptoType::CTR: - // During normal BKTR decryption, this entire function is skipped. This is for the metadata, - // which uses the same CTR as usual. - case NCASectionCryptoType::BKTR: - LOG_TRACE(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset); - { - std::optional<Core::Crypto::Key128> key; - if (has_rights_id) { - status = Loader::ResultStatus::Success; - key = GetTitlekey(); - if (!key) { - if (status == Loader::ResultStatus::Success) - status = Loader::ResultStatus::ErrorMissingTitlekey; - return nullptr; - } - } else { - key = GetKeyAreaKey(NCASectionCryptoType::CTR); - if (!key) { - status = Loader::ResultStatus::ErrorMissingKeyAreaKey; - return nullptr; - } - } - - auto out = std::make_shared<Core::Crypto::CTREncryptionLayer>(std::move(in), *key, - starting_offset); - Core::Crypto::CTREncryptionLayer::IVData iv{}; - for (std::size_t i = 0; i < 8; ++i) { - iv[i] = s_header.raw.section_ctr[8 - i - 1]; - } - out->SetIV(iv); - return std::static_pointer_cast<VfsFile>(out); - } - case NCASectionCryptoType::XTS: - // TODO(DarkLordZach): Find a test case for XTS-encrypted NCAs - default: - LOG_ERROR(Crypto, "called with unhandled crypto type={:02X}", - s_header.raw.header.crypto_type); - return nullptr; - } -} +NCA::~NCA() = default; Loader::ResultStatus NCA::GetStatus() const { return status; @@ -579,21 +152,24 @@ VirtualDir NCA::GetParentDirectory() const { } NCAContentType NCA::GetType() const { - return header.content_type; + return static_cast<NCAContentType>(reader->GetContentType()); } u64 NCA::GetTitleId() const { - if (is_update || status == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) - return header.title_id | 0x800; - return header.title_id; + if (is_update) { + return reader->GetProgramId() | 0x800; + } + return reader->GetProgramId(); } -std::array<u8, 16> NCA::GetRightsId() const { - return header.rights_id; +RightsId NCA::GetRightsId() const { + RightsId result; + reader->GetRightsId(result.data(), result.size()); + return result; } u32 NCA::GetSDKVersion() const { - return header.sdk_version; + return reader->GetSdkAddonVersion(); } bool NCA::IsUpdate() const { @@ -612,10 +188,6 @@ VirtualFile NCA::GetBaseFile() const { return file; } -u64 NCA::GetBaseIVFCOffset() const { - return ivfc_offset; -} - VirtualDir NCA::GetLogoPartition() const { return logo; } diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h index 20f524f80..af521d453 100644 --- a/src/core/file_sys/content_archive.h +++ b/src/core/file_sys/content_archive.h @@ -21,7 +21,7 @@ enum class ResultStatus : u16; namespace FileSys { -union NCASectionHeader; +class NcaReader; /// Describes the type of content within an NCA archive. enum class NCAContentType : u8 { @@ -45,41 +45,7 @@ enum class NCAContentType : u8 { PublicData = 5, }; -enum class NCASectionCryptoType : u8 { - NONE = 1, - XTS = 2, - CTR = 3, - BKTR = 4, -}; - -struct NCASectionTableEntry { - u32_le media_offset; - u32_le media_end_offset; - INSERT_PADDING_BYTES(0x8); -}; -static_assert(sizeof(NCASectionTableEntry) == 0x10, "NCASectionTableEntry has incorrect size."); - -struct NCAHeader { - std::array<u8, 0x100> rsa_signature_1; - std::array<u8, 0x100> rsa_signature_2; - u32_le magic; - u8 is_system; - NCAContentType content_type; - u8 crypto_type; - u8 key_index; - u64_le size; - u64_le title_id; - INSERT_PADDING_BYTES(0x4); - u32_le sdk_version; - u8 crypto_type_2; - INSERT_PADDING_BYTES(15); - std::array<u8, 0x10> rights_id; - std::array<NCASectionTableEntry, 0x4> section_tables; - std::array<std::array<u8, 0x20>, 0x4> hash_tables; - std::array<u8, 0x40> key_area; - INSERT_PADDING_BYTES(0xC0); -}; -static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size."); +using RightsId = std::array<u8, 0x10>; inline bool IsDirectoryExeFS(const VirtualDir& pfs) { // According to switchbrew, an exefs must only contain these two files: @@ -97,8 +63,7 @@ inline bool IsDirectoryLogoPartition(const VirtualDir& pfs) { // After construction, use GetStatus to determine if the file is valid and ready to be used. class NCA : public ReadOnlyVfsDirectory { public: - explicit NCA(VirtualFile file, VirtualFile bktr_base_romfs = nullptr, - u64 bktr_base_ivfc_offset = 0); + explicit NCA(VirtualFile file, const NCA* base_nca = nullptr); ~NCA() override; Loader::ResultStatus GetStatus() const; @@ -110,7 +75,7 @@ public: NCAContentType GetType() const; u64 GetTitleId() const; - std::array<u8, 0x10> GetRightsId() const; + RightsId GetRightsId() const; u32 GetSDKVersion() const; bool IsUpdate() const; @@ -119,26 +84,9 @@ public: VirtualFile GetBaseFile() const; - // Returns the base ivfc offset used in BKTR patching. - u64 GetBaseIVFCOffset() const; - VirtualDir GetLogoPartition() const; private: - bool CheckSupportedNCA(const NCAHeader& header); - bool HandlePotentialHeaderDecryption(); - - std::vector<NCASectionHeader> ReadSectionHeaders() const; - bool ReadSections(const std::vector<NCASectionHeader>& sections, u64 bktr_base_ivfc_offset); - bool ReadRomFSSection(const NCASectionHeader& section, const NCASectionTableEntry& entry, - u64 bktr_base_ivfc_offset); - bool ReadPFS0Section(const NCASectionHeader& section, const NCASectionTableEntry& entry); - - u8 GetCryptoRevision() const; - std::optional<Core::Crypto::Key128> GetKeyAreaKey(NCASectionCryptoType type) const; - std::optional<Core::Crypto::Key128> GetTitlekey(); - VirtualFile Decrypt(const NCASectionHeader& header, VirtualFile in, u64 starting_offset); - std::vector<VirtualDir> dirs; std::vector<VirtualFile> files; @@ -146,11 +94,6 @@ private: VirtualDir exefs = nullptr; VirtualDir logo = nullptr; VirtualFile file; - VirtualFile bktr_base_romfs; - u64 ivfc_offset = 0; - - NCAHeader header{}; - bool has_rights_id{}; Loader::ResultStatus status{}; @@ -158,6 +101,7 @@ private: bool is_update = false; Core::Crypto::KeyManager& keys; + std::shared_ptr<NcaReader> reader; }; } // namespace FileSys diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp index cd9ac2e75..0697c29ae 100644 --- a/src/core/file_sys/control_metadata.cpp +++ b/src/core/file_sys/control_metadata.cpp @@ -68,7 +68,8 @@ NACP::NACP(VirtualFile file) { NACP::~NACP() = default; const LanguageEntry& NACP::GetLanguageEntry() const { - Language language = language_to_codes[Settings::values.language_index.GetValue()]; + Language language = + language_to_codes[static_cast<s32>(Settings::values.language_index.GetValue())]; { const auto& language_entry = raw.language_entries.at(static_cast<u8>(language)); diff --git a/src/core/file_sys/errors.h b/src/core/file_sys/errors.h index 7cee0c7df..2f5045a67 100644 --- a/src/core/file_sys/errors.h +++ b/src/core/file_sys/errors.h @@ -17,4 +17,74 @@ constexpr Result ERROR_INVALID_ARGUMENT{ErrorModule::FS, 6001}; constexpr Result ERROR_INVALID_OFFSET{ErrorModule::FS, 6061}; constexpr Result ERROR_INVALID_SIZE{ErrorModule::FS, 6062}; +constexpr Result ResultUnsupportedSdkVersion{ErrorModule::FS, 50}; +constexpr Result ResultPartitionNotFound{ErrorModule::FS, 1001}; +constexpr Result ResultUnsupportedVersion{ErrorModule::FS, 3002}; +constexpr Result ResultOutOfRange{ErrorModule::FS, 3005}; +constexpr Result ResultAllocationMemoryFailedInFileSystemBuddyHeapA{ErrorModule::FS, 3294}; +constexpr Result ResultAllocationMemoryFailedInNcaFileSystemDriverI{ErrorModule::FS, 3341}; +constexpr Result ResultAllocationMemoryFailedInNcaReaderA{ErrorModule::FS, 3363}; +constexpr Result ResultAllocationMemoryFailedInAesCtrCounterExtendedStorageA{ErrorModule::FS, 3399}; +constexpr Result ResultAllocationMemoryFailedInIntegrityRomFsStorageA{ErrorModule::FS, 3412}; +constexpr Result ResultAllocationMemoryFailedMakeUnique{ErrorModule::FS, 3422}; +constexpr Result ResultAllocationMemoryFailedAllocateShared{ErrorModule::FS, 3423}; +constexpr Result ResultInvalidAesCtrCounterExtendedEntryOffset{ErrorModule::FS, 4012}; +constexpr Result ResultIndirectStorageCorrupted{ErrorModule::FS, 4021}; +constexpr Result ResultInvalidIndirectEntryOffset{ErrorModule::FS, 4022}; +constexpr Result ResultInvalidIndirectEntryStorageIndex{ErrorModule::FS, 4023}; +constexpr Result ResultInvalidIndirectStorageSize{ErrorModule::FS, 4024}; +constexpr Result ResultInvalidBucketTreeSignature{ErrorModule::FS, 4032}; +constexpr Result ResultInvalidBucketTreeEntryCount{ErrorModule::FS, 4033}; +constexpr Result ResultInvalidBucketTreeNodeEntryCount{ErrorModule::FS, 4034}; +constexpr Result ResultInvalidBucketTreeNodeOffset{ErrorModule::FS, 4035}; +constexpr Result ResultInvalidBucketTreeEntryOffset{ErrorModule::FS, 4036}; +constexpr Result ResultInvalidBucketTreeEntrySetOffset{ErrorModule::FS, 4037}; +constexpr Result ResultInvalidBucketTreeNodeIndex{ErrorModule::FS, 4038}; +constexpr Result ResultInvalidBucketTreeVirtualOffset{ErrorModule::FS, 4039}; +constexpr Result ResultRomNcaInvalidPatchMetaDataHashType{ErrorModule::FS, 4084}; +constexpr Result ResultRomNcaInvalidIntegrityLayerInfoOffset{ErrorModule::FS, 4085}; +constexpr Result ResultRomNcaInvalidPatchMetaDataHashDataSize{ErrorModule::FS, 4086}; +constexpr Result ResultRomNcaInvalidPatchMetaDataHashDataOffset{ErrorModule::FS, 4087}; +constexpr Result ResultRomNcaInvalidPatchMetaDataHashDataHash{ErrorModule::FS, 4088}; +constexpr Result ResultRomNcaInvalidSparseMetaDataHashType{ErrorModule::FS, 4089}; +constexpr Result ResultRomNcaInvalidSparseMetaDataHashDataSize{ErrorModule::FS, 4090}; +constexpr Result ResultRomNcaInvalidSparseMetaDataHashDataOffset{ErrorModule::FS, 4091}; +constexpr Result ResultRomNcaInvalidSparseMetaDataHashDataHash{ErrorModule::FS, 4091}; +constexpr Result ResultNcaBaseStorageOutOfRangeB{ErrorModule::FS, 4509}; +constexpr Result ResultNcaBaseStorageOutOfRangeC{ErrorModule::FS, 4510}; +constexpr Result ResultNcaBaseStorageOutOfRangeD{ErrorModule::FS, 4511}; +constexpr Result ResultInvalidNcaSignature{ErrorModule::FS, 4517}; +constexpr Result ResultNcaFsHeaderHashVerificationFailed{ErrorModule::FS, 4520}; +constexpr Result ResultInvalidNcaKeyIndex{ErrorModule::FS, 4521}; +constexpr Result ResultInvalidNcaFsHeaderHashType{ErrorModule::FS, 4522}; +constexpr Result ResultInvalidNcaFsHeaderEncryptionType{ErrorModule::FS, 4523}; +constexpr Result ResultInvalidNcaPatchInfoIndirectSize{ErrorModule::FS, 4524}; +constexpr Result ResultInvalidNcaPatchInfoAesCtrExSize{ErrorModule::FS, 4525}; +constexpr Result ResultInvalidNcaPatchInfoAesCtrExOffset{ErrorModule::FS, 4526}; +constexpr Result ResultInvalidNcaHeader{ErrorModule::FS, 4528}; +constexpr Result ResultInvalidNcaFsHeader{ErrorModule::FS, 4529}; +constexpr Result ResultNcaBaseStorageOutOfRangeE{ErrorModule::FS, 4530}; +constexpr Result ResultInvalidHierarchicalSha256BlockSize{ErrorModule::FS, 4532}; +constexpr Result ResultInvalidHierarchicalSha256LayerCount{ErrorModule::FS, 4533}; +constexpr Result ResultHierarchicalSha256BaseStorageTooLarge{ErrorModule::FS, 4534}; +constexpr Result ResultHierarchicalSha256HashVerificationFailed{ErrorModule::FS, 4535}; +constexpr Result ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount{ErrorModule::FS, 4541}; +constexpr Result ResultInvalidNcaIndirectStorageOutOfRange{ErrorModule::FS, 4542}; +constexpr Result ResultInvalidNcaHeader1SignatureKeyGeneration{ErrorModule::FS, 4543}; +constexpr Result ResultInvalidCompressedStorageSize{ErrorModule::FS, 4547}; +constexpr Result ResultInvalidNcaMetaDataHashDataSize{ErrorModule::FS, 4548}; +constexpr Result ResultInvalidNcaMetaDataHashDataHash{ErrorModule::FS, 4549}; +constexpr Result ResultUnexpectedInCompressedStorageA{ErrorModule::FS, 5324}; +constexpr Result ResultUnexpectedInCompressedStorageB{ErrorModule::FS, 5325}; +constexpr Result ResultUnexpectedInCompressedStorageC{ErrorModule::FS, 5326}; +constexpr Result ResultUnexpectedInCompressedStorageD{ErrorModule::FS, 5327}; +constexpr Result ResultInvalidArgument{ErrorModule::FS, 6001}; +constexpr Result ResultInvalidOffset{ErrorModule::FS, 6061}; +constexpr Result ResultInvalidSize{ErrorModule::FS, 6062}; +constexpr Result ResultNullptrArgument{ErrorModule::FS, 6063}; +constexpr Result ResultUnsupportedSetSizeForIndirectStorage{ErrorModule::FS, 6325}; +constexpr Result ResultUnsupportedWriteForCompressedStorage{ErrorModule::FS, 6387}; +constexpr Result ResultUnsupportedOperateRangeForCompressedStorage{ErrorModule::FS, 6388}; +constexpr Result ResultBufferAllocationFailed{ErrorModule::FS, 6705}; + } // namespace FileSys diff --git a/src/core/file_sys/fssystem/fs_i_storage.h b/src/core/file_sys/fssystem/fs_i_storage.h new file mode 100644 index 000000000..416dd57b8 --- /dev/null +++ b/src/core/file_sys/fssystem/fs_i_storage.h @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/overflow.h" +#include "core/file_sys/errors.h" +#include "core/file_sys/vfs.h" + +namespace FileSys { + +class IStorage : public VfsFile { +public: + virtual std::string GetName() const override { + return {}; + } + + virtual VirtualDir GetContainingDirectory() const override { + return {}; + } + + virtual bool IsWritable() const override { + return true; + } + + virtual bool IsReadable() const override { + return true; + } + + virtual bool Resize(size_t size) override { + return false; + } + + virtual bool Rename(std::string_view name) override { + return false; + } + + static inline Result CheckAccessRange(s64 offset, s64 size, s64 total_size) { + R_UNLESS(offset >= 0, ResultInvalidOffset); + R_UNLESS(size >= 0, ResultInvalidSize); + R_UNLESS(Common::WrappingAdd(offset, size) >= offset, ResultOutOfRange); + R_UNLESS(offset + size <= total_size, ResultOutOfRange); + R_SUCCEED(); + } +}; + +class IReadOnlyStorage : public IStorage { +public: + virtual bool IsWritable() const override { + return false; + } + + virtual size_t Write(const u8* buffer, size_t size, size_t offset) override { + return 0; + } +}; + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fs_types.h b/src/core/file_sys/fssystem/fs_types.h new file mode 100644 index 000000000..43aeaf447 --- /dev/null +++ b/src/core/file_sys/fssystem/fs_types.h @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/common_funcs.h" + +namespace FileSys { + +struct Int64 { + u32 low; + u32 high; + + constexpr void Set(s64 v) { + this->low = static_cast<u32>((v & static_cast<u64>(0x00000000FFFFFFFFULL)) >> 0); + this->high = static_cast<u32>((v & static_cast<u64>(0xFFFFFFFF00000000ULL)) >> 32); + } + + constexpr s64 Get() const { + return (static_cast<s64>(this->high) << 32) | (static_cast<s64>(this->low)); + } + + constexpr Int64& operator=(s64 v) { + this->Set(v); + return *this; + } + + constexpr operator s64() const { + return this->Get(); + } +}; + +struct HashSalt { + static constexpr size_t Size = 32; + + std::array<u8, Size> value; +}; +static_assert(std::is_trivial_v<HashSalt>); +static_assert(sizeof(HashSalt) == HashSalt::Size); + +constexpr inline size_t IntegrityMinLayerCount = 2; +constexpr inline size_t IntegrityMaxLayerCount = 7; +constexpr inline size_t IntegrityLayerCountSave = 5; +constexpr inline size_t IntegrityLayerCountSaveDataMeta = 4; + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp b/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp new file mode 100644 index 000000000..f25c95472 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.cpp @@ -0,0 +1,251 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h" +#include "core/file_sys/fssystem/fssystem_aes_ctr_storage.h" +#include "core/file_sys/fssystem/fssystem_nca_header.h" +#include "core/file_sys/vfs_offset.h" + +namespace FileSys { + +namespace { + +class SoftwareDecryptor final : public AesCtrCounterExtendedStorage::IDecryptor { +public: + virtual void Decrypt( + u8* buf, size_t buf_size, const std::array<u8, AesCtrCounterExtendedStorage::KeySize>& key, + const std::array<u8, AesCtrCounterExtendedStorage::IvSize>& iv) override final; +}; + +} // namespace + +Result AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::unique_ptr<IDecryptor>* out) { + std::unique_ptr<IDecryptor> decryptor = std::make_unique<SoftwareDecryptor>(); + R_UNLESS(decryptor != nullptr, ResultAllocationMemoryFailedInAesCtrCounterExtendedStorageA); + *out = std::move(decryptor); + R_SUCCEED(); +} + +Result AesCtrCounterExtendedStorage::Initialize(const void* key, size_t key_size, u32 secure_value, + VirtualFile data_storage, + VirtualFile table_storage) { + // Read and verify the bucket tree header. + BucketTree::Header header; + table_storage->ReadObject(std::addressof(header), 0); + R_TRY(header.Verify()); + + // Determine extents. + const auto node_storage_size = QueryNodeStorageSize(header.entry_count); + const auto entry_storage_size = QueryEntryStorageSize(header.entry_count); + const auto node_storage_offset = QueryHeaderStorageSize(); + const auto entry_storage_offset = node_storage_offset + node_storage_size; + + // Create a software decryptor. + std::unique_ptr<IDecryptor> sw_decryptor; + R_TRY(CreateSoftwareDecryptor(std::addressof(sw_decryptor))); + + // Initialize. + R_RETURN(this->Initialize( + key, key_size, secure_value, 0, data_storage, + std::make_shared<OffsetVfsFile>(table_storage, node_storage_size, node_storage_offset), + std::make_shared<OffsetVfsFile>(table_storage, entry_storage_size, entry_storage_offset), + header.entry_count, std::move(sw_decryptor))); +} + +Result AesCtrCounterExtendedStorage::Initialize(const void* key, size_t key_size, u32 secure_value, + s64 counter_offset, VirtualFile data_storage, + VirtualFile node_storage, VirtualFile entry_storage, + s32 entry_count, + std::unique_ptr<IDecryptor>&& decryptor) { + // Validate preconditions. + ASSERT(key != nullptr); + ASSERT(key_size == KeySize); + ASSERT(counter_offset >= 0); + ASSERT(decryptor != nullptr); + + // Initialize the bucket tree table. + if (entry_count > 0) { + R_TRY( + m_table.Initialize(node_storage, entry_storage, NodeSize, sizeof(Entry), entry_count)); + } else { + m_table.Initialize(NodeSize, 0); + } + + // Set members. + m_data_storage = data_storage; + std::memcpy(m_key.data(), key, key_size); + m_secure_value = secure_value; + m_counter_offset = counter_offset; + m_decryptor = std::move(decryptor); + + R_SUCCEED(); +} + +void AesCtrCounterExtendedStorage::Finalize() { + if (this->IsInitialized()) { + m_table.Finalize(); + m_data_storage = VirtualFile(); + } +} + +Result AesCtrCounterExtendedStorage::GetEntryList(Entry* out_entries, s32* out_entry_count, + s32 entry_count, s64 offset, s64 size) { + // Validate pre-conditions. + ASSERT(offset >= 0); + ASSERT(size >= 0); + ASSERT(this->IsInitialized()); + + // Clear the out count. + R_UNLESS(out_entry_count != nullptr, ResultNullptrArgument); + *out_entry_count = 0; + + // Succeed if there's no range. + R_SUCCEED_IF(size == 0); + + // If we have an output array, we need it to be non-null. + R_UNLESS(out_entries != nullptr || entry_count == 0, ResultNullptrArgument); + + // Check that our range is valid. + BucketTree::Offsets table_offsets; + R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); + + R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange); + + // Find the offset in our tree. + BucketTree::Visitor visitor; + R_TRY(m_table.Find(std::addressof(visitor), offset)); + { + const auto entry_offset = visitor.Get<Entry>()->GetOffset(); + R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset), + ResultInvalidAesCtrCounterExtendedEntryOffset); + } + + // Prepare to loop over entries. + const auto end_offset = offset + static_cast<s64>(size); + s32 count = 0; + + auto cur_entry = *visitor.Get<Entry>(); + while (cur_entry.GetOffset() < end_offset) { + // Try to write the entry to the out list. + if (entry_count != 0) { + if (count >= entry_count) { + break; + } + std::memcpy(out_entries + count, std::addressof(cur_entry), sizeof(Entry)); + } + + count++; + + // Advance. + if (visitor.CanMoveNext()) { + R_TRY(visitor.MoveNext()); + cur_entry = *visitor.Get<Entry>(); + } else { + break; + } + } + + // Write the output count. + *out_entry_count = count; + R_SUCCEED(); +} + +size_t AesCtrCounterExtendedStorage::Read(u8* buffer, size_t size, size_t offset) const { + // Validate preconditions. + ASSERT(this->IsInitialized()); + + // Allow zero size. + if (size == 0) { + return size; + } + + // Validate arguments. + ASSERT(buffer != nullptr); + ASSERT(Common::IsAligned(offset, BlockSize)); + ASSERT(Common::IsAligned(size, BlockSize)); + + BucketTree::Offsets table_offsets; + ASSERT(R_SUCCEEDED(m_table.GetOffsets(std::addressof(table_offsets)))); + + ASSERT(table_offsets.IsInclude(offset, size)); + + // Read the data. + m_data_storage->Read(buffer, size, offset); + + // Find the offset in our tree. + BucketTree::Visitor visitor; + ASSERT(R_SUCCEEDED(m_table.Find(std::addressof(visitor), offset))); + { + const auto entry_offset = visitor.Get<Entry>()->GetOffset(); + ASSERT(Common::IsAligned(entry_offset, BlockSize)); + ASSERT(0 <= entry_offset && table_offsets.IsInclude(entry_offset)); + } + + // Prepare to read in chunks. + u8* cur_data = static_cast<u8*>(buffer); + auto cur_offset = offset; + const auto end_offset = offset + static_cast<s64>(size); + + while (cur_offset < end_offset) { + // Get the current entry. + const auto cur_entry = *visitor.Get<Entry>(); + + // Get and validate the entry's offset. + const auto cur_entry_offset = cur_entry.GetOffset(); + ASSERT(static_cast<size_t>(cur_entry_offset) <= cur_offset); + + // Get and validate the next entry offset. + s64 next_entry_offset; + if (visitor.CanMoveNext()) { + ASSERT(R_SUCCEEDED(visitor.MoveNext())); + next_entry_offset = visitor.Get<Entry>()->GetOffset(); + ASSERT(table_offsets.IsInclude(next_entry_offset)); + } else { + next_entry_offset = table_offsets.end_offset; + } + ASSERT(Common::IsAligned(next_entry_offset, BlockSize)); + ASSERT(cur_offset < static_cast<size_t>(next_entry_offset)); + + // Get the offset of the entry in the data we read. + const auto data_offset = cur_offset - cur_entry_offset; + const auto data_size = (next_entry_offset - cur_entry_offset) - data_offset; + ASSERT(data_size > 0); + + // Determine how much is left. + const auto remaining_size = end_offset - cur_offset; + const auto cur_size = static_cast<size_t>(std::min(remaining_size, data_size)); + ASSERT(cur_size <= size); + + // If necessary, perform decryption. + if (cur_entry.encryption_value == Entry::Encryption::Encrypted) { + // Make the CTR for the data we're decrypting. + const auto counter_offset = m_counter_offset + cur_entry_offset + data_offset; + NcaAesCtrUpperIv upper_iv = { + .part = {.generation = static_cast<u32>(cur_entry.generation), + .secure_value = m_secure_value}}; + + std::array<u8, IvSize> iv; + AesCtrStorage::MakeIv(iv.data(), IvSize, upper_iv.value, counter_offset); + + // Decrypt. + m_decryptor->Decrypt(cur_data, cur_size, m_key, iv); + } + + // Advance. + cur_data += cur_size; + cur_offset += cur_size; + } + + return size; +} + +void SoftwareDecryptor::Decrypt(u8* buf, size_t buf_size, + const std::array<u8, AesCtrCounterExtendedStorage::KeySize>& key, + const std::array<u8, AesCtrCounterExtendedStorage::IvSize>& iv) { + Core::Crypto::AESCipher<Core::Crypto::Key128, AesCtrCounterExtendedStorage::KeySize> cipher( + key, Core::Crypto::Mode::CTR); + cipher.SetIV(iv); + cipher.Transcode(buf, buf_size, buf, Core::Crypto::Op::Decrypt); +} + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h b/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h new file mode 100644 index 000000000..d0e9ceed0 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h @@ -0,0 +1,114 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <optional> + +#include "common/literals.h" +#include "core/file_sys/fssystem/fs_i_storage.h" +#include "core/file_sys/fssystem/fssystem_bucket_tree.h" + +namespace FileSys { + +using namespace Common::Literals; + +class AesCtrCounterExtendedStorage : public IReadOnlyStorage { + YUZU_NON_COPYABLE(AesCtrCounterExtendedStorage); + YUZU_NON_MOVEABLE(AesCtrCounterExtendedStorage); + +public: + static constexpr size_t BlockSize = 0x10; + static constexpr size_t KeySize = 0x10; + static constexpr size_t IvSize = 0x10; + static constexpr size_t NodeSize = 16_KiB; + + class IDecryptor { + public: + virtual ~IDecryptor() {} + virtual void Decrypt(u8* buf, size_t buf_size, const std::array<u8, KeySize>& key, + const std::array<u8, IvSize>& iv) = 0; + }; + + struct Entry { + enum class Encryption : u8 { + Encrypted = 0, + NotEncrypted = 1, + }; + + std::array<u8, sizeof(s64)> offset; + Encryption encryption_value; + std::array<u8, 3> reserved; + s32 generation; + + void SetOffset(s64 value) { + std::memcpy(this->offset.data(), std::addressof(value), sizeof(s64)); + } + + s64 GetOffset() const { + s64 value; + std::memcpy(std::addressof(value), this->offset.data(), sizeof(s64)); + return value; + } + }; + static_assert(sizeof(Entry) == 0x10); + static_assert(alignof(Entry) == 4); + static_assert(std::is_trivial_v<Entry>); + +public: + static constexpr s64 QueryHeaderStorageSize() { + return BucketTree::QueryHeaderStorageSize(); + } + + static constexpr s64 QueryNodeStorageSize(s32 entry_count) { + return BucketTree::QueryNodeStorageSize(NodeSize, sizeof(Entry), entry_count); + } + + static constexpr s64 QueryEntryStorageSize(s32 entry_count) { + return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count); + } + + static Result CreateSoftwareDecryptor(std::unique_ptr<IDecryptor>* out); + +public: + AesCtrCounterExtendedStorage() + : m_table(), m_data_storage(), m_secure_value(), m_counter_offset(), m_decryptor() {} + virtual ~AesCtrCounterExtendedStorage() { + this->Finalize(); + } + + Result Initialize(const void* key, size_t key_size, u32 secure_value, s64 counter_offset, + VirtualFile data_storage, VirtualFile node_storage, VirtualFile entry_storage, + s32 entry_count, std::unique_ptr<IDecryptor>&& decryptor); + void Finalize(); + + bool IsInitialized() const { + return m_table.IsInitialized(); + } + + virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; + + virtual size_t GetSize() const override { + BucketTree::Offsets offsets; + ASSERT(R_SUCCEEDED(m_table.GetOffsets(std::addressof(offsets)))); + + return offsets.end_offset; + } + + Result GetEntryList(Entry* out_entries, s32* out_entry_count, s32 entry_count, s64 offset, + s64 size); + +private: + Result Initialize(const void* key, size_t key_size, u32 secure_value, VirtualFile data_storage, + VirtualFile table_storage); + +private: + mutable BucketTree m_table; + VirtualFile m_data_storage; + std::array<u8, KeySize> m_key; + u32 m_secure_value; + s64 m_counter_offset; + std::unique_ptr<IDecryptor> m_decryptor; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.cpp b/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.cpp new file mode 100644 index 000000000..b65aca18d --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.cpp @@ -0,0 +1,129 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/alignment.h" +#include "common/swap.h" +#include "core/file_sys/fssystem/fssystem_aes_ctr_storage.h" +#include "core/file_sys/fssystem/fssystem_pooled_buffer.h" +#include "core/file_sys/fssystem/fssystem_utility.h" + +namespace FileSys { + +void AesCtrStorage::MakeIv(void* dst, size_t dst_size, u64 upper, s64 offset) { + ASSERT(dst != nullptr); + ASSERT(dst_size == IvSize); + ASSERT(offset >= 0); + + const uintptr_t out_addr = reinterpret_cast<uintptr_t>(dst); + + *reinterpret_cast<u64_be*>(out_addr + 0) = upper; + *reinterpret_cast<s64_be*>(out_addr + sizeof(u64)) = static_cast<s64>(offset / BlockSize); +} + +AesCtrStorage::AesCtrStorage(VirtualFile base, const void* key, size_t key_size, const void* iv, + size_t iv_size) + : m_base_storage(std::move(base)) { + ASSERT(m_base_storage != nullptr); + ASSERT(key != nullptr); + ASSERT(iv != nullptr); + ASSERT(key_size == KeySize); + ASSERT(iv_size == IvSize); + + std::memcpy(m_key.data(), key, KeySize); + std::memcpy(m_iv.data(), iv, IvSize); + + m_cipher.emplace(m_key, Core::Crypto::Mode::CTR); +} + +size_t AesCtrStorage::Read(u8* buffer, size_t size, size_t offset) const { + // Allow zero-size reads. + if (size == 0) { + return size; + } + + // Ensure buffer is valid. + ASSERT(buffer != nullptr); + + // We can only read at block aligned offsets. + ASSERT(Common::IsAligned(offset, BlockSize)); + ASSERT(Common::IsAligned(size, BlockSize)); + + // Read the data. + m_base_storage->Read(buffer, size, offset); + + // Setup the counter. + std::array<u8, IvSize> ctr; + std::memcpy(ctr.data(), m_iv.data(), IvSize); + AddCounter(ctr.data(), IvSize, offset / BlockSize); + + // Decrypt. + m_cipher->SetIV(ctr); + m_cipher->Transcode(buffer, size, buffer, Core::Crypto::Op::Decrypt); + + return size; +} + +size_t AesCtrStorage::Write(const u8* buffer, size_t size, size_t offset) { + // Allow zero-size writes. + if (size == 0) { + return size; + } + + // Ensure buffer is valid. + ASSERT(buffer != nullptr); + + // We can only write at block aligned offsets. + ASSERT(Common::IsAligned(offset, BlockSize)); + ASSERT(Common::IsAligned(size, BlockSize)); + + // Get a pooled buffer. + PooledBuffer pooled_buffer; + const bool use_work_buffer = true; + if (use_work_buffer) { + pooled_buffer.Allocate(size, BlockSize); + } + + // Setup the counter. + std::array<u8, IvSize> ctr; + std::memcpy(ctr.data(), m_iv.data(), IvSize); + AddCounter(ctr.data(), IvSize, offset / BlockSize); + + // Loop until all data is written. + size_t remaining = size; + s64 cur_offset = 0; + while (remaining > 0) { + // Determine data we're writing and where. + const size_t write_size = + use_work_buffer ? std::min(pooled_buffer.GetSize(), remaining) : remaining; + + void* write_buf; + if (use_work_buffer) { + write_buf = pooled_buffer.GetBuffer(); + } else { + write_buf = const_cast<u8*>(buffer); + } + + // Encrypt the data. + m_cipher->SetIV(ctr); + m_cipher->Transcode(buffer, write_size, reinterpret_cast<u8*>(write_buf), + Core::Crypto::Op::Encrypt); + + // Write the encrypted data. + m_base_storage->Write(reinterpret_cast<u8*>(write_buf), write_size, offset + cur_offset); + + // Advance. + cur_offset += write_size; + remaining -= write_size; + if (remaining > 0) { + AddCounter(ctr.data(), IvSize, write_size / BlockSize); + } + } + + return size; +} + +size_t AesCtrStorage::GetSize() const { + return m_base_storage->GetSize(); +} + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.h b/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.h new file mode 100644 index 000000000..339e49697 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_aes_ctr_storage.h @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <optional> + +#include "core/crypto/aes_util.h" +#include "core/crypto/key_manager.h" +#include "core/file_sys/errors.h" +#include "core/file_sys/fssystem/fs_i_storage.h" +#include "core/file_sys/vfs.h" + +namespace FileSys { + +class AesCtrStorage : public IStorage { + YUZU_NON_COPYABLE(AesCtrStorage); + YUZU_NON_MOVEABLE(AesCtrStorage); + +public: + static constexpr size_t BlockSize = 0x10; + static constexpr size_t KeySize = 0x10; + static constexpr size_t IvSize = 0x10; + +public: + static void MakeIv(void* dst, size_t dst_size, u64 upper, s64 offset); + +public: + AesCtrStorage(VirtualFile base, const void* key, size_t key_size, const void* iv, + size_t iv_size); + + virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; + virtual size_t Write(const u8* buffer, size_t size, size_t offset) override; + virtual size_t GetSize() const override; + +private: + VirtualFile m_base_storage; + std::array<u8, KeySize> m_key; + std::array<u8, IvSize> m_iv; + mutable std::optional<Core::Crypto::AESCipher<Core::Crypto::Key128>> m_cipher; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_aes_xts_storage.cpp b/src/core/file_sys/fssystem/fssystem_aes_xts_storage.cpp new file mode 100644 index 000000000..022424229 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_aes_xts_storage.cpp @@ -0,0 +1,112 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/alignment.h" +#include "common/swap.h" +#include "core/file_sys/errors.h" +#include "core/file_sys/fssystem/fssystem_aes_xts_storage.h" +#include "core/file_sys/fssystem/fssystem_pooled_buffer.h" +#include "core/file_sys/fssystem/fssystem_utility.h" + +namespace FileSys { + +void AesXtsStorage::MakeAesXtsIv(void* dst, size_t dst_size, s64 offset, size_t block_size) { + ASSERT(dst != nullptr); + ASSERT(dst_size == IvSize); + ASSERT(offset >= 0); + + const uintptr_t out_addr = reinterpret_cast<uintptr_t>(dst); + + *reinterpret_cast<s64_be*>(out_addr + sizeof(s64)) = offset / block_size; +} + +AesXtsStorage::AesXtsStorage(VirtualFile base, const void* key1, const void* key2, size_t key_size, + const void* iv, size_t iv_size, size_t block_size) + : m_base_storage(std::move(base)), m_block_size(block_size), m_mutex() { + ASSERT(m_base_storage != nullptr); + ASSERT(key1 != nullptr); + ASSERT(key2 != nullptr); + ASSERT(iv != nullptr); + ASSERT(key_size == KeySize); + ASSERT(iv_size == IvSize); + ASSERT(Common::IsAligned(m_block_size, AesBlockSize)); + + std::memcpy(m_key.data() + 0, key1, KeySize); + std::memcpy(m_key.data() + 0x10, key2, KeySize); + std::memcpy(m_iv.data(), iv, IvSize); + + m_cipher.emplace(m_key, Core::Crypto::Mode::XTS); +} + +size_t AesXtsStorage::Read(u8* buffer, size_t size, size_t offset) const { + // Allow zero-size reads. + if (size == 0) { + return size; + } + + // Ensure buffer is valid. + ASSERT(buffer != nullptr); + + // We can only read at block aligned offsets. + ASSERT(Common::IsAligned(offset, AesBlockSize)); + ASSERT(Common::IsAligned(size, AesBlockSize)); + + // Read the data. + m_base_storage->Read(buffer, size, offset); + + // Setup the counter. + std::array<u8, IvSize> ctr; + std::memcpy(ctr.data(), m_iv.data(), IvSize); + AddCounter(ctr.data(), IvSize, offset / m_block_size); + + // Handle any unaligned data before the start. + size_t processed_size = 0; + if ((offset % m_block_size) != 0) { + // Determine the size of the pre-data read. + const size_t skip_size = + static_cast<size_t>(offset - Common::AlignDown(offset, m_block_size)); + const size_t data_size = std::min(size, m_block_size - skip_size); + + // Decrypt into a pooled buffer. + { + PooledBuffer tmp_buf(m_block_size, m_block_size); + ASSERT(tmp_buf.GetSize() >= m_block_size); + + std::memset(tmp_buf.GetBuffer(), 0, skip_size); + std::memcpy(tmp_buf.GetBuffer() + skip_size, buffer, data_size); + + m_cipher->SetIV(ctr); + m_cipher->Transcode(tmp_buf.GetBuffer(), m_block_size, tmp_buf.GetBuffer(), + Core::Crypto::Op::Decrypt); + + std::memcpy(buffer, tmp_buf.GetBuffer() + skip_size, data_size); + } + + AddCounter(ctr.data(), IvSize, 1); + processed_size += data_size; + ASSERT(processed_size == std::min(size, m_block_size - skip_size)); + } + + // Decrypt aligned chunks. + char* cur = reinterpret_cast<char*>(buffer) + processed_size; + size_t remaining = size - processed_size; + while (remaining > 0) { + const size_t cur_size = std::min(m_block_size, remaining); + + m_cipher->SetIV(ctr); + m_cipher->Transcode(cur, cur_size, cur, Core::Crypto::Op::Decrypt); + + remaining -= cur_size; + cur += cur_size; + + AddCounter(ctr.data(), IvSize, 1); + } + + return size; +} + +size_t AesXtsStorage::GetSize() const { + return m_base_storage->GetSize(); +} + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_aes_xts_storage.h b/src/core/file_sys/fssystem/fssystem_aes_xts_storage.h new file mode 100644 index 000000000..f342efb57 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_aes_xts_storage.h @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <optional> + +#include "core/crypto/aes_util.h" +#include "core/crypto/key_manager.h" +#include "core/file_sys/fssystem/fs_i_storage.h" + +namespace FileSys { + +class AesXtsStorage : public IReadOnlyStorage { + YUZU_NON_COPYABLE(AesXtsStorage); + YUZU_NON_MOVEABLE(AesXtsStorage); + +public: + static constexpr size_t AesBlockSize = 0x10; + static constexpr size_t KeySize = 0x20; + static constexpr size_t IvSize = 0x10; + +public: + static void MakeAesXtsIv(void* dst, size_t dst_size, s64 offset, size_t block_size); + +public: + AesXtsStorage(VirtualFile base, const void* key1, const void* key2, size_t key_size, + const void* iv, size_t iv_size, size_t block_size); + + virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; + virtual size_t GetSize() const override; + +private: + VirtualFile m_base_storage; + std::array<u8, KeySize> m_key; + std::array<u8, IvSize> m_iv; + const size_t m_block_size; + std::mutex m_mutex; + mutable std::optional<Core::Crypto::AESCipher<Core::Crypto::Key256>> m_cipher; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_alignment_matching_storage.h b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage.h new file mode 100644 index 000000000..f96691d03 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage.h @@ -0,0 +1,146 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/alignment.h" +#include "core/file_sys/errors.h" +#include "core/file_sys/fssystem/fs_i_storage.h" +#include "core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h" +#include "core/file_sys/fssystem/fssystem_pooled_buffer.h" + +namespace FileSys { + +template <size_t DataAlign_, size_t BufferAlign_> +class AlignmentMatchingStorage : public IStorage { + YUZU_NON_COPYABLE(AlignmentMatchingStorage); + YUZU_NON_MOVEABLE(AlignmentMatchingStorage); + +public: + static constexpr size_t DataAlign = DataAlign_; + static constexpr size_t BufferAlign = BufferAlign_; + + static constexpr size_t DataAlignMax = 0x200; + static_assert(DataAlign <= DataAlignMax); + static_assert(Common::IsPowerOfTwo(DataAlign)); + static_assert(Common::IsPowerOfTwo(BufferAlign)); + +private: + VirtualFile m_base_storage; + s64 m_base_storage_size; + +public: + explicit AlignmentMatchingStorage(VirtualFile bs) : m_base_storage(std::move(bs)) {} + + virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { + // Allocate a work buffer on stack. + alignas(DataAlignMax) std::array<char, DataAlign> work_buf; + + // Succeed if zero size. + if (size == 0) { + return size; + } + + // Validate arguments. + ASSERT(buffer != nullptr); + + s64 bs_size = this->GetSize(); + ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size))); + + return AlignmentMatchingStorageImpl::Read(m_base_storage, work_buf.data(), work_buf.size(), + DataAlign, BufferAlign, offset, buffer, size); + } + + virtual size_t Write(const u8* buffer, size_t size, size_t offset) override { + // Allocate a work buffer on stack. + alignas(DataAlignMax) std::array<char, DataAlign> work_buf; + + // Succeed if zero size. + if (size == 0) { + return size; + } + + // Validate arguments. + ASSERT(buffer != nullptr); + + s64 bs_size = this->GetSize(); + ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size))); + + return AlignmentMatchingStorageImpl::Write(m_base_storage, work_buf.data(), work_buf.size(), + DataAlign, BufferAlign, offset, buffer, size); + } + + virtual size_t GetSize() const override { + return m_base_storage->GetSize(); + } +}; + +template <size_t BufferAlign_> +class AlignmentMatchingStoragePooledBuffer : public IStorage { + YUZU_NON_COPYABLE(AlignmentMatchingStoragePooledBuffer); + YUZU_NON_MOVEABLE(AlignmentMatchingStoragePooledBuffer); + +public: + static constexpr size_t BufferAlign = BufferAlign_; + + static_assert(Common::IsPowerOfTwo(BufferAlign)); + +private: + VirtualFile m_base_storage; + s64 m_base_storage_size; + size_t m_data_align; + +public: + explicit AlignmentMatchingStoragePooledBuffer(VirtualFile bs, size_t da) + : m_base_storage(std::move(bs)), m_data_align(da) { + ASSERT(Common::IsPowerOfTwo(da)); + } + + virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { + // Succeed if zero size. + if (size == 0) { + return size; + } + + // Validate arguments. + ASSERT(buffer != nullptr); + + s64 bs_size = this->GetSize(); + ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size))); + + // Allocate a pooled buffer. + PooledBuffer pooled_buffer; + pooled_buffer.AllocateParticularlyLarge(m_data_align, m_data_align); + + return AlignmentMatchingStorageImpl::Read(m_base_storage, pooled_buffer.GetBuffer(), + pooled_buffer.GetSize(), m_data_align, + BufferAlign, offset, buffer, size); + } + + virtual size_t Write(const u8* buffer, size_t size, size_t offset) override { + // Succeed if zero size. + if (size == 0) { + return size; + } + + // Validate arguments. + ASSERT(buffer != nullptr); + + s64 bs_size = this->GetSize(); + ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange(offset, size, bs_size))); + + // Allocate a pooled buffer. + PooledBuffer pooled_buffer; + pooled_buffer.AllocateParticularlyLarge(m_data_align, m_data_align); + + return AlignmentMatchingStorageImpl::Write(m_base_storage, pooled_buffer.GetBuffer(), + pooled_buffer.GetSize(), m_data_align, + BufferAlign, offset, buffer, size); + } + + virtual size_t GetSize() const override { + return m_base_storage->GetSize(); + } +}; + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.cpp b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.cpp new file mode 100644 index 000000000..641c888ae --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.cpp @@ -0,0 +1,204 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/alignment.h" +#include "core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h" + +namespace FileSys { + +namespace { + +template <typename T> +constexpr size_t GetRoundDownDifference(T x, size_t align) { + return static_cast<size_t>(x - Common::AlignDown(x, align)); +} + +template <typename T> +constexpr size_t GetRoundUpDifference(T x, size_t align) { + return static_cast<size_t>(Common::AlignUp(x, align) - x); +} + +template <typename T> +size_t GetRoundUpDifference(T* x, size_t align) { + return GetRoundUpDifference(reinterpret_cast<uintptr_t>(x), align); +} + +} // namespace + +size_t AlignmentMatchingStorageImpl::Read(VirtualFile base_storage, char* work_buf, + size_t work_buf_size, size_t data_alignment, + size_t buffer_alignment, s64 offset, u8* buffer, + size_t size) { + // Check preconditions. + ASSERT(work_buf_size >= data_alignment); + + // Succeed if zero size. + if (size == 0) { + return size; + } + + // Validate arguments. + ASSERT(buffer != nullptr); + + // Determine extents. + u8* aligned_core_buffer; + s64 core_offset; + size_t core_size; + size_t buffer_gap; + size_t offset_gap; + s64 covered_offset; + + const size_t offset_round_up_difference = GetRoundUpDifference(offset, data_alignment); + if (Common::IsAligned(reinterpret_cast<uintptr_t>(buffer) + offset_round_up_difference, + buffer_alignment)) { + aligned_core_buffer = buffer + offset_round_up_difference; + + core_offset = Common::AlignUp(offset, data_alignment); + core_size = (size < offset_round_up_difference) + ? 0 + : Common::AlignDown(size - offset_round_up_difference, data_alignment); + buffer_gap = 0; + offset_gap = 0; + + covered_offset = core_size > 0 ? core_offset : offset; + } else { + const size_t buffer_round_up_difference = GetRoundUpDifference(buffer, buffer_alignment); + + aligned_core_buffer = buffer + buffer_round_up_difference; + + core_offset = Common::AlignDown(offset, data_alignment); + core_size = (size < buffer_round_up_difference) + ? 0 + : Common::AlignDown(size - buffer_round_up_difference, data_alignment); + buffer_gap = buffer_round_up_difference; + offset_gap = GetRoundDownDifference(offset, data_alignment); + + covered_offset = offset; + } + + // Read the core portion. + if (core_size > 0) { + base_storage->Read(aligned_core_buffer, core_size, core_offset); + + if (offset_gap != 0 || buffer_gap != 0) { + std::memmove(aligned_core_buffer - buffer_gap, aligned_core_buffer + offset_gap, + core_size - offset_gap); + core_size -= offset_gap; + } + } + + // Handle the head portion. + if (offset < covered_offset) { + const s64 head_offset = Common::AlignDown(offset, data_alignment); + const size_t head_size = static_cast<size_t>(covered_offset - offset); + + ASSERT(GetRoundDownDifference(offset, data_alignment) + head_size <= work_buf_size); + + base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, head_offset); + std::memcpy(buffer, work_buf + GetRoundDownDifference(offset, data_alignment), head_size); + } + + // Handle the tail portion. + s64 tail_offset = covered_offset + core_size; + size_t remaining_tail_size = static_cast<size_t>((offset + size) - tail_offset); + while (remaining_tail_size > 0) { + const auto aligned_tail_offset = Common::AlignDown(tail_offset, data_alignment); + const auto cur_size = + std::min(static_cast<size_t>(aligned_tail_offset + data_alignment - tail_offset), + remaining_tail_size); + base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, aligned_tail_offset); + + ASSERT((tail_offset - offset) + cur_size <= size); + ASSERT((tail_offset - aligned_tail_offset) + cur_size <= data_alignment); + std::memcpy(reinterpret_cast<char*>(buffer) + (tail_offset - offset), + work_buf + (tail_offset - aligned_tail_offset), cur_size); + + remaining_tail_size -= cur_size; + tail_offset += cur_size; + } + + return size; +} + +size_t AlignmentMatchingStorageImpl::Write(VirtualFile base_storage, char* work_buf, + size_t work_buf_size, size_t data_alignment, + size_t buffer_alignment, s64 offset, const u8* buffer, + size_t size) { + // Check preconditions. + ASSERT(work_buf_size >= data_alignment); + + // Succeed if zero size. + if (size == 0) { + return size; + } + + // Validate arguments. + ASSERT(buffer != nullptr); + + // Determine extents. + const u8* aligned_core_buffer; + s64 core_offset; + size_t core_size; + s64 covered_offset; + + const size_t offset_round_up_difference = GetRoundUpDifference(offset, data_alignment); + if (Common::IsAligned(reinterpret_cast<uintptr_t>(buffer) + offset_round_up_difference, + buffer_alignment)) { + aligned_core_buffer = buffer + offset_round_up_difference; + + core_offset = Common::AlignUp(offset, data_alignment); + core_size = (size < offset_round_up_difference) + ? 0 + : Common::AlignDown(size - offset_round_up_difference, data_alignment); + + covered_offset = core_size > 0 ? core_offset : offset; + } else { + aligned_core_buffer = nullptr; + + core_offset = Common::AlignDown(offset, data_alignment); + core_size = 0; + + covered_offset = offset; + } + + // Write the core portion. + if (core_size > 0) { + base_storage->Write(aligned_core_buffer, core_size, core_offset); + } + + // Handle the head portion. + if (offset < covered_offset) { + const s64 head_offset = Common::AlignDown(offset, data_alignment); + const size_t head_size = static_cast<size_t>(covered_offset - offset); + + ASSERT((offset - head_offset) + head_size <= data_alignment); + + base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, head_offset); + std::memcpy(work_buf + (offset - head_offset), buffer, head_size); + base_storage->Write(reinterpret_cast<u8*>(work_buf), data_alignment, head_offset); + } + + // Handle the tail portion. + s64 tail_offset = covered_offset + core_size; + size_t remaining_tail_size = static_cast<size_t>((offset + size) - tail_offset); + while (remaining_tail_size > 0) { + ASSERT(static_cast<size_t>(tail_offset - offset) < size); + + const auto aligned_tail_offset = Common::AlignDown(tail_offset, data_alignment); + const auto cur_size = + std::min(static_cast<size_t>(aligned_tail_offset + data_alignment - tail_offset), + remaining_tail_size); + + base_storage->Read(reinterpret_cast<u8*>(work_buf), data_alignment, aligned_tail_offset); + std::memcpy(work_buf + GetRoundDownDifference(tail_offset, data_alignment), + buffer + (tail_offset - offset), cur_size); + base_storage->Write(reinterpret_cast<u8*>(work_buf), data_alignment, aligned_tail_offset); + + remaining_tail_size -= cur_size; + tail_offset += cur_size; + } + + return size; +} + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h new file mode 100644 index 000000000..4a05b0e88 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_alignment_matching_storage_impl.h @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/file_sys/errors.h" +#include "core/file_sys/fssystem/fs_i_storage.h" + +namespace FileSys { + +class AlignmentMatchingStorageImpl { +public: + static size_t Read(VirtualFile base_storage, char* work_buf, size_t work_buf_size, + size_t data_alignment, size_t buffer_alignment, s64 offset, u8* buffer, + size_t size); + static size_t Write(VirtualFile base_storage, char* work_buf, size_t work_buf_size, + size_t data_alignment, size_t buffer_alignment, s64 offset, + const u8* buffer, size_t size); +}; + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp b/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp new file mode 100644 index 000000000..af8541009 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_bucket_tree.cpp @@ -0,0 +1,598 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/file_sys/errors.h" +#include "core/file_sys/fssystem/fssystem_bucket_tree.h" +#include "core/file_sys/fssystem/fssystem_bucket_tree_utils.h" +#include "core/file_sys/fssystem/fssystem_pooled_buffer.h" + +namespace FileSys { + +namespace { + +using Node = impl::BucketTreeNode<const s64*>; +static_assert(sizeof(Node) == sizeof(BucketTree::NodeHeader)); +static_assert(std::is_trivial_v<Node>); + +constexpr inline s32 NodeHeaderSize = sizeof(BucketTree::NodeHeader); + +class StorageNode { +private: + class Offset { + public: + using difference_type = s64; + + private: + s64 m_offset; + s32 m_stride; + + public: + constexpr Offset(s64 offset, s32 stride) : m_offset(offset), m_stride(stride) {} + + constexpr Offset& operator++() { + m_offset += m_stride; + return *this; + } + constexpr Offset operator++(int) { + Offset ret(*this); + m_offset += m_stride; + return ret; + } + + constexpr Offset& operator--() { + m_offset -= m_stride; + return *this; + } + constexpr Offset operator--(int) { + Offset ret(*this); + m_offset -= m_stride; + return ret; + } + + constexpr difference_type operator-(const Offset& rhs) const { + return (m_offset - rhs.m_offset) / m_stride; + } + + constexpr Offset operator+(difference_type ofs) const { + return Offset(m_offset + ofs * m_stride, m_stride); + } + constexpr Offset operator-(difference_type ofs) const { + return Offset(m_offset - ofs * m_stride, m_stride); + } + + constexpr Offset& operator+=(difference_type ofs) { + m_offset += ofs * m_stride; + return *this; + } + constexpr Offset& operator-=(difference_type ofs) { + m_offset -= ofs * m_stride; + return *this; + } + + constexpr bool operator==(const Offset& rhs) const { + return m_offset == rhs.m_offset; + } + constexpr bool operator!=(const Offset& rhs) const { + return m_offset != rhs.m_offset; + } + + constexpr s64 Get() const { + return m_offset; + } + }; + +private: + const Offset m_start; + const s32 m_count; + s32 m_index; + +public: + StorageNode(size_t size, s32 count) + : m_start(NodeHeaderSize, static_cast<s32>(size)), m_count(count), m_index(-1) {} + StorageNode(s64 ofs, size_t size, s32 count) + : m_start(NodeHeaderSize + ofs, static_cast<s32>(size)), m_count(count), m_index(-1) {} + + s32 GetIndex() const { + return m_index; + } + + void Find(const char* buffer, s64 virtual_address) { + s32 end = m_count; + auto pos = m_start; + + while (end > 0) { + auto half = end / 2; + auto mid = pos + half; + + s64 offset = 0; + std::memcpy(std::addressof(offset), buffer + mid.Get(), sizeof(s64)); + + if (offset <= virtual_address) { + pos = mid + 1; + end -= half + 1; + } else { + end = half; + } + } + + m_index = static_cast<s32>(pos - m_start) - 1; + } + + Result Find(VirtualFile storage, s64 virtual_address) { + s32 end = m_count; + auto pos = m_start; + + while (end > 0) { + auto half = end / 2; + auto mid = pos + half; + + s64 offset = 0; + storage->ReadObject(std::addressof(offset), mid.Get()); + + if (offset <= virtual_address) { + pos = mid + 1; + end -= half + 1; + } else { + end = half; + } + } + + m_index = static_cast<s32>(pos - m_start) - 1; + R_SUCCEED(); + } +}; + +} // namespace + +void BucketTree::Header::Format(s32 entry_count_) { + ASSERT(entry_count_ >= 0); + + this->magic = Magic; + this->version = Version; + this->entry_count = entry_count_; + this->reserved = 0; +} + +Result BucketTree::Header::Verify() const { + R_UNLESS(this->magic == Magic, ResultInvalidBucketTreeSignature); + R_UNLESS(this->entry_count >= 0, ResultInvalidBucketTreeEntryCount); + R_UNLESS(this->version <= Version, ResultUnsupportedVersion); + R_SUCCEED(); +} + +Result BucketTree::NodeHeader::Verify(s32 node_index, size_t node_size, size_t entry_size) const { + R_UNLESS(this->index == node_index, ResultInvalidBucketTreeNodeIndex); + R_UNLESS(entry_size != 0 && node_size >= entry_size + NodeHeaderSize, ResultInvalidSize); + + const size_t max_entry_count = (node_size - NodeHeaderSize) / entry_size; + R_UNLESS(this->count > 0 && static_cast<size_t>(this->count) <= max_entry_count, + ResultInvalidBucketTreeNodeEntryCount); + R_UNLESS(this->offset >= 0, ResultInvalidBucketTreeNodeOffset); + + R_SUCCEED(); +} + +Result BucketTree::Initialize(VirtualFile node_storage, VirtualFile entry_storage, size_t node_size, + size_t entry_size, s32 entry_count) { + // Validate preconditions. + ASSERT(entry_size >= sizeof(s64)); + ASSERT(node_size >= entry_size + sizeof(NodeHeader)); + ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax); + ASSERT(Common::IsPowerOfTwo(node_size)); + ASSERT(!this->IsInitialized()); + + // Ensure valid entry count. + R_UNLESS(entry_count > 0, ResultInvalidArgument); + + // Allocate node. + R_UNLESS(m_node_l1.Allocate(node_size), ResultBufferAllocationFailed); + ON_RESULT_FAILURE { + m_node_l1.Free(node_size); + }; + + // Read node. + node_storage->Read(reinterpret_cast<u8*>(m_node_l1.Get()), node_size); + + // Verify node. + R_TRY(m_node_l1->Verify(0, node_size, sizeof(s64))); + + // Validate offsets. + const auto offset_count = GetOffsetCount(node_size); + const auto entry_set_count = GetEntrySetCount(node_size, entry_size, entry_count); + const auto* const node = m_node_l1.Get<Node>(); + + s64 start_offset; + if (offset_count < entry_set_count && node->GetCount() < offset_count) { + start_offset = *node->GetEnd(); + } else { + start_offset = *node->GetBegin(); + } + const auto end_offset = node->GetEndOffset(); + + R_UNLESS(0 <= start_offset && start_offset <= node->GetBeginOffset(), + ResultInvalidBucketTreeEntryOffset); + R_UNLESS(start_offset < end_offset, ResultInvalidBucketTreeEntryOffset); + + // Set member variables. + m_node_storage = node_storage; + m_entry_storage = entry_storage; + m_node_size = node_size; + m_entry_size = entry_size; + m_entry_count = entry_count; + m_offset_count = offset_count; + m_entry_set_count = entry_set_count; + + m_offset_cache.offsets.start_offset = start_offset; + m_offset_cache.offsets.end_offset = end_offset; + m_offset_cache.is_initialized = true; + + // We succeeded. + R_SUCCEED(); +} + +void BucketTree::Initialize(size_t node_size, s64 end_offset) { + ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax); + ASSERT(Common::IsPowerOfTwo(node_size)); + ASSERT(end_offset > 0); + ASSERT(!this->IsInitialized()); + + m_node_size = node_size; + + m_offset_cache.offsets.start_offset = 0; + m_offset_cache.offsets.end_offset = end_offset; + m_offset_cache.is_initialized = true; +} + +void BucketTree::Finalize() { + if (this->IsInitialized()) { + m_node_storage = VirtualFile(); + m_entry_storage = VirtualFile(); + m_node_l1.Free(m_node_size); + m_node_size = 0; + m_entry_size = 0; + m_entry_count = 0; + m_offset_count = 0; + m_entry_set_count = 0; + + m_offset_cache.offsets.start_offset = 0; + m_offset_cache.offsets.end_offset = 0; + m_offset_cache.is_initialized = false; + } +} + +Result BucketTree::Find(Visitor* visitor, s64 virtual_address) { + ASSERT(visitor != nullptr); + ASSERT(this->IsInitialized()); + + R_UNLESS(virtual_address >= 0, ResultInvalidOffset); + R_UNLESS(!this->IsEmpty(), ResultOutOfRange); + + BucketTree::Offsets offsets; + R_TRY(this->GetOffsets(std::addressof(offsets))); + + R_TRY(visitor->Initialize(this, offsets)); + + R_RETURN(visitor->Find(virtual_address)); +} + +Result BucketTree::InvalidateCache() { + // Reset our offsets. + m_offset_cache.is_initialized = false; + + R_SUCCEED(); +} + +Result BucketTree::EnsureOffsetCache() { + // If we already have an offset cache, we're good. + R_SUCCEED_IF(m_offset_cache.is_initialized); + + // Acquire exclusive right to edit the offset cache. + std::scoped_lock lk(m_offset_cache.mutex); + + // Check again, to be sure. + R_SUCCEED_IF(m_offset_cache.is_initialized); + + // Read/verify L1. + m_node_storage->Read(reinterpret_cast<u8*>(m_node_l1.Get()), m_node_size); + R_TRY(m_node_l1->Verify(0, m_node_size, sizeof(s64))); + + // Get the node. + auto* const node = m_node_l1.Get<Node>(); + + s64 start_offset; + if (m_offset_count < m_entry_set_count && node->GetCount() < m_offset_count) { + start_offset = *node->GetEnd(); + } else { + start_offset = *node->GetBegin(); + } + const auto end_offset = node->GetEndOffset(); + + R_UNLESS(0 <= start_offset && start_offset <= node->GetBeginOffset(), + ResultInvalidBucketTreeEntryOffset); + R_UNLESS(start_offset < end_offset, ResultInvalidBucketTreeEntryOffset); + + m_offset_cache.offsets.start_offset = start_offset; + m_offset_cache.offsets.end_offset = end_offset; + m_offset_cache.is_initialized = true; + + R_SUCCEED(); +} + +Result BucketTree::Visitor::Initialize(const BucketTree* tree, const BucketTree::Offsets& offsets) { + ASSERT(tree != nullptr); + ASSERT(m_tree == nullptr || m_tree == tree); + + if (m_entry == nullptr) { + m_entry = ::operator new(tree->m_entry_size); + R_UNLESS(m_entry != nullptr, ResultBufferAllocationFailed); + + m_tree = tree; + m_offsets = offsets; + } + + R_SUCCEED(); +} + +Result BucketTree::Visitor::MoveNext() { + R_UNLESS(this->IsValid(), ResultOutOfRange); + + // Invalidate our index, and read the header for the next index. + auto entry_index = m_entry_index + 1; + if (entry_index == m_entry_set.info.count) { + const auto entry_set_index = m_entry_set.info.index + 1; + R_UNLESS(entry_set_index < m_entry_set_count, ResultOutOfRange); + + m_entry_index = -1; + + const auto end = m_entry_set.info.end; + + const auto entry_set_size = m_tree->m_node_size; + const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size); + + m_tree->m_entry_storage->ReadObject(std::addressof(m_entry_set), entry_set_offset); + R_TRY(m_entry_set.header.Verify(entry_set_index, entry_set_size, m_tree->m_entry_size)); + + R_UNLESS(m_entry_set.info.start == end && m_entry_set.info.start < m_entry_set.info.end, + ResultInvalidBucketTreeEntrySetOffset); + + entry_index = 0; + } else { + m_entry_index = -1; + } + + // Read the new entry. + const auto entry_size = m_tree->m_entry_size; + const auto entry_offset = impl::GetBucketTreeEntryOffset( + m_entry_set.info.index, m_tree->m_node_size, entry_size, entry_index); + m_tree->m_entry_storage->Read(reinterpret_cast<u8*>(m_entry), entry_size, entry_offset); + + // Note that we changed index. + m_entry_index = entry_index; + R_SUCCEED(); +} + +Result BucketTree::Visitor::MovePrevious() { + R_UNLESS(this->IsValid(), ResultOutOfRange); + + // Invalidate our index, and read the header for the previous index. + auto entry_index = m_entry_index; + if (entry_index == 0) { + R_UNLESS(m_entry_set.info.index > 0, ResultOutOfRange); + + m_entry_index = -1; + + const auto start = m_entry_set.info.start; + + const auto entry_set_size = m_tree->m_node_size; + const auto entry_set_index = m_entry_set.info.index - 1; + const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size); + + m_tree->m_entry_storage->ReadObject(std::addressof(m_entry_set), entry_set_offset); + R_TRY(m_entry_set.header.Verify(entry_set_index, entry_set_size, m_tree->m_entry_size)); + + R_UNLESS(m_entry_set.info.end == start && m_entry_set.info.start < m_entry_set.info.end, + ResultInvalidBucketTreeEntrySetOffset); + + entry_index = m_entry_set.info.count; + } else { + m_entry_index = -1; + } + + --entry_index; + + // Read the new entry. + const auto entry_size = m_tree->m_entry_size; + const auto entry_offset = impl::GetBucketTreeEntryOffset( + m_entry_set.info.index, m_tree->m_node_size, entry_size, entry_index); + m_tree->m_entry_storage->Read(reinterpret_cast<u8*>(m_entry), entry_size, entry_offset); + + // Note that we changed index. + m_entry_index = entry_index; + R_SUCCEED(); +} + +Result BucketTree::Visitor::Find(s64 virtual_address) { + ASSERT(m_tree != nullptr); + + // Get the node. + const auto* const node = m_tree->m_node_l1.Get<Node>(); + R_UNLESS(virtual_address < node->GetEndOffset(), ResultOutOfRange); + + // Get the entry set index. + s32 entry_set_index = -1; + if (m_tree->IsExistOffsetL2OnL1() && virtual_address < node->GetBeginOffset()) { + const auto start = node->GetEnd(); + const auto end = node->GetBegin() + m_tree->m_offset_count; + + auto pos = std::upper_bound(start, end, virtual_address); + R_UNLESS(start < pos, ResultOutOfRange); + --pos; + + entry_set_index = static_cast<s32>(pos - start); + } else { + const auto start = node->GetBegin(); + const auto end = node->GetEnd(); + + auto pos = std::upper_bound(start, end, virtual_address); + R_UNLESS(start < pos, ResultOutOfRange); + --pos; + + if (m_tree->IsExistL2()) { + const auto node_index = static_cast<s32>(pos - start); + R_UNLESS(0 <= node_index && node_index < m_tree->m_offset_count, + ResultInvalidBucketTreeNodeOffset); + + R_TRY(this->FindEntrySet(std::addressof(entry_set_index), virtual_address, node_index)); + } else { + entry_set_index = static_cast<s32>(pos - start); + } + } + + // Validate the entry set index. + R_UNLESS(0 <= entry_set_index && entry_set_index < m_tree->m_entry_set_count, + ResultInvalidBucketTreeNodeOffset); + + // Find the entry. + R_TRY(this->FindEntry(virtual_address, entry_set_index)); + + // Set count. + m_entry_set_count = m_tree->m_entry_set_count; + R_SUCCEED(); +} + +Result BucketTree::Visitor::FindEntrySet(s32* out_index, s64 virtual_address, s32 node_index) { + const auto node_size = m_tree->m_node_size; + + PooledBuffer pool(node_size, 1); + if (node_size <= pool.GetSize()) { + R_RETURN( + this->FindEntrySetWithBuffer(out_index, virtual_address, node_index, pool.GetBuffer())); + } else { + pool.Deallocate(); + R_RETURN(this->FindEntrySetWithoutBuffer(out_index, virtual_address, node_index)); + } +} + +Result BucketTree::Visitor::FindEntrySetWithBuffer(s32* out_index, s64 virtual_address, + s32 node_index, char* buffer) { + // Calculate node extents. + const auto node_size = m_tree->m_node_size; + const auto node_offset = (node_index + 1) * static_cast<s64>(node_size); + VirtualFile storage = m_tree->m_node_storage; + + // Read the node. + storage->Read(reinterpret_cast<u8*>(buffer), node_size, node_offset); + + // Validate the header. + NodeHeader header; + std::memcpy(std::addressof(header), buffer, NodeHeaderSize); + R_TRY(header.Verify(node_index, node_size, sizeof(s64))); + + // Create the node, and find. + StorageNode node(sizeof(s64), header.count); + node.Find(buffer, virtual_address); + R_UNLESS(node.GetIndex() >= 0, ResultInvalidBucketTreeVirtualOffset); + + // Return the index. + *out_index = static_cast<s32>(m_tree->GetEntrySetIndex(header.index, node.GetIndex())); + R_SUCCEED(); +} + +Result BucketTree::Visitor::FindEntrySetWithoutBuffer(s32* out_index, s64 virtual_address, + s32 node_index) { + // Calculate node extents. + const auto node_size = m_tree->m_node_size; + const auto node_offset = (node_index + 1) * static_cast<s64>(node_size); + VirtualFile storage = m_tree->m_node_storage; + + // Read and validate the header. + NodeHeader header; + storage->ReadObject(std::addressof(header), node_offset); + R_TRY(header.Verify(node_index, node_size, sizeof(s64))); + + // Create the node, and find. + StorageNode node(node_offset, sizeof(s64), header.count); + R_TRY(node.Find(storage, virtual_address)); + R_UNLESS(node.GetIndex() >= 0, ResultOutOfRange); + + // Return the index. + *out_index = static_cast<s32>(m_tree->GetEntrySetIndex(header.index, node.GetIndex())); + R_SUCCEED(); +} + +Result BucketTree::Visitor::FindEntry(s64 virtual_address, s32 entry_set_index) { + const auto entry_set_size = m_tree->m_node_size; + + PooledBuffer pool(entry_set_size, 1); + if (entry_set_size <= pool.GetSize()) { + R_RETURN(this->FindEntryWithBuffer(virtual_address, entry_set_index, pool.GetBuffer())); + } else { + pool.Deallocate(); + R_RETURN(this->FindEntryWithoutBuffer(virtual_address, entry_set_index)); + } +} + +Result BucketTree::Visitor::FindEntryWithBuffer(s64 virtual_address, s32 entry_set_index, + char* buffer) { + // Calculate entry set extents. + const auto entry_size = m_tree->m_entry_size; + const auto entry_set_size = m_tree->m_node_size; + const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size); + VirtualFile storage = m_tree->m_entry_storage; + + // Read the entry set. + storage->Read(reinterpret_cast<u8*>(buffer), entry_set_size, entry_set_offset); + + // Validate the entry_set. + EntrySetHeader entry_set; + std::memcpy(std::addressof(entry_set), buffer, sizeof(EntrySetHeader)); + R_TRY(entry_set.header.Verify(entry_set_index, entry_set_size, entry_size)); + + // Create the node, and find. + StorageNode node(entry_size, entry_set.info.count); + node.Find(buffer, virtual_address); + R_UNLESS(node.GetIndex() >= 0, ResultOutOfRange); + + // Copy the data into entry. + const auto entry_index = node.GetIndex(); + const auto entry_offset = impl::GetBucketTreeEntryOffset(0, entry_size, entry_index); + std::memcpy(m_entry, buffer + entry_offset, entry_size); + + // Set our entry set/index. + m_entry_set = entry_set; + m_entry_index = entry_index; + + R_SUCCEED(); +} + +Result BucketTree::Visitor::FindEntryWithoutBuffer(s64 virtual_address, s32 entry_set_index) { + // Calculate entry set extents. + const auto entry_size = m_tree->m_entry_size; + const auto entry_set_size = m_tree->m_node_size; + const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size); + VirtualFile storage = m_tree->m_entry_storage; + + // Read and validate the entry_set. + EntrySetHeader entry_set; + storage->ReadObject(std::addressof(entry_set), entry_set_offset); + R_TRY(entry_set.header.Verify(entry_set_index, entry_set_size, entry_size)); + + // Create the node, and find. + StorageNode node(entry_set_offset, entry_size, entry_set.info.count); + R_TRY(node.Find(storage, virtual_address)); + R_UNLESS(node.GetIndex() >= 0, ResultOutOfRange); + + // Copy the data into entry. + const auto entry_index = node.GetIndex(); + const auto entry_offset = + impl::GetBucketTreeEntryOffset(entry_set_offset, entry_size, entry_index); + storage->Read(reinterpret_cast<u8*>(m_entry), entry_size, entry_offset); + + // Set our entry set/index. + m_entry_set = entry_set; + m_entry_index = entry_index; + + R_SUCCEED(); +} + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree.h b/src/core/file_sys/fssystem/fssystem_bucket_tree.h new file mode 100644 index 000000000..46850cd48 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_bucket_tree.h @@ -0,0 +1,416 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <mutex> + +#include "common/alignment.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/literals.h" + +#include "core/file_sys/vfs.h" +#include "core/hle/result.h" + +namespace FileSys { + +using namespace Common::Literals; + +class BucketTree { + YUZU_NON_COPYABLE(BucketTree); + YUZU_NON_MOVEABLE(BucketTree); + +public: + static constexpr u32 Magic = Common::MakeMagic('B', 'K', 'T', 'R'); + static constexpr u32 Version = 1; + + static constexpr size_t NodeSizeMin = 1_KiB; + static constexpr size_t NodeSizeMax = 512_KiB; + +public: + class Visitor; + + struct Header { + u32 magic; + u32 version; + s32 entry_count; + s32 reserved; + + void Format(s32 entry_count); + Result Verify() const; + }; + static_assert(std::is_trivial_v<Header>); + static_assert(sizeof(Header) == 0x10); + + struct NodeHeader { + s32 index; + s32 count; + s64 offset; + + Result Verify(s32 node_index, size_t node_size, size_t entry_size) const; + }; + static_assert(std::is_trivial_v<NodeHeader>); + static_assert(sizeof(NodeHeader) == 0x10); + + struct Offsets { + s64 start_offset; + s64 end_offset; + + constexpr bool IsInclude(s64 offset) const { + return this->start_offset <= offset && offset < this->end_offset; + } + + constexpr bool IsInclude(s64 offset, s64 size) const { + return size > 0 && this->start_offset <= offset && size <= (this->end_offset - offset); + } + }; + static_assert(std::is_trivial_v<Offsets>); + static_assert(sizeof(Offsets) == 0x10); + + struct OffsetCache { + Offsets offsets; + std::mutex mutex; + bool is_initialized; + + OffsetCache() : offsets{-1, -1}, mutex(), is_initialized(false) {} + }; + + class ContinuousReadingInfo { + public: + constexpr ContinuousReadingInfo() : m_read_size(), m_skip_count(), m_done() {} + + constexpr void Reset() { + m_read_size = 0; + m_skip_count = 0; + m_done = false; + } + + constexpr void SetSkipCount(s32 count) { + ASSERT(count >= 0); + m_skip_count = count; + } + constexpr s32 GetSkipCount() const { + return m_skip_count; + } + constexpr bool CheckNeedScan() { + return (--m_skip_count) <= 0; + } + + constexpr void Done() { + m_read_size = 0; + m_done = true; + } + constexpr bool IsDone() const { + return m_done; + } + + constexpr void SetReadSize(size_t size) { + m_read_size = size; + } + constexpr size_t GetReadSize() const { + return m_read_size; + } + constexpr bool CanDo() const { + return m_read_size > 0; + } + + private: + size_t m_read_size; + s32 m_skip_count; + bool m_done; + }; + +private: + class NodeBuffer { + YUZU_NON_COPYABLE(NodeBuffer); + + public: + NodeBuffer() : m_header() {} + + ~NodeBuffer() { + ASSERT(m_header == nullptr); + } + + NodeBuffer(NodeBuffer&& rhs) : m_header(rhs.m_header) { + rhs.m_header = nullptr; + } + + NodeBuffer& operator=(NodeBuffer&& rhs) { + if (this != std::addressof(rhs)) { + ASSERT(m_header == nullptr); + + m_header = rhs.m_header; + + rhs.m_header = nullptr; + } + return *this; + } + + bool Allocate(size_t node_size) { + ASSERT(m_header == nullptr); + + m_header = ::operator new(node_size, std::align_val_t{sizeof(s64)}); + + // ASSERT(Common::IsAligned(m_header, sizeof(s64))); + + return m_header != nullptr; + } + + void Free(size_t node_size) { + if (m_header) { + ::operator delete(m_header, std::align_val_t{sizeof(s64)}); + m_header = nullptr; + } + } + + void FillZero(size_t node_size) const { + if (m_header) { + std::memset(m_header, 0, node_size); + } + } + + NodeHeader* Get() const { + return reinterpret_cast<NodeHeader*>(m_header); + } + + NodeHeader* operator->() const { + return this->Get(); + } + + template <typename T> + T* Get() const { + static_assert(std::is_trivial_v<T>); + static_assert(sizeof(T) == sizeof(NodeHeader)); + return reinterpret_cast<T*>(m_header); + } + + private: + void* m_header; + }; + +private: + static constexpr s32 GetEntryCount(size_t node_size, size_t entry_size) { + return static_cast<s32>((node_size - sizeof(NodeHeader)) / entry_size); + } + + static constexpr s32 GetOffsetCount(size_t node_size) { + return static_cast<s32>((node_size - sizeof(NodeHeader)) / sizeof(s64)); + } + + static constexpr s32 GetEntrySetCount(size_t node_size, size_t entry_size, s32 entry_count) { + const s32 entry_count_per_node = GetEntryCount(node_size, entry_size); + return Common::DivideUp(entry_count, entry_count_per_node); + } + + static constexpr s32 GetNodeL2Count(size_t node_size, size_t entry_size, s32 entry_count) { + const s32 offset_count_per_node = GetOffsetCount(node_size); + const s32 entry_set_count = GetEntrySetCount(node_size, entry_size, entry_count); + + if (entry_set_count <= offset_count_per_node) { + return 0; + } + + const s32 node_l2_count = Common::DivideUp(entry_set_count, offset_count_per_node); + ASSERT(node_l2_count <= offset_count_per_node); + + return Common::DivideUp(entry_set_count - (offset_count_per_node - (node_l2_count - 1)), + offset_count_per_node); + } + +public: + BucketTree() + : m_node_storage(), m_entry_storage(), m_node_l1(), m_node_size(), m_entry_size(), + m_entry_count(), m_offset_count(), m_entry_set_count(), m_offset_cache() {} + ~BucketTree() { + this->Finalize(); + } + + Result Initialize(VirtualFile node_storage, VirtualFile entry_storage, size_t node_size, + size_t entry_size, s32 entry_count); + void Initialize(size_t node_size, s64 end_offset); + void Finalize(); + + bool IsInitialized() const { + return m_node_size > 0; + } + bool IsEmpty() const { + return m_entry_size == 0; + } + + Result Find(Visitor* visitor, s64 virtual_address); + Result InvalidateCache(); + + s32 GetEntryCount() const { + return m_entry_count; + } + + Result GetOffsets(Offsets* out) { + // Ensure we have an offset cache. + R_TRY(this->EnsureOffsetCache()); + + // Set the output. + *out = m_offset_cache.offsets; + R_SUCCEED(); + } + +public: + static constexpr s64 QueryHeaderStorageSize() { + return sizeof(Header); + } + + static constexpr s64 QueryNodeStorageSize(size_t node_size, size_t entry_size, + s32 entry_count) { + ASSERT(entry_size >= sizeof(s64)); + ASSERT(node_size >= entry_size + sizeof(NodeHeader)); + ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax); + ASSERT(Common::IsPowerOfTwo(node_size)); + ASSERT(entry_count >= 0); + + if (entry_count <= 0) { + return 0; + } + return (1 + GetNodeL2Count(node_size, entry_size, entry_count)) * + static_cast<s64>(node_size); + } + + static constexpr s64 QueryEntryStorageSize(size_t node_size, size_t entry_size, + s32 entry_count) { + ASSERT(entry_size >= sizeof(s64)); + ASSERT(node_size >= entry_size + sizeof(NodeHeader)); + ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax); + ASSERT(Common::IsPowerOfTwo(node_size)); + ASSERT(entry_count >= 0); + + if (entry_count <= 0) { + return 0; + } + return GetEntrySetCount(node_size, entry_size, entry_count) * static_cast<s64>(node_size); + } + +private: + template <typename EntryType> + struct ContinuousReadingParam { + s64 offset; + size_t size; + NodeHeader entry_set; + s32 entry_index; + Offsets offsets; + EntryType entry; + }; + +private: + template <typename EntryType> + Result ScanContinuousReading(ContinuousReadingInfo* out_info, + const ContinuousReadingParam<EntryType>& param) const; + + bool IsExistL2() const { + return m_offset_count < m_entry_set_count; + } + bool IsExistOffsetL2OnL1() const { + return this->IsExistL2() && m_node_l1->count < m_offset_count; + } + + s64 GetEntrySetIndex(s32 node_index, s32 offset_index) const { + return (m_offset_count - m_node_l1->count) + (m_offset_count * node_index) + offset_index; + } + + Result EnsureOffsetCache(); + +private: + mutable VirtualFile m_node_storage; + mutable VirtualFile m_entry_storage; + NodeBuffer m_node_l1; + size_t m_node_size; + size_t m_entry_size; + s32 m_entry_count; + s32 m_offset_count; + s32 m_entry_set_count; + OffsetCache m_offset_cache; +}; + +class BucketTree::Visitor { + YUZU_NON_COPYABLE(Visitor); + YUZU_NON_MOVEABLE(Visitor); + +public: + constexpr Visitor() + : m_tree(), m_entry(), m_entry_index(-1), m_entry_set_count(), m_entry_set{} {} + ~Visitor() { + if (m_entry != nullptr) { + ::operator delete(m_entry, m_tree->m_entry_size); + m_tree = nullptr; + m_entry = nullptr; + } + } + + bool IsValid() const { + return m_entry_index >= 0; + } + bool CanMoveNext() const { + return this->IsValid() && (m_entry_index + 1 < m_entry_set.info.count || + m_entry_set.info.index + 1 < m_entry_set_count); + } + bool CanMovePrevious() const { + return this->IsValid() && (m_entry_index > 0 || m_entry_set.info.index > 0); + } + + Result MoveNext(); + Result MovePrevious(); + + template <typename EntryType> + Result ScanContinuousReading(ContinuousReadingInfo* out_info, s64 offset, size_t size) const; + + const void* Get() const { + ASSERT(this->IsValid()); + return m_entry; + } + + template <typename T> + const T* Get() const { + ASSERT(this->IsValid()); + return reinterpret_cast<const T*>(m_entry); + } + + const BucketTree* GetTree() const { + return m_tree; + } + +private: + Result Initialize(const BucketTree* tree, const BucketTree::Offsets& offsets); + + Result Find(s64 virtual_address); + + Result FindEntrySet(s32* out_index, s64 virtual_address, s32 node_index); + Result FindEntrySetWithBuffer(s32* out_index, s64 virtual_address, s32 node_index, + char* buffer); + Result FindEntrySetWithoutBuffer(s32* out_index, s64 virtual_address, s32 node_index); + + Result FindEntry(s64 virtual_address, s32 entry_set_index); + Result FindEntryWithBuffer(s64 virtual_address, s32 entry_set_index, char* buffer); + Result FindEntryWithoutBuffer(s64 virtual_address, s32 entry_set_index); + +private: + friend class BucketTree; + + union EntrySetHeader { + NodeHeader header; + struct Info { + s32 index; + s32 count; + s64 end; + s64 start; + } info; + static_assert(std::is_trivial_v<Info>); + }; + static_assert(std::is_trivial_v<EntrySetHeader>); + + const BucketTree* m_tree; + BucketTree::Offsets m_offsets; + void* m_entry; + s32 m_entry_index; + s32 m_entry_set_count; + EntrySetHeader m_entry_set; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h b/src/core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h new file mode 100644 index 000000000..030b2916b --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h @@ -0,0 +1,170 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/file_sys/errors.h" +#include "core/file_sys/fssystem/fssystem_bucket_tree.h" +#include "core/file_sys/fssystem/fssystem_bucket_tree_utils.h" +#include "core/file_sys/fssystem/fssystem_pooled_buffer.h" + +namespace FileSys { + +template <typename EntryType> +Result BucketTree::ScanContinuousReading(ContinuousReadingInfo* out_info, + const ContinuousReadingParam<EntryType>& param) const { + static_assert(std::is_trivial_v<ContinuousReadingParam<EntryType>>); + + // Validate our preconditions. + ASSERT(this->IsInitialized()); + ASSERT(out_info != nullptr); + ASSERT(m_entry_size == sizeof(EntryType)); + + // Reset the output. + out_info->Reset(); + + // If there's nothing to read, we're done. + R_SUCCEED_IF(param.size == 0); + + // If we're reading a fragment, we're done. + R_SUCCEED_IF(param.entry.IsFragment()); + + // Validate the first entry. + auto entry = param.entry; + auto cur_offset = param.offset; + R_UNLESS(entry.GetVirtualOffset() <= cur_offset, ResultOutOfRange); + + // Create a pooled buffer for our scan. + PooledBuffer pool(m_node_size, 1); + char* buffer = nullptr; + + s64 entry_storage_size = m_entry_storage->GetSize(); + + // Read the node. + if (m_node_size <= pool.GetSize()) { + buffer = pool.GetBuffer(); + const auto ofs = param.entry_set.index * static_cast<s64>(m_node_size); + R_UNLESS(m_node_size + ofs <= static_cast<size_t>(entry_storage_size), + ResultInvalidBucketTreeNodeEntryCount); + + m_entry_storage->Read(reinterpret_cast<u8*>(buffer), m_node_size, ofs); + } + + // Calculate extents. + const auto end_offset = cur_offset + static_cast<s64>(param.size); + s64 phys_offset = entry.GetPhysicalOffset(); + + // Start merge tracking. + s64 merge_size = 0; + s64 readable_size = 0; + bool merged = false; + + // Iterate. + auto entry_index = param.entry_index; + for (const auto entry_count = param.entry_set.count; entry_index < entry_count; ++entry_index) { + // If we're past the end, we're done. + if (end_offset <= cur_offset) { + break; + } + + // Validate the entry offset. + const auto entry_offset = entry.GetVirtualOffset(); + R_UNLESS(entry_offset <= cur_offset, ResultInvalidIndirectEntryOffset); + + // Get the next entry. + EntryType next_entry = {}; + s64 next_entry_offset; + + if (entry_index + 1 < entry_count) { + if (buffer != nullptr) { + const auto ofs = impl::GetBucketTreeEntryOffset(0, m_entry_size, entry_index + 1); + std::memcpy(std::addressof(next_entry), buffer + ofs, m_entry_size); + } else { + const auto ofs = impl::GetBucketTreeEntryOffset(param.entry_set.index, m_node_size, + m_entry_size, entry_index + 1); + m_entry_storage->ReadObject(std::addressof(next_entry), ofs); + } + + next_entry_offset = next_entry.GetVirtualOffset(); + R_UNLESS(param.offsets.IsInclude(next_entry_offset), ResultInvalidIndirectEntryOffset); + } else { + next_entry_offset = param.entry_set.offset; + } + + // Validate the next entry offset. + R_UNLESS(cur_offset < next_entry_offset, ResultInvalidIndirectEntryOffset); + + // Determine the much data there is. + const auto data_size = next_entry_offset - cur_offset; + ASSERT(data_size > 0); + + // Determine how much data we should read. + const auto remaining_size = end_offset - cur_offset; + const size_t read_size = static_cast<size_t>(std::min(data_size, remaining_size)); + ASSERT(read_size <= param.size); + + // Update our merge tracking. + if (entry.IsFragment()) { + // If we can't merge, stop looping. + if (EntryType::FragmentSizeMax <= read_size || remaining_size <= data_size) { + break; + } + + // Otherwise, add the current size to the merge size. + merge_size += read_size; + } else { + // If we can't merge, stop looping. + if (phys_offset != entry.GetPhysicalOffset()) { + break; + } + + // Add the size to the readable amount. + readable_size += merge_size + read_size; + ASSERT(readable_size <= static_cast<s64>(param.size)); + + // Update whether we've merged. + merged |= merge_size > 0; + merge_size = 0; + } + + // Advance. + cur_offset += read_size; + ASSERT(cur_offset <= end_offset); + + phys_offset += next_entry_offset - entry_offset; + entry = next_entry; + } + + // If we merged, set our readable size. + if (merged) { + out_info->SetReadSize(static_cast<size_t>(readable_size)); + } + out_info->SetSkipCount(entry_index - param.entry_index); + + R_SUCCEED(); +} + +template <typename EntryType> +Result BucketTree::Visitor::ScanContinuousReading(ContinuousReadingInfo* out_info, s64 offset, + size_t size) const { + static_assert(std::is_trivial_v<EntryType>); + ASSERT(this->IsValid()); + + // Create our parameters. + ContinuousReadingParam<EntryType> param = { + .offset = offset, + .size = size, + .entry_set = m_entry_set.header, + .entry_index = m_entry_index, + .offsets{}, + .entry{}, + }; + std::memcpy(std::addressof(param.offsets), std::addressof(m_offsets), + sizeof(BucketTree::Offsets)); + std::memcpy(std::addressof(param.entry), m_entry, sizeof(EntryType)); + + // Scan. + R_RETURN(m_tree->ScanContinuousReading<EntryType>(out_info, param)); +} + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_bucket_tree_utils.h b/src/core/file_sys/fssystem/fssystem_bucket_tree_utils.h new file mode 100644 index 000000000..5503613fc --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_bucket_tree_utils.h @@ -0,0 +1,110 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/file_sys/fssystem/fssystem_bucket_tree.h" + +namespace FileSys::impl { + +class SafeValue { +public: + static s64 GetInt64(const void* ptr) { + s64 value; + std::memcpy(std::addressof(value), ptr, sizeof(s64)); + return value; + } + + static s64 GetInt64(const s64* ptr) { + return GetInt64(static_cast<const void*>(ptr)); + } + + static s64 GetInt64(const s64& v) { + return GetInt64(std::addressof(v)); + } + + static void SetInt64(void* dst, const void* src) { + std::memcpy(dst, src, sizeof(s64)); + } + + static void SetInt64(void* dst, const s64* src) { + return SetInt64(dst, static_cast<const void*>(src)); + } + + static void SetInt64(void* dst, const s64& v) { + return SetInt64(dst, std::addressof(v)); + } +}; + +template <typename IteratorType> +struct BucketTreeNode { + using Header = BucketTree::NodeHeader; + + Header header; + + s32 GetCount() const { + return this->header.count; + } + + void* GetArray() { + return std::addressof(this->header) + 1; + } + template <typename T> + T* GetArray() { + return reinterpret_cast<T*>(this->GetArray()); + } + const void* GetArray() const { + return std::addressof(this->header) + 1; + } + template <typename T> + const T* GetArray() const { + return reinterpret_cast<const T*>(this->GetArray()); + } + + s64 GetBeginOffset() const { + return *this->GetArray<s64>(); + } + s64 GetEndOffset() const { + return this->header.offset; + } + + IteratorType GetBegin() { + return IteratorType(this->GetArray<s64>()); + } + IteratorType GetEnd() { + return IteratorType(this->GetArray<s64>()) + this->header.count; + } + IteratorType GetBegin() const { + return IteratorType(this->GetArray<s64>()); + } + IteratorType GetEnd() const { + return IteratorType(this->GetArray<s64>()) + this->header.count; + } + + IteratorType GetBegin(size_t entry_size) { + return IteratorType(this->GetArray(), entry_size); + } + IteratorType GetEnd(size_t entry_size) { + return IteratorType(this->GetArray(), entry_size) + this->header.count; + } + IteratorType GetBegin(size_t entry_size) const { + return IteratorType(this->GetArray(), entry_size); + } + IteratorType GetEnd(size_t entry_size) const { + return IteratorType(this->GetArray(), entry_size) + this->header.count; + } +}; + +constexpr inline s64 GetBucketTreeEntryOffset(s64 entry_set_offset, size_t entry_size, + s32 entry_index) { + return entry_set_offset + sizeof(BucketTree::NodeHeader) + + entry_index * static_cast<s64>(entry_size); +} + +constexpr inline s64 GetBucketTreeEntryOffset(s32 entry_set_index, size_t node_size, + size_t entry_size, s32 entry_index) { + return GetBucketTreeEntryOffset(entry_set_index * static_cast<s64>(node_size), entry_size, + entry_index); +} + +} // namespace FileSys::impl diff --git a/src/core/file_sys/fssystem/fssystem_compressed_storage.h b/src/core/file_sys/fssystem/fssystem_compressed_storage.h new file mode 100644 index 000000000..33d93938e --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_compressed_storage.h @@ -0,0 +1,963 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/literals.h" + +#include "core/file_sys/errors.h" +#include "core/file_sys/fssystem/fs_i_storage.h" +#include "core/file_sys/fssystem/fssystem_bucket_tree.h" +#include "core/file_sys/fssystem/fssystem_compression_common.h" +#include "core/file_sys/fssystem/fssystem_pooled_buffer.h" +#include "core/file_sys/vfs.h" + +namespace FileSys { + +using namespace Common::Literals; + +class CompressedStorage : public IReadOnlyStorage { + YUZU_NON_COPYABLE(CompressedStorage); + YUZU_NON_MOVEABLE(CompressedStorage); + +public: + static constexpr size_t NodeSize = 16_KiB; + + struct Entry { + s64 virt_offset; + s64 phys_offset; + CompressionType compression_type; + s32 phys_size; + + s64 GetPhysicalSize() const { + return this->phys_size; + } + }; + static_assert(std::is_trivial_v<Entry>); + static_assert(sizeof(Entry) == 0x18); + +public: + static constexpr s64 QueryNodeStorageSize(s32 entry_count) { + return BucketTree::QueryNodeStorageSize(NodeSize, sizeof(Entry), entry_count); + } + + static constexpr s64 QueryEntryStorageSize(s32 entry_count) { + return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count); + } + +private: + class CompressedStorageCore { + YUZU_NON_COPYABLE(CompressedStorageCore); + YUZU_NON_MOVEABLE(CompressedStorageCore); + + public: + CompressedStorageCore() : m_table(), m_data_storage() {} + + ~CompressedStorageCore() { + this->Finalize(); + } + + public: + Result Initialize(VirtualFile data_storage, VirtualFile node_storage, + VirtualFile entry_storage, s32 bktr_entry_count, size_t block_size_max, + size_t continuous_reading_size_max, + GetDecompressorFunction get_decompressor) { + // Check pre-conditions. + ASSERT(0 < block_size_max); + ASSERT(block_size_max <= continuous_reading_size_max); + ASSERT(get_decompressor != nullptr); + + // Initialize our entry table. + R_TRY(m_table.Initialize(node_storage, entry_storage, NodeSize, sizeof(Entry), + bktr_entry_count)); + + // Set our other fields. + m_block_size_max = block_size_max; + m_continuous_reading_size_max = continuous_reading_size_max; + m_data_storage = data_storage; + m_get_decompressor_function = get_decompressor; + + R_SUCCEED(); + } + + void Finalize() { + if (this->IsInitialized()) { + m_table.Finalize(); + m_data_storage = VirtualFile(); + } + } + + VirtualFile GetDataStorage() { + return m_data_storage; + } + + Result GetDataStorageSize(s64* out) { + // Check pre-conditions. + ASSERT(out != nullptr); + + // Get size. + *out = m_data_storage->GetSize(); + + R_SUCCEED(); + } + + BucketTree& GetEntryTable() { + return m_table; + } + + Result GetEntryList(Entry* out_entries, s32* out_read_count, s32 max_entry_count, + s64 offset, s64 size) { + // Check pre-conditions. + ASSERT(offset >= 0); + ASSERT(size >= 0); + ASSERT(this->IsInitialized()); + + // Check that we can output the count. + R_UNLESS(out_read_count != nullptr, ResultNullptrArgument); + + // Check that we have anything to read at all. + R_SUCCEED_IF(size == 0); + + // Check that either we have a buffer, or this is to determine how many we need. + if (max_entry_count != 0) { + R_UNLESS(out_entries != nullptr, ResultNullptrArgument); + } + + // Get the table offsets. + BucketTree::Offsets table_offsets; + R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); + + // Validate arguments. + R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange); + + // Find the offset in our tree. + BucketTree::Visitor visitor; + R_TRY(m_table.Find(std::addressof(visitor), offset)); + { + const auto entry_offset = visitor.Get<Entry>()->virt_offset; + R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset), + ResultUnexpectedInCompressedStorageA); + } + + // Get the entries. + const auto end_offset = offset + size; + s32 read_count = 0; + while (visitor.Get<Entry>()->virt_offset < end_offset) { + // If we should be setting the output, do so. + if (max_entry_count != 0) { + // Ensure we only read as many entries as we can. + if (read_count >= max_entry_count) { + break; + } + + // Set the current output entry. + out_entries[read_count] = *visitor.Get<Entry>(); + } + + // Increase the read count. + ++read_count; + + // If we're at the end, we're done. + if (!visitor.CanMoveNext()) { + break; + } + + // Move to the next entry. + R_TRY(visitor.MoveNext()); + } + + // Set the output read count. + *out_read_count = read_count; + R_SUCCEED(); + } + + Result GetSize(s64* out) { + // Check pre-conditions. + ASSERT(out != nullptr); + + // Get our table offsets. + BucketTree::Offsets offsets; + R_TRY(m_table.GetOffsets(std::addressof(offsets))); + + // Set the output. + *out = offsets.end_offset; + R_SUCCEED(); + } + + Result OperatePerEntry(s64 offset, s64 size, auto f) { + // Check pre-conditions. + ASSERT(offset >= 0); + ASSERT(size >= 0); + ASSERT(this->IsInitialized()); + + // Succeed if there's nothing to operate on. + R_SUCCEED_IF(size == 0); + + // Get the table offsets. + BucketTree::Offsets table_offsets; + R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); + + // Validate arguments. + R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange); + + // Find the offset in our tree. + BucketTree::Visitor visitor; + R_TRY(m_table.Find(std::addressof(visitor), offset)); + { + const auto entry_offset = visitor.Get<Entry>()->virt_offset; + R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset), + ResultUnexpectedInCompressedStorageA); + } + + // Prepare to operate in chunks. + auto cur_offset = offset; + const auto end_offset = offset + static_cast<s64>(size); + + while (cur_offset < end_offset) { + // Get the current entry. + const auto cur_entry = *visitor.Get<Entry>(); + + // Get and validate the entry's offset. + const auto cur_entry_offset = cur_entry.virt_offset; + R_UNLESS(cur_entry_offset <= cur_offset, ResultUnexpectedInCompressedStorageA); + + // Get and validate the next entry offset. + s64 next_entry_offset; + if (visitor.CanMoveNext()) { + R_TRY(visitor.MoveNext()); + next_entry_offset = visitor.Get<Entry>()->virt_offset; + R_UNLESS(table_offsets.IsInclude(next_entry_offset), + ResultUnexpectedInCompressedStorageA); + } else { + next_entry_offset = table_offsets.end_offset; + } + R_UNLESS(cur_offset < next_entry_offset, ResultUnexpectedInCompressedStorageA); + + // Get the offset of the entry in the data we read. + const auto data_offset = cur_offset - cur_entry_offset; + const auto data_size = (next_entry_offset - cur_entry_offset); + ASSERT(data_size > 0); + + // Determine how much is left. + const auto remaining_size = end_offset - cur_offset; + const auto cur_size = std::min<s64>(remaining_size, data_size - data_offset); + ASSERT(cur_size <= size); + + // Get the data storage size. + s64 storage_size = m_data_storage->GetSize(); + + // Check that our read remains naively physically in bounds. + R_UNLESS(0 <= cur_entry.phys_offset && cur_entry.phys_offset <= storage_size, + ResultUnexpectedInCompressedStorageC); + + // If we have any compression, verify that we remain physically in bounds. + if (cur_entry.compression_type != CompressionType::None) { + R_UNLESS(cur_entry.phys_offset + cur_entry.GetPhysicalSize() <= storage_size, + ResultUnexpectedInCompressedStorageC); + } + + // Check that block alignment requirements are met. + if (CompressionTypeUtility::IsBlockAlignmentRequired(cur_entry.compression_type)) { + R_UNLESS(Common::IsAligned(cur_entry.phys_offset, CompressionBlockAlignment), + ResultUnexpectedInCompressedStorageA); + } + + // Invoke the operator. + bool is_continuous = true; + R_TRY( + f(std::addressof(is_continuous), cur_entry, data_size, data_offset, cur_size)); + + // If not continuous, we're done. + if (!is_continuous) { + break; + } + + // Advance. + cur_offset += cur_size; + } + + R_SUCCEED(); + } + + public: + using ReadImplFunction = std::function<Result(void*, size_t)>; + using ReadFunction = std::function<Result(size_t, const ReadImplFunction&)>; + + public: + Result Read(s64 offset, s64 size, const ReadFunction& read_func) { + // Check pre-conditions. + ASSERT(offset >= 0); + ASSERT(this->IsInitialized()); + + // Succeed immediately, if we have nothing to read. + R_SUCCEED_IF(size == 0); + + // Declare read lambda. + constexpr int EntriesCountMax = 0x80; + struct Entries { + CompressionType compression_type; + u32 gap_from_prev; + u32 physical_size; + u32 virtual_size; + }; + std::array<Entries, EntriesCountMax> entries; + s32 entry_count = 0; + Entry prev_entry = { + .virt_offset = -1, + .phys_offset{}, + .compression_type{}, + .phys_size{}, + }; + bool will_allocate_pooled_buffer = false; + s64 required_access_physical_offset = 0; + s64 required_access_physical_size = 0; + + auto PerformRequiredRead = [&]() -> Result { + // If there are no entries, we have nothing to do. + R_SUCCEED_IF(entry_count == 0); + + // Get the remaining size in a convenient form. + const size_t total_required_size = + static_cast<size_t>(required_access_physical_size); + + // Perform the read based on whether we need to allocate a buffer. + if (will_allocate_pooled_buffer) { + // Allocate a pooled buffer. + PooledBuffer pooled_buffer; + if (pooled_buffer.GetAllocatableSizeMax() >= total_required_size) { + pooled_buffer.Allocate(total_required_size, m_block_size_max); + } else { + pooled_buffer.AllocateParticularlyLarge( + std::min<size_t>( + total_required_size, + PooledBuffer::GetAllocatableParticularlyLargeSizeMax()), + m_block_size_max); + } + + // Read each of the entries. + for (s32 entry_idx = 0; entry_idx < entry_count; ++entry_idx) { + // Determine the current read size. + bool will_use_pooled_buffer = false; + const size_t cur_read_size = [&]() -> size_t { + if (const size_t target_entry_size = + static_cast<size_t>(entries[entry_idx].physical_size) + + static_cast<size_t>(entries[entry_idx].gap_from_prev); + target_entry_size <= pooled_buffer.GetSize()) { + // We'll be using the pooled buffer. + will_use_pooled_buffer = true; + + // Determine how much we can read. + const size_t max_size = std::min<size_t>( + required_access_physical_size, pooled_buffer.GetSize()); + + size_t read_size = 0; + for (auto n = entry_idx; n < entry_count; ++n) { + const size_t cur_entry_size = + static_cast<size_t>(entries[n].physical_size) + + static_cast<size_t>(entries[n].gap_from_prev); + if (read_size + cur_entry_size > max_size) { + break; + } + + read_size += cur_entry_size; + } + + return read_size; + } else { + // If we don't fit, we must be uncompressed. + ASSERT(entries[entry_idx].compression_type == + CompressionType::None); + + // We can perform the whole of an uncompressed read directly. + return entries[entry_idx].virtual_size; + } + }(); + + // Perform the read based on whether or not we'll use the pooled buffer. + if (will_use_pooled_buffer) { + // Read the compressed data into the pooled buffer. + auto* const buffer = pooled_buffer.GetBuffer(); + m_data_storage->Read(reinterpret_cast<u8*>(buffer), cur_read_size, + required_access_physical_offset); + + // Decompress the data. + size_t buffer_offset; + for (buffer_offset = 0; + entry_idx < entry_count && + ((static_cast<size_t>(entries[entry_idx].physical_size) + + static_cast<size_t>(entries[entry_idx].gap_from_prev)) == 0 || + buffer_offset < cur_read_size); + buffer_offset += entries[entry_idx++].physical_size) { + // Advance by the relevant gap. + buffer_offset += entries[entry_idx].gap_from_prev; + + const auto compression_type = entries[entry_idx].compression_type; + switch (compression_type) { + case CompressionType::None: { + // Check that we can remain within bounds. + ASSERT(buffer_offset + entries[entry_idx].virtual_size <= + cur_read_size); + + // Perform no decompression. + R_TRY(read_func( + entries[entry_idx].virtual_size, + [&](void* dst, size_t dst_size) -> Result { + // Check that the size is valid. + ASSERT(dst_size == entries[entry_idx].virtual_size); + + // We have no compression, so just copy the data + // out. + std::memcpy(dst, buffer + buffer_offset, + entries[entry_idx].virtual_size); + R_SUCCEED(); + })); + + break; + } + case CompressionType::Zeros: { + // Check that we can remain within bounds. + ASSERT(buffer_offset <= cur_read_size); + + // Zero the memory. + R_TRY(read_func( + entries[entry_idx].virtual_size, + [&](void* dst, size_t dst_size) -> Result { + // Check that the size is valid. + ASSERT(dst_size == entries[entry_idx].virtual_size); + + // The data is zeroes, so zero the buffer. + std::memset(dst, 0, entries[entry_idx].virtual_size); + R_SUCCEED(); + })); + + break; + } + default: { + // Check that we can remain within bounds. + ASSERT(buffer_offset + entries[entry_idx].physical_size <= + cur_read_size); + + // Get the decompressor. + const auto decompressor = + this->GetDecompressor(compression_type); + R_UNLESS(decompressor != nullptr, + ResultUnexpectedInCompressedStorageB); + + // Decompress the data. + R_TRY(read_func(entries[entry_idx].virtual_size, + [&](void* dst, size_t dst_size) -> Result { + // Check that the size is valid. + ASSERT(dst_size == + entries[entry_idx].virtual_size); + + // Perform the decompression. + R_RETURN(decompressor( + dst, entries[entry_idx].virtual_size, + buffer + buffer_offset, + entries[entry_idx].physical_size)); + })); + + break; + } + } + } + + // Check that we processed the correct amount of data. + ASSERT(buffer_offset == cur_read_size); + } else { + // Account for the gap from the previous entry. + required_access_physical_offset += entries[entry_idx].gap_from_prev; + required_access_physical_size -= entries[entry_idx].gap_from_prev; + + // We don't need the buffer (as the data is uncompressed), so just + // execute the read. + R_TRY( + read_func(cur_read_size, [&](void* dst, size_t dst_size) -> Result { + // Check that the size is valid. + ASSERT(dst_size == cur_read_size); + + // Perform the read. + m_data_storage->Read(reinterpret_cast<u8*>(dst), cur_read_size, + required_access_physical_offset); + + R_SUCCEED(); + })); + } + + // Advance on. + required_access_physical_offset += cur_read_size; + required_access_physical_size -= cur_read_size; + } + + // Verify that we have nothing remaining to read. + ASSERT(required_access_physical_size == 0); + + R_SUCCEED(); + } else { + // We don't need a buffer, so just execute the read. + R_TRY(read_func(total_required_size, [&](void* dst, size_t dst_size) -> Result { + // Check that the size is valid. + ASSERT(dst_size == total_required_size); + + // Perform the read. + m_data_storage->Read(reinterpret_cast<u8*>(dst), total_required_size, + required_access_physical_offset); + + R_SUCCEED(); + })); + } + + R_SUCCEED(); + }; + + R_TRY(this->OperatePerEntry( + offset, size, + [&](bool* out_continuous, const Entry& entry, s64 virtual_data_size, + s64 data_offset, s64 read_size) -> Result { + // Determine the physical extents. + s64 physical_offset, physical_size; + if (CompressionTypeUtility::IsRandomAccessible(entry.compression_type)) { + physical_offset = entry.phys_offset + data_offset; + physical_size = read_size; + } else { + physical_offset = entry.phys_offset; + physical_size = entry.GetPhysicalSize(); + } + + // If we have a pending data storage operation, perform it if we have to. + const s64 required_access_physical_end = + required_access_physical_offset + required_access_physical_size; + if (required_access_physical_size > 0) { + const bool required_by_gap = + !(required_access_physical_end <= physical_offset && + physical_offset <= Common::AlignUp(required_access_physical_end, + CompressionBlockAlignment)); + const bool required_by_continuous_size = + ((physical_size + physical_offset) - required_access_physical_end) + + required_access_physical_size > + static_cast<s64>(m_continuous_reading_size_max); + const bool required_by_entry_count = entry_count == EntriesCountMax; + if (required_by_gap || required_by_continuous_size || + required_by_entry_count) { + // Check that our planned access is sane. + ASSERT(!will_allocate_pooled_buffer || + required_access_physical_size <= + static_cast<s64>(m_continuous_reading_size_max)); + + // Perform the required read. + const Result rc = PerformRequiredRead(); + if (R_FAILED(rc)) { + R_THROW(rc); + } + + // Reset our requirements. + prev_entry.virt_offset = -1; + required_access_physical_size = 0; + entry_count = 0; + will_allocate_pooled_buffer = false; + } + } + + // Sanity check that we're within bounds on entries. + ASSERT(entry_count < EntriesCountMax); + + // Determine if a buffer allocation is needed. + if (entry.compression_type != CompressionType::None || + (prev_entry.virt_offset >= 0 && + entry.virt_offset - prev_entry.virt_offset != + entry.phys_offset - prev_entry.phys_offset)) { + will_allocate_pooled_buffer = true; + } + + // If we need to access the data storage, update our required access parameters. + if (CompressionTypeUtility::IsDataStorageAccessRequired( + entry.compression_type)) { + // If the data is compressed, ensure the access is sane. + if (entry.compression_type != CompressionType::None) { + R_UNLESS(data_offset == 0, ResultInvalidOffset); + R_UNLESS(virtual_data_size == read_size, ResultInvalidSize); + R_UNLESS(entry.GetPhysicalSize() <= static_cast<s64>(m_block_size_max), + ResultUnexpectedInCompressedStorageD); + } + + // Update the required access parameters. + s64 gap_from_prev; + if (required_access_physical_size > 0) { + gap_from_prev = physical_offset - required_access_physical_end; + } else { + gap_from_prev = 0; + required_access_physical_offset = physical_offset; + } + required_access_physical_size += physical_size + gap_from_prev; + + // Create an entry to access the data storage. + entries[entry_count++] = { + .compression_type = entry.compression_type, + .gap_from_prev = static_cast<u32>(gap_from_prev), + .physical_size = static_cast<u32>(physical_size), + .virtual_size = static_cast<u32>(read_size), + }; + } else { + // Verify that we're allowed to be operating on the non-data-storage-access + // type. + R_UNLESS(entry.compression_type == CompressionType::Zeros, + ResultUnexpectedInCompressedStorageB); + + // If we have entries, create a fake entry for the zero region. + if (entry_count != 0) { + // We need to have a physical size. + R_UNLESS(entry.GetPhysicalSize() != 0, + ResultUnexpectedInCompressedStorageD); + + // Create a fake entry. + entries[entry_count++] = { + .compression_type = CompressionType::Zeros, + .gap_from_prev = 0, + .physical_size = 0, + .virtual_size = static_cast<u32>(read_size), + }; + } else { + // We have no entries, so we can just perform the read. + const Result rc = + read_func(static_cast<size_t>(read_size), + [&](void* dst, size_t dst_size) -> Result { + // Check the space we should zero is correct. + ASSERT(dst_size == static_cast<size_t>(read_size)); + + // Zero the memory. + std::memset(dst, 0, read_size); + R_SUCCEED(); + }); + if (R_FAILED(rc)) { + R_THROW(rc); + } + } + } + + // Set the previous entry. + prev_entry = entry; + + // We're continuous. + *out_continuous = true; + R_SUCCEED(); + })); + + // If we still have a pending access, perform it. + if (required_access_physical_size != 0) { + R_TRY(PerformRequiredRead()); + } + + R_SUCCEED(); + } + + private: + DecompressorFunction GetDecompressor(CompressionType type) const { + // Check that we can get a decompressor for the type. + if (CompressionTypeUtility::IsUnknownType(type)) { + return nullptr; + } + + // Get the decompressor. + return m_get_decompressor_function(type); + } + + bool IsInitialized() const { + return m_table.IsInitialized(); + } + + private: + size_t m_block_size_max; + size_t m_continuous_reading_size_max; + BucketTree m_table; + VirtualFile m_data_storage; + GetDecompressorFunction m_get_decompressor_function; + }; + + class CacheManager { + YUZU_NON_COPYABLE(CacheManager); + YUZU_NON_MOVEABLE(CacheManager); + + private: + struct AccessRange { + s64 virtual_offset; + s64 virtual_size; + u32 physical_size; + bool is_block_alignment_required; + + s64 GetEndVirtualOffset() const { + return this->virtual_offset + this->virtual_size; + } + }; + static_assert(std::is_trivial_v<AccessRange>); + + public: + CacheManager() = default; + + public: + Result Initialize(s64 storage_size, size_t cache_size_0, size_t cache_size_1, + size_t max_cache_entries) { + // Set our fields. + m_storage_size = storage_size; + + R_SUCCEED(); + } + + Result Read(CompressedStorageCore& core, s64 offset, void* buffer, size_t size) { + // If we have nothing to read, succeed. + R_SUCCEED_IF(size == 0); + + // Check that we have a buffer to read into. + R_UNLESS(buffer != nullptr, ResultNullptrArgument); + + // Check that the read is in bounds. + R_UNLESS(offset <= m_storage_size, ResultInvalidOffset); + + // Determine how much we can read. + const size_t read_size = std::min<size_t>(size, m_storage_size - offset); + + // Create head/tail ranges. + AccessRange head_range = {}; + AccessRange tail_range = {}; + bool is_tail_set = false; + + // Operate to determine the head range. + R_TRY(core.OperatePerEntry( + offset, 1, + [&](bool* out_continuous, const Entry& entry, s64 virtual_data_size, + s64 data_offset, s64 data_read_size) -> Result { + // Set the head range. + head_range = { + .virtual_offset = entry.virt_offset, + .virtual_size = virtual_data_size, + .physical_size = static_cast<u32>(entry.phys_size), + .is_block_alignment_required = + CompressionTypeUtility::IsBlockAlignmentRequired( + entry.compression_type), + }; + + // If required, set the tail range. + if (static_cast<s64>(offset + read_size) <= + entry.virt_offset + virtual_data_size) { + tail_range = { + .virtual_offset = entry.virt_offset, + .virtual_size = virtual_data_size, + .physical_size = static_cast<u32>(entry.phys_size), + .is_block_alignment_required = + CompressionTypeUtility::IsBlockAlignmentRequired( + entry.compression_type), + }; + is_tail_set = true; + } + + // We only want to determine the head range, so we're not continuous. + *out_continuous = false; + R_SUCCEED(); + })); + + // If necessary, determine the tail range. + if (!is_tail_set) { + R_TRY(core.OperatePerEntry( + offset + read_size - 1, 1, + [&](bool* out_continuous, const Entry& entry, s64 virtual_data_size, + s64 data_offset, s64 data_read_size) -> Result { + // Set the tail range. + tail_range = { + .virtual_offset = entry.virt_offset, + .virtual_size = virtual_data_size, + .physical_size = static_cast<u32>(entry.phys_size), + .is_block_alignment_required = + CompressionTypeUtility::IsBlockAlignmentRequired( + entry.compression_type), + }; + + // We only want to determine the tail range, so we're not continuous. + *out_continuous = false; + R_SUCCEED(); + })); + } + + // Begin performing the accesses. + s64 cur_offset = offset; + size_t cur_size = read_size; + char* cur_dst = static_cast<char*>(buffer); + + // Determine our alignment. + const bool head_unaligned = head_range.is_block_alignment_required && + (cur_offset != head_range.virtual_offset || + static_cast<s64>(cur_size) < head_range.virtual_size); + const bool tail_unaligned = [&]() -> bool { + if (tail_range.is_block_alignment_required) { + if (static_cast<s64>(cur_size + cur_offset) == + tail_range.GetEndVirtualOffset()) { + return false; + } else if (!head_unaligned) { + return true; + } else { + return head_range.GetEndVirtualOffset() < + static_cast<s64>(cur_size + cur_offset); + } + } else { + return false; + } + }(); + + // Determine start/end offsets. + const s64 start_offset = + head_range.is_block_alignment_required ? head_range.virtual_offset : cur_offset; + const s64 end_offset = tail_range.is_block_alignment_required + ? tail_range.GetEndVirtualOffset() + : cur_offset + cur_size; + + // Perform the read. + bool is_burst_reading = false; + R_TRY(core.Read( + start_offset, end_offset - start_offset, + [&](size_t size_buffer_required, + const CompressedStorageCore::ReadImplFunction& read_impl) -> Result { + // Determine whether we're burst reading. + const AccessRange* unaligned_range = nullptr; + if (!is_burst_reading) { + // Check whether we're using head, tail, or none as unaligned. + if (head_unaligned && head_range.virtual_offset <= cur_offset && + cur_offset < head_range.GetEndVirtualOffset()) { + unaligned_range = std::addressof(head_range); + } else if (tail_unaligned && tail_range.virtual_offset <= cur_offset && + cur_offset < tail_range.GetEndVirtualOffset()) { + unaligned_range = std::addressof(tail_range); + } else { + is_burst_reading = true; + } + } + ASSERT((is_burst_reading ^ (unaligned_range != nullptr))); + + // Perform reading by burst, or not. + if (is_burst_reading) { + // Check that the access is valid for burst reading. + ASSERT(size_buffer_required <= cur_size); + + // Perform the read. + Result rc = read_impl(cur_dst, size_buffer_required); + if (R_FAILED(rc)) { + R_THROW(rc); + } + + // Advance. + cur_dst += size_buffer_required; + cur_offset += size_buffer_required; + cur_size -= size_buffer_required; + + // Determine whether we're going to continue burst reading. + const s64 offset_aligned = + tail_unaligned ? tail_range.virtual_offset : end_offset; + ASSERT(cur_offset <= offset_aligned); + + if (offset_aligned <= cur_offset) { + is_burst_reading = false; + } + } else { + // We're not burst reading, so we have some unaligned range. + ASSERT(unaligned_range != nullptr); + + // Check that the size is correct. + ASSERT(size_buffer_required == + static_cast<size_t>(unaligned_range->virtual_size)); + + // Get a pooled buffer for our read. + PooledBuffer pooled_buffer; + pooled_buffer.Allocate(size_buffer_required, size_buffer_required); + + // Perform read. + Result rc = read_impl(pooled_buffer.GetBuffer(), size_buffer_required); + if (R_FAILED(rc)) { + R_THROW(rc); + } + + // Copy the data we read to the destination. + const size_t skip_size = cur_offset - unaligned_range->virtual_offset; + const size_t copy_size = std::min<size_t>( + cur_size, unaligned_range->GetEndVirtualOffset() - cur_offset); + + std::memcpy(cur_dst, pooled_buffer.GetBuffer() + skip_size, copy_size); + + // Advance. + cur_dst += copy_size; + cur_offset += copy_size; + cur_size -= copy_size; + } + + R_SUCCEED(); + })); + + R_SUCCEED(); + } + + private: + s64 m_storage_size = 0; + }; + +public: + CompressedStorage() = default; + virtual ~CompressedStorage() { + this->Finalize(); + } + + Result Initialize(VirtualFile data_storage, VirtualFile node_storage, VirtualFile entry_storage, + s32 bktr_entry_count, size_t block_size_max, + size_t continuous_reading_size_max, GetDecompressorFunction get_decompressor, + size_t cache_size_0, size_t cache_size_1, s32 max_cache_entries) { + // Initialize our core. + R_TRY(m_core.Initialize(data_storage, node_storage, entry_storage, bktr_entry_count, + block_size_max, continuous_reading_size_max, get_decompressor)); + + // Get our core size. + s64 core_size = 0; + R_TRY(m_core.GetSize(std::addressof(core_size))); + + // Initialize our cache manager. + R_TRY(m_cache_manager.Initialize(core_size, cache_size_0, cache_size_1, max_cache_entries)); + + R_SUCCEED(); + } + + void Finalize() { + m_core.Finalize(); + } + + VirtualFile GetDataStorage() { + return m_core.GetDataStorage(); + } + + Result GetDataStorageSize(s64* out) { + R_RETURN(m_core.GetDataStorageSize(out)); + } + + Result GetEntryList(Entry* out_entries, s32* out_read_count, s32 max_entry_count, s64 offset, + s64 size) { + R_RETURN(m_core.GetEntryList(out_entries, out_read_count, max_entry_count, offset, size)); + } + + BucketTree& GetEntryTable() { + return m_core.GetEntryTable(); + } + +public: + virtual size_t GetSize() const override { + s64 ret{}; + m_core.GetSize(&ret); + return ret; + } + + virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { + if (R_SUCCEEDED(m_cache_manager.Read(m_core, offset, buffer, size))) { + return size; + } else { + return 0; + } + } + +private: + mutable CompressedStorageCore m_core; + mutable CacheManager m_cache_manager; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_compression_common.h b/src/core/file_sys/fssystem/fssystem_compression_common.h new file mode 100644 index 000000000..266e0a7e5 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_compression_common.h @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/hle/result.h" + +namespace FileSys { + +enum class CompressionType : u8 { + None = 0, + Zeros = 1, + Two = 2, + Lz4 = 3, + Unknown = 4, +}; + +using DecompressorFunction = Result (*)(void*, size_t, const void*, size_t); +using GetDecompressorFunction = DecompressorFunction (*)(CompressionType); + +constexpr s64 CompressionBlockAlignment = 0x10; + +namespace CompressionTypeUtility { + +constexpr bool IsBlockAlignmentRequired(CompressionType type) { + return type != CompressionType::None && type != CompressionType::Zeros; +} + +constexpr bool IsDataStorageAccessRequired(CompressionType type) { + return type != CompressionType::Zeros; +} + +constexpr bool IsRandomAccessible(CompressionType type) { + return type == CompressionType::None; +} + +constexpr bool IsUnknownType(CompressionType type) { + return type >= CompressionType::Unknown; +} + +} // namespace CompressionTypeUtility + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_compression_configuration.cpp b/src/core/file_sys/fssystem/fssystem_compression_configuration.cpp new file mode 100644 index 000000000..ef552cefe --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_compression_configuration.cpp @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/lz4_compression.h" +#include "core/file_sys/fssystem/fssystem_compression_configuration.h" + +namespace FileSys { + +namespace { + +Result DecompressLz4(void* dst, size_t dst_size, const void* src, size_t src_size) { + auto result = Common::Compression::DecompressDataLZ4(dst, dst_size, src, src_size); + R_UNLESS(static_cast<size_t>(result) == dst_size, ResultUnexpectedInCompressedStorageC); + R_SUCCEED(); +} + +constexpr DecompressorFunction GetNcaDecompressorFunction(CompressionType type) { + switch (type) { + case CompressionType::Lz4: + return DecompressLz4; + default: + return nullptr; + } +} + +} // namespace + +const NcaCompressionConfiguration& GetNcaCompressionConfiguration() { + static const NcaCompressionConfiguration configuration = { + .get_decompressor = GetNcaDecompressorFunction, + }; + + return configuration; +} + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_compression_configuration.h b/src/core/file_sys/fssystem/fssystem_compression_configuration.h new file mode 100644 index 000000000..ec9b48e9a --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_compression_configuration.h @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h" + +namespace FileSys { + +const NcaCompressionConfiguration& GetNcaCompressionConfiguration(); + +} diff --git a/src/core/file_sys/fssystem/fssystem_crypto_configuration.cpp b/src/core/file_sys/fssystem/fssystem_crypto_configuration.cpp new file mode 100644 index 000000000..a4f0cde28 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_crypto_configuration.cpp @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/crypto/aes_util.h" +#include "core/crypto/key_manager.h" +#include "core/file_sys/fssystem/fssystem_crypto_configuration.h" + +namespace FileSys { + +namespace { + +void GenerateKey(void* dst_key, size_t dst_key_size, const void* src_key, size_t src_key_size, + s32 key_type) { + if (key_type == static_cast<s32>(KeyType::ZeroKey)) { + std::memset(dst_key, 0, dst_key_size); + return; + } + + if (key_type == static_cast<s32>(KeyType::InvalidKey) || + key_type < static_cast<s32>(KeyType::ZeroKey) || + key_type >= static_cast<s32>(KeyType::NcaExternalKey)) { + std::memset(dst_key, 0xFF, dst_key_size); + return; + } + + const auto& instance = Core::Crypto::KeyManager::Instance(); + + if (key_type == static_cast<s32>(KeyType::NcaHeaderKey1) || + key_type == static_cast<s32>(KeyType::NcaHeaderKey2)) { + const s32 key_index = static_cast<s32>(KeyType::NcaHeaderKey2) == key_type; + const auto key = instance.GetKey(Core::Crypto::S256KeyType::Header); + std::memcpy(dst_key, key.data() + key_index * 0x10, std::min(dst_key_size, key.size() / 2)); + return; + } + + const s32 key_generation = + std::max(key_type / NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount, 1) - 1; + const s32 key_index = key_type % NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount; + + Core::Crypto::AESCipher<Core::Crypto::Key128> cipher( + instance.GetKey(Core::Crypto::S128KeyType::KeyArea, key_generation, key_index), + Core::Crypto::Mode::ECB); + cipher.Transcode(reinterpret_cast<const u8*>(src_key), src_key_size, + reinterpret_cast<u8*>(dst_key), Core::Crypto::Op::Decrypt); +} + +} // namespace + +const NcaCryptoConfiguration& GetCryptoConfiguration() { + static const NcaCryptoConfiguration configuration = { + .header_1_sign_key_moduli{}, + .header_1_sign_key_public_exponent{}, + .key_area_encryption_key_source{}, + .header_encryption_key_source{}, + .header_encrypted_encryption_keys{}, + .generate_key = GenerateKey, + .verify_sign1{}, + .is_plaintext_header_available{}, + .is_available_sw_key{}, + }; + + return configuration; +} + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_crypto_configuration.h b/src/core/file_sys/fssystem/fssystem_crypto_configuration.h new file mode 100644 index 000000000..7fd9c5a8d --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_crypto_configuration.h @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h" + +namespace FileSys { + +const NcaCryptoConfiguration& GetCryptoConfiguration(); + +} diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp new file mode 100644 index 000000000..4a75b5308 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.cpp @@ -0,0 +1,127 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h" +#include "core/file_sys/vfs_offset.h" + +namespace FileSys { + +HierarchicalIntegrityVerificationStorage::HierarchicalIntegrityVerificationStorage() + : m_data_size(-1) { + for (size_t i = 0; i < MaxLayers - 1; i++) { + m_verify_storages[i] = std::make_shared<IntegrityVerificationStorage>(); + } +} + +Result HierarchicalIntegrityVerificationStorage::Initialize( + const HierarchicalIntegrityVerificationInformation& info, + HierarchicalStorageInformation storage, int max_data_cache_entries, int max_hash_cache_entries, + s8 buffer_level) { + // Validate preconditions. + ASSERT(IntegrityMinLayerCount <= info.max_layers && info.max_layers <= IntegrityMaxLayerCount); + + // Set member variables. + m_max_layers = info.max_layers; + + // Initialize the top level verification storage. + m_verify_storages[0]->Initialize(storage[HierarchicalStorageInformation::MasterStorage], + storage[HierarchicalStorageInformation::Layer1Storage], + static_cast<s64>(1) << info.info[0].block_order, HashSize, + false); + + // Ensure we don't leak state if further initialization goes wrong. + ON_RESULT_FAILURE { + m_verify_storages[0]->Finalize(); + m_data_size = -1; + }; + + // Initialize the top level buffer storage. + m_buffer_storages[0] = m_verify_storages[0]; + R_UNLESS(m_buffer_storages[0] != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Prepare to initialize the level storages. + s32 level = 0; + + // Ensure we don't leak state if further initialization goes wrong. + ON_RESULT_FAILURE_2 { + m_verify_storages[level + 1]->Finalize(); + for (; level > 0; --level) { + m_buffer_storages[level].reset(); + m_verify_storages[level]->Finalize(); + } + }; + + // Initialize the level storages. + for (; level < m_max_layers - 3; ++level) { + // Initialize the verification storage. + auto buffer_storage = + std::make_shared<OffsetVfsFile>(m_buffer_storages[level], info.info[level].size, 0); + m_verify_storages[level + 1]->Initialize( + std::move(buffer_storage), storage[level + 2], + static_cast<s64>(1) << info.info[level + 1].block_order, + static_cast<s64>(1) << info.info[level].block_order, false); + + // Initialize the buffer storage. + m_buffer_storages[level + 1] = m_verify_storages[level + 1]; + R_UNLESS(m_buffer_storages[level + 1] != nullptr, + ResultAllocationMemoryFailedAllocateShared); + } + + // Initialize the final level storage. + { + // Initialize the verification storage. + auto buffer_storage = + std::make_shared<OffsetVfsFile>(m_buffer_storages[level], info.info[level].size, 0); + m_verify_storages[level + 1]->Initialize( + std::move(buffer_storage), storage[level + 2], + static_cast<s64>(1) << info.info[level + 1].block_order, + static_cast<s64>(1) << info.info[level].block_order, true); + + // Initialize the buffer storage. + m_buffer_storages[level + 1] = m_verify_storages[level + 1]; + R_UNLESS(m_buffer_storages[level + 1] != nullptr, + ResultAllocationMemoryFailedAllocateShared); + } + + // Set the data size. + m_data_size = info.info[level + 1].size; + + // We succeeded. + R_SUCCEED(); +} + +void HierarchicalIntegrityVerificationStorage::Finalize() { + if (m_data_size >= 0) { + m_data_size = 0; + + for (s32 level = m_max_layers - 2; level >= 0; --level) { + m_buffer_storages[level].reset(); + m_verify_storages[level]->Finalize(); + } + + m_data_size = -1; + } +} + +size_t HierarchicalIntegrityVerificationStorage::Read(u8* buffer, size_t size, + size_t offset) const { + // Validate preconditions. + ASSERT(m_data_size >= 0); + + // Succeed if zero-size. + if (size == 0) { + return size; + } + + // Validate arguments. + ASSERT(buffer != nullptr); + + // Read the data. + return m_buffer_storages[m_max_layers - 2]->Read(buffer, size, offset); +} + +size_t HierarchicalIntegrityVerificationStorage::GetSize() const { + return m_data_size; +} + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h new file mode 100644 index 000000000..5cf697efe --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h @@ -0,0 +1,164 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/alignment.h" +#include "core/file_sys/fssystem/fs_i_storage.h" +#include "core/file_sys/fssystem/fs_types.h" +#include "core/file_sys/fssystem/fssystem_alignment_matching_storage.h" +#include "core/file_sys/fssystem/fssystem_integrity_verification_storage.h" +#include "core/file_sys/vfs_offset.h" + +namespace FileSys { + +struct HierarchicalIntegrityVerificationLevelInformation { + Int64 offset; + Int64 size; + s32 block_order; + std::array<u8, 4> reserved; +}; +static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationLevelInformation>); +static_assert(sizeof(HierarchicalIntegrityVerificationLevelInformation) == 0x18); +static_assert(alignof(HierarchicalIntegrityVerificationLevelInformation) == 0x4); + +struct HierarchicalIntegrityVerificationInformation { + u32 max_layers; + std::array<HierarchicalIntegrityVerificationLevelInformation, IntegrityMaxLayerCount - 1> info; + HashSalt seed; + + s64 GetLayeredHashSize() const { + return this->info[this->max_layers - 2].offset; + } + + s64 GetDataOffset() const { + return this->info[this->max_layers - 2].offset; + } + + s64 GetDataSize() const { + return this->info[this->max_layers - 2].size; + } +}; +static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationInformation>); + +struct HierarchicalIntegrityVerificationMetaInformation { + u32 magic; + u32 version; + u32 master_hash_size; + HierarchicalIntegrityVerificationInformation level_hash_info; +}; +static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationMetaInformation>); + +struct HierarchicalIntegrityVerificationSizeSet { + s64 control_size; + s64 master_hash_size; + std::array<s64, IntegrityMaxLayerCount - 2> layered_hash_sizes; +}; +static_assert(std::is_trivial_v<HierarchicalIntegrityVerificationSizeSet>); + +class HierarchicalIntegrityVerificationStorage : public IReadOnlyStorage { + YUZU_NON_COPYABLE(HierarchicalIntegrityVerificationStorage); + YUZU_NON_MOVEABLE(HierarchicalIntegrityVerificationStorage); + +public: + using GenerateRandomFunction = void (*)(void* dst, size_t size); + + class HierarchicalStorageInformation { + public: + enum { + MasterStorage = 0, + Layer1Storage = 1, + Layer2Storage = 2, + Layer3Storage = 3, + Layer4Storage = 4, + Layer5Storage = 5, + DataStorage = 6, + }; + + private: + std::array<VirtualFile, DataStorage + 1> m_storages; + + public: + void SetMasterHashStorage(VirtualFile s) { + m_storages[MasterStorage] = s; + } + void SetLayer1HashStorage(VirtualFile s) { + m_storages[Layer1Storage] = s; + } + void SetLayer2HashStorage(VirtualFile s) { + m_storages[Layer2Storage] = s; + } + void SetLayer3HashStorage(VirtualFile s) { + m_storages[Layer3Storage] = s; + } + void SetLayer4HashStorage(VirtualFile s) { + m_storages[Layer4Storage] = s; + } + void SetLayer5HashStorage(VirtualFile s) { + m_storages[Layer5Storage] = s; + } + void SetDataStorage(VirtualFile s) { + m_storages[DataStorage] = s; + } + + VirtualFile& operator[](s32 index) { + ASSERT(MasterStorage <= index && index <= DataStorage); + return m_storages[index]; + } + }; + +public: + HierarchicalIntegrityVerificationStorage(); + virtual ~HierarchicalIntegrityVerificationStorage() override { + this->Finalize(); + } + + Result Initialize(const HierarchicalIntegrityVerificationInformation& info, + HierarchicalStorageInformation storage, int max_data_cache_entries, + int max_hash_cache_entries, s8 buffer_level); + void Finalize(); + + virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; + virtual size_t GetSize() const override; + + bool IsInitialized() const { + return m_data_size >= 0; + } + + s64 GetL1HashVerificationBlockSize() const { + return m_verify_storages[m_max_layers - 2]->GetBlockSize(); + } + + VirtualFile GetL1HashStorage() { + return std::make_shared<OffsetVfsFile>( + m_buffer_storages[m_max_layers - 3], + Common::DivideUp(m_data_size, this->GetL1HashVerificationBlockSize()), 0); + } + +public: + static constexpr s8 GetDefaultDataCacheBufferLevel(u32 max_layers) { + return static_cast<s8>(16 + max_layers - 2); + } + +protected: + static constexpr s64 HashSize = 256 / 8; + static constexpr size_t MaxLayers = IntegrityMaxLayerCount; + +private: + static GenerateRandomFunction s_generate_random; + + static void SetGenerateRandomFunction(GenerateRandomFunction func) { + s_generate_random = func; + } + +private: + friend struct HierarchicalIntegrityVerificationMetaInformation; + +private: + std::array<std::shared_ptr<IntegrityVerificationStorage>, MaxLayers - 1> m_verify_storages; + std::array<VirtualFile, MaxLayers - 1> m_buffer_storages; + s64 m_data_size; + s32 m_max_layers; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp b/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp new file mode 100644 index 000000000..caea0b8f8 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.cpp @@ -0,0 +1,80 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/alignment.h" +#include "common/scope_exit.h" +#include "core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h" + +namespace FileSys { + +namespace { + +s32 Log2(s32 value) { + ASSERT(value > 0); + ASSERT(Common::IsPowerOfTwo(value)); + + s32 log = 0; + while ((value >>= 1) > 0) { + ++log; + } + return log; +} + +} // namespace + +Result HierarchicalSha256Storage::Initialize(VirtualFile* base_storages, s32 layer_count, + size_t htbs, void* hash_buf, size_t hash_buf_size) { + // Validate preconditions. + ASSERT(layer_count == LayerCount); + ASSERT(Common::IsPowerOfTwo(htbs)); + ASSERT(hash_buf != nullptr); + + // Set size tracking members. + m_hash_target_block_size = static_cast<s32>(htbs); + m_log_size_ratio = Log2(m_hash_target_block_size / HashSize); + + // Get the base storage size. + m_base_storage_size = base_storages[2]->GetSize(); + { + auto size_guard = SCOPE_GUARD({ m_base_storage_size = 0; }); + R_UNLESS(m_base_storage_size <= static_cast<s64>(HashSize) + << m_log_size_ratio << m_log_size_ratio, + ResultHierarchicalSha256BaseStorageTooLarge); + size_guard.Cancel(); + } + + // Set hash buffer tracking members. + m_base_storage = base_storages[2]; + m_hash_buffer = static_cast<char*>(hash_buf); + m_hash_buffer_size = hash_buf_size; + + // Read the master hash. + std::array<u8, HashSize> master_hash{}; + base_storages[0]->ReadObject(std::addressof(master_hash)); + + // Read and validate the data being hashed. + s64 hash_storage_size = base_storages[1]->GetSize(); + ASSERT(Common::IsAligned(hash_storage_size, HashSize)); + ASSERT(hash_storage_size <= m_hash_target_block_size); + ASSERT(hash_storage_size <= static_cast<s64>(m_hash_buffer_size)); + + base_storages[1]->Read(reinterpret_cast<u8*>(m_hash_buffer), + static_cast<size_t>(hash_storage_size), 0); + + R_SUCCEED(); +} + +size_t HierarchicalSha256Storage::Read(u8* buffer, size_t size, size_t offset) const { + // Succeed if zero-size. + if (size == 0) { + return size; + } + + // Validate that we have a buffer to read into. + ASSERT(buffer != nullptr); + + // Read the data. + return m_base_storage->Read(buffer, size, offset); +} + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h b/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h new file mode 100644 index 000000000..18df400af --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <mutex> + +#include "core/file_sys/errors.h" +#include "core/file_sys/fssystem/fs_i_storage.h" +#include "core/file_sys/vfs.h" + +namespace FileSys { + +class HierarchicalSha256Storage : public IReadOnlyStorage { + YUZU_NON_COPYABLE(HierarchicalSha256Storage); + YUZU_NON_MOVEABLE(HierarchicalSha256Storage); + +public: + static constexpr s32 LayerCount = 3; + static constexpr size_t HashSize = 256 / 8; + +public: + HierarchicalSha256Storage() : m_mutex() {} + + Result Initialize(VirtualFile* base_storages, s32 layer_count, size_t htbs, void* hash_buf, + size_t hash_buf_size); + + virtual size_t GetSize() const override { + return m_base_storage->GetSize(); + } + + virtual size_t Read(u8* buffer, size_t length, size_t offset) const override; + +private: + VirtualFile m_base_storage; + s64 m_base_storage_size; + char* m_hash_buffer; + size_t m_hash_buffer_size; + s32 m_hash_target_block_size; + s32 m_log_size_ratio; + std::mutex m_mutex; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_indirect_storage.cpp b/src/core/file_sys/fssystem/fssystem_indirect_storage.cpp new file mode 100644 index 000000000..7544e70b2 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_indirect_storage.cpp @@ -0,0 +1,119 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/file_sys/errors.h" +#include "core/file_sys/fssystem/fssystem_indirect_storage.h" + +namespace FileSys { + +Result IndirectStorage::Initialize(VirtualFile table_storage) { + // Read and verify the bucket tree header. + BucketTree::Header header; + table_storage->ReadObject(std::addressof(header)); + R_TRY(header.Verify()); + + // Determine extents. + const auto node_storage_size = QueryNodeStorageSize(header.entry_count); + const auto entry_storage_size = QueryEntryStorageSize(header.entry_count); + const auto node_storage_offset = QueryHeaderStorageSize(); + const auto entry_storage_offset = node_storage_offset + node_storage_size; + + // Initialize. + R_RETURN(this->Initialize( + std::make_shared<OffsetVfsFile>(table_storage, node_storage_size, node_storage_offset), + std::make_shared<OffsetVfsFile>(table_storage, entry_storage_size, entry_storage_offset), + header.entry_count)); +} + +void IndirectStorage::Finalize() { + if (this->IsInitialized()) { + m_table.Finalize(); + for (auto i = 0; i < StorageCount; i++) { + m_data_storage[i] = VirtualFile(); + } + } +} + +Result IndirectStorage::GetEntryList(Entry* out_entries, s32* out_entry_count, s32 entry_count, + s64 offset, s64 size) { + // Validate pre-conditions. + ASSERT(offset >= 0); + ASSERT(size >= 0); + ASSERT(this->IsInitialized()); + + // Clear the out count. + R_UNLESS(out_entry_count != nullptr, ResultNullptrArgument); + *out_entry_count = 0; + + // Succeed if there's no range. + R_SUCCEED_IF(size == 0); + + // If we have an output array, we need it to be non-null. + R_UNLESS(out_entries != nullptr || entry_count == 0, ResultNullptrArgument); + + // Check that our range is valid. + BucketTree::Offsets table_offsets; + R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); + + R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange); + + // Find the offset in our tree. + BucketTree::Visitor visitor; + R_TRY(m_table.Find(std::addressof(visitor), offset)); + { + const auto entry_offset = visitor.Get<Entry>()->GetVirtualOffset(); + R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset), + ResultInvalidIndirectEntryOffset); + } + + // Prepare to loop over entries. + const auto end_offset = offset + static_cast<s64>(size); + s32 count = 0; + + auto cur_entry = *visitor.Get<Entry>(); + while (cur_entry.GetVirtualOffset() < end_offset) { + // Try to write the entry to the out list. + if (entry_count != 0) { + if (count >= entry_count) { + break; + } + std::memcpy(out_entries + count, std::addressof(cur_entry), sizeof(Entry)); + } + + count++; + + // Advance. + if (visitor.CanMoveNext()) { + R_TRY(visitor.MoveNext()); + cur_entry = *visitor.Get<Entry>(); + } else { + break; + } + } + + // Write the output count. + *out_entry_count = count; + R_SUCCEED(); +} + +size_t IndirectStorage::Read(u8* buffer, size_t size, size_t offset) const { + // Validate pre-conditions. + ASSERT(this->IsInitialized()); + ASSERT(buffer != nullptr); + + // Succeed if there's nothing to read. + if (size == 0) { + return 0; + } + + const_cast<IndirectStorage*>(this)->OperatePerEntry<true, true>( + offset, size, + [=](VirtualFile storage, s64 data_offset, s64 cur_offset, s64 cur_size) -> Result { + storage->Read(reinterpret_cast<u8*>(buffer) + (cur_offset - offset), + static_cast<size_t>(cur_size), data_offset); + R_SUCCEED(); + }); + + return size; +} +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_indirect_storage.h b/src/core/file_sys/fssystem/fssystem_indirect_storage.h new file mode 100644 index 000000000..7854335bf --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_indirect_storage.h @@ -0,0 +1,294 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/file_sys/errors.h" +#include "core/file_sys/fssystem/fs_i_storage.h" +#include "core/file_sys/fssystem/fssystem_bucket_tree.h" +#include "core/file_sys/fssystem/fssystem_bucket_tree_template_impl.h" +#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs_offset.h" + +namespace FileSys { + +class IndirectStorage : public IReadOnlyStorage { + YUZU_NON_COPYABLE(IndirectStorage); + YUZU_NON_MOVEABLE(IndirectStorage); + +public: + static constexpr s32 StorageCount = 2; + static constexpr size_t NodeSize = 16_KiB; + + struct Entry { + std::array<u8, sizeof(s64)> virt_offset; + std::array<u8, sizeof(s64)> phys_offset; + s32 storage_index; + + void SetVirtualOffset(const s64& ofs) { + std::memcpy(this->virt_offset.data(), std::addressof(ofs), sizeof(s64)); + } + + s64 GetVirtualOffset() const { + s64 offset; + std::memcpy(std::addressof(offset), this->virt_offset.data(), sizeof(s64)); + return offset; + } + + void SetPhysicalOffset(const s64& ofs) { + std::memcpy(this->phys_offset.data(), std::addressof(ofs), sizeof(s64)); + } + + s64 GetPhysicalOffset() const { + s64 offset; + std::memcpy(std::addressof(offset), this->phys_offset.data(), sizeof(s64)); + return offset; + } + }; + static_assert(std::is_trivial_v<Entry>); + static_assert(sizeof(Entry) == 0x14); + + struct EntryData { + s64 virt_offset; + s64 phys_offset; + s32 storage_index; + + void Set(const Entry& entry) { + this->virt_offset = entry.GetVirtualOffset(); + this->phys_offset = entry.GetPhysicalOffset(); + this->storage_index = entry.storage_index; + } + }; + static_assert(std::is_trivial_v<EntryData>); + +public: + IndirectStorage() : m_table(), m_data_storage() {} + virtual ~IndirectStorage() { + this->Finalize(); + } + + Result Initialize(VirtualFile table_storage); + void Finalize(); + + bool IsInitialized() const { + return m_table.IsInitialized(); + } + + Result Initialize(VirtualFile node_storage, VirtualFile entry_storage, s32 entry_count) { + R_RETURN( + m_table.Initialize(node_storage, entry_storage, NodeSize, sizeof(Entry), entry_count)); + } + + void SetStorage(s32 idx, VirtualFile storage) { + ASSERT(0 <= idx && idx < StorageCount); + m_data_storage[idx] = storage; + } + + template <typename T> + void SetStorage(s32 idx, T storage, s64 offset, s64 size) { + ASSERT(0 <= idx && idx < StorageCount); + m_data_storage[idx] = std::make_shared<OffsetVfsFile>(storage, size, offset); + } + + Result GetEntryList(Entry* out_entries, s32* out_entry_count, s32 entry_count, s64 offset, + s64 size); + + virtual size_t GetSize() const override { + BucketTree::Offsets offsets{}; + m_table.GetOffsets(std::addressof(offsets)); + + return offsets.end_offset; + } + + virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; + +public: + static constexpr s64 QueryHeaderStorageSize() { + return BucketTree::QueryHeaderStorageSize(); + } + + static constexpr s64 QueryNodeStorageSize(s32 entry_count) { + return BucketTree::QueryNodeStorageSize(NodeSize, sizeof(Entry), entry_count); + } + + static constexpr s64 QueryEntryStorageSize(s32 entry_count) { + return BucketTree::QueryEntryStorageSize(NodeSize, sizeof(Entry), entry_count); + } + +protected: + BucketTree& GetEntryTable() { + return m_table; + } + + VirtualFile& GetDataStorage(s32 index) { + ASSERT(0 <= index && index < StorageCount); + return m_data_storage[index]; + } + + template <bool ContinuousCheck, bool RangeCheck, typename F> + Result OperatePerEntry(s64 offset, s64 size, F func); + +private: + struct ContinuousReadingEntry { + static constexpr size_t FragmentSizeMax = 4_KiB; + + IndirectStorage::Entry entry; + + s64 GetVirtualOffset() const { + return this->entry.GetVirtualOffset(); + } + + s64 GetPhysicalOffset() const { + return this->entry.GetPhysicalOffset(); + } + + bool IsFragment() const { + return this->entry.storage_index != 0; + } + }; + static_assert(std::is_trivial_v<ContinuousReadingEntry>); + +private: + mutable BucketTree m_table; + std::array<VirtualFile, StorageCount> m_data_storage; +}; + +template <bool ContinuousCheck, bool RangeCheck, typename F> +Result IndirectStorage::OperatePerEntry(s64 offset, s64 size, F func) { + // Validate preconditions. + ASSERT(offset >= 0); + ASSERT(size >= 0); + ASSERT(this->IsInitialized()); + + // Succeed if there's nothing to operate on. + R_SUCCEED_IF(size == 0); + + // Get the table offsets. + BucketTree::Offsets table_offsets; + R_TRY(m_table.GetOffsets(std::addressof(table_offsets))); + + // Validate arguments. + R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange); + + // Find the offset in our tree. + BucketTree::Visitor visitor; + R_TRY(m_table.Find(std::addressof(visitor), offset)); + { + const auto entry_offset = visitor.Get<Entry>()->GetVirtualOffset(); + R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset), + ResultInvalidIndirectEntryOffset); + } + + // Prepare to operate in chunks. + auto cur_offset = offset; + const auto end_offset = offset + static_cast<s64>(size); + BucketTree::ContinuousReadingInfo cr_info; + + while (cur_offset < end_offset) { + // Get the current entry. + const auto cur_entry = *visitor.Get<Entry>(); + + // Get and validate the entry's offset. + const auto cur_entry_offset = cur_entry.GetVirtualOffset(); + R_UNLESS(cur_entry_offset <= cur_offset, ResultInvalidIndirectEntryOffset); + + // Validate the storage index. + R_UNLESS(0 <= cur_entry.storage_index && cur_entry.storage_index < StorageCount, + ResultInvalidIndirectEntryStorageIndex); + + // If we need to check the continuous info, do so. + if constexpr (ContinuousCheck) { + // Scan, if we need to. + if (cr_info.CheckNeedScan()) { + R_TRY(visitor.ScanContinuousReading<ContinuousReadingEntry>( + std::addressof(cr_info), cur_offset, + static_cast<size_t>(end_offset - cur_offset))); + } + + // Process a base storage entry. + if (cr_info.CanDo()) { + // Ensure that we can process. + R_UNLESS(cur_entry.storage_index == 0, ResultInvalidIndirectEntryStorageIndex); + + // Ensure that we remain within range. + const auto data_offset = cur_offset - cur_entry_offset; + const auto cur_entry_phys_offset = cur_entry.GetPhysicalOffset(); + const auto cur_size = static_cast<s64>(cr_info.GetReadSize()); + + // If we should, verify the range. + if constexpr (RangeCheck) { + // Get the current data storage's size. + s64 cur_data_storage_size = m_data_storage[0]->GetSize(); + + R_UNLESS(0 <= cur_entry_phys_offset && + cur_entry_phys_offset <= cur_data_storage_size, + ResultInvalidIndirectEntryOffset); + R_UNLESS(cur_entry_phys_offset + data_offset + cur_size <= + cur_data_storage_size, + ResultInvalidIndirectStorageSize); + } + + // Operate. + R_TRY(func(m_data_storage[0], cur_entry_phys_offset + data_offset, cur_offset, + cur_size)); + + // Mark as done. + cr_info.Done(); + } + } + + // Get and validate the next entry offset. + s64 next_entry_offset; + if (visitor.CanMoveNext()) { + R_TRY(visitor.MoveNext()); + next_entry_offset = visitor.Get<Entry>()->GetVirtualOffset(); + R_UNLESS(table_offsets.IsInclude(next_entry_offset), ResultInvalidIndirectEntryOffset); + } else { + next_entry_offset = table_offsets.end_offset; + } + R_UNLESS(cur_offset < next_entry_offset, ResultInvalidIndirectEntryOffset); + + // Get the offset of the entry in the data we read. + const auto data_offset = cur_offset - cur_entry_offset; + const auto data_size = (next_entry_offset - cur_entry_offset); + ASSERT(data_size > 0); + + // Determine how much is left. + const auto remaining_size = end_offset - cur_offset; + const auto cur_size = std::min<s64>(remaining_size, data_size - data_offset); + ASSERT(cur_size <= size); + + // Operate, if we need to. + bool needs_operate; + if constexpr (!ContinuousCheck) { + needs_operate = true; + } else { + needs_operate = !cr_info.IsDone() || cur_entry.storage_index != 0; + } + + if (needs_operate) { + const auto cur_entry_phys_offset = cur_entry.GetPhysicalOffset(); + + if constexpr (RangeCheck) { + // Get the current data storage's size. + s64 cur_data_storage_size = m_data_storage[cur_entry.storage_index]->GetSize(); + + // Ensure that we remain within range. + R_UNLESS(0 <= cur_entry_phys_offset && + cur_entry_phys_offset <= cur_data_storage_size, + ResultIndirectStorageCorrupted); + R_UNLESS(cur_entry_phys_offset + data_offset + cur_size <= cur_data_storage_size, + ResultIndirectStorageCorrupted); + } + + R_TRY(func(m_data_storage[cur_entry.storage_index], cur_entry_phys_offset + data_offset, + cur_offset, cur_size)); + } + + cur_offset += cur_size; + } + + R_SUCCEED(); +} + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.cpp b/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.cpp new file mode 100644 index 000000000..2c3da230c --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.cpp @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/file_sys/fssystem/fssystem_integrity_romfs_storage.h" + +namespace FileSys { + +Result IntegrityRomFsStorage::Initialize( + HierarchicalIntegrityVerificationInformation level_hash_info, Hash master_hash, + HierarchicalIntegrityVerificationStorage::HierarchicalStorageInformation storage_info, + int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level) { + // Set master hash. + m_master_hash = master_hash; + m_master_hash_storage = std::make_shared<ArrayVfsFile<sizeof(Hash)>>(m_master_hash.value); + R_UNLESS(m_master_hash_storage != nullptr, + ResultAllocationMemoryFailedInIntegrityRomFsStorageA); + + // Set the master hash storage. + storage_info[0] = m_master_hash_storage; + + // Initialize our integrity storage. + R_RETURN(m_integrity_storage.Initialize(level_hash_info, storage_info, max_data_cache_entries, + max_hash_cache_entries, buffer_level)); +} + +void IntegrityRomFsStorage::Finalize() { + m_integrity_storage.Finalize(); +} + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.h b/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.h new file mode 100644 index 000000000..5f8512b2a --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_integrity_romfs_storage.h @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h" +#include "core/file_sys/fssystem/fssystem_nca_header.h" +#include "core/file_sys/vfs_vector.h" + +namespace FileSys { + +constexpr inline size_t IntegrityLayerCountRomFs = 7; +constexpr inline size_t IntegrityHashLayerBlockSize = 16_KiB; + +class IntegrityRomFsStorage : public IReadOnlyStorage { +public: + IntegrityRomFsStorage() {} + virtual ~IntegrityRomFsStorage() override { + this->Finalize(); + } + + Result Initialize( + HierarchicalIntegrityVerificationInformation level_hash_info, Hash master_hash, + HierarchicalIntegrityVerificationStorage::HierarchicalStorageInformation storage_info, + int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level); + void Finalize(); + + virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { + return m_integrity_storage.Read(buffer, size, offset); + } + + virtual size_t GetSize() const override { + return m_integrity_storage.GetSize(); + } + +private: + HierarchicalIntegrityVerificationStorage m_integrity_storage; + Hash m_master_hash; + std::shared_ptr<ArrayVfsFile<sizeof(Hash)>> m_master_hash_storage; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.cpp b/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.cpp new file mode 100644 index 000000000..2f73abf86 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.cpp @@ -0,0 +1,91 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/alignment.h" +#include "core/file_sys/fssystem/fssystem_integrity_verification_storage.h" + +namespace FileSys { + +constexpr inline u32 ILog2(u32 val) { + ASSERT(val > 0); + return static_cast<u32>((sizeof(u32) * 8) - 1 - std::countl_zero<u32>(val)); +} + +void IntegrityVerificationStorage::Initialize(VirtualFile hs, VirtualFile ds, s64 verif_block_size, + s64 upper_layer_verif_block_size, bool is_real_data) { + // Validate preconditions. + ASSERT(verif_block_size >= HashSize); + + // Set storages. + m_hash_storage = hs; + m_data_storage = ds; + + // Set verification block sizes. + m_verification_block_size = verif_block_size; + m_verification_block_order = ILog2(static_cast<u32>(verif_block_size)); + ASSERT(m_verification_block_size == 1ll << m_verification_block_order); + + // Set upper layer block sizes. + upper_layer_verif_block_size = std::max(upper_layer_verif_block_size, HashSize); + m_upper_layer_verification_block_size = upper_layer_verif_block_size; + m_upper_layer_verification_block_order = ILog2(static_cast<u32>(upper_layer_verif_block_size)); + ASSERT(m_upper_layer_verification_block_size == 1ll << m_upper_layer_verification_block_order); + + // Validate sizes. + { + s64 hash_size = m_hash_storage->GetSize(); + s64 data_size = m_data_storage->GetSize(); + ASSERT(((hash_size / HashSize) * m_verification_block_size) >= data_size); + } + + // Set data. + m_is_real_data = is_real_data; +} + +void IntegrityVerificationStorage::Finalize() { + m_hash_storage = VirtualFile(); + m_data_storage = VirtualFile(); +} + +size_t IntegrityVerificationStorage::Read(u8* buffer, size_t size, size_t offset) const { + // Succeed if zero size. + if (size == 0) { + return size; + } + + // Validate arguments. + ASSERT(buffer != nullptr); + + // Validate the offset. + s64 data_size = m_data_storage->GetSize(); + ASSERT(offset <= static_cast<size_t>(data_size)); + + // Validate the access range. + ASSERT(R_SUCCEEDED(IStorage::CheckAccessRange( + offset, size, Common::AlignUp(data_size, static_cast<size_t>(m_verification_block_size))))); + + // Determine the read extents. + size_t read_size = size; + if (static_cast<s64>(offset + read_size) > data_size) { + // Determine the padding sizes. + s64 padding_offset = data_size - offset; + size_t padding_size = static_cast<size_t>( + m_verification_block_size - (padding_offset & (m_verification_block_size - 1))); + ASSERT(static_cast<s64>(padding_size) < m_verification_block_size); + + // Clear the padding. + std::memset(static_cast<u8*>(buffer) + padding_offset, 0, padding_size); + + // Set the new in-bounds size. + read_size = static_cast<size_t>(data_size - offset); + } + + // Perform the read. + return m_data_storage->Read(buffer, read_size, offset); +} + +size_t IntegrityVerificationStorage::GetSize() const { + return m_data_storage->GetSize(); +} + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.h b/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.h new file mode 100644 index 000000000..09f76799d --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_integrity_verification_storage.h @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <optional> + +#include "core/file_sys/fssystem/fs_i_storage.h" +#include "core/file_sys/fssystem/fs_types.h" + +namespace FileSys { + +class IntegrityVerificationStorage : public IReadOnlyStorage { + YUZU_NON_COPYABLE(IntegrityVerificationStorage); + YUZU_NON_MOVEABLE(IntegrityVerificationStorage); + +public: + static constexpr s64 HashSize = 256 / 8; + + struct BlockHash { + std::array<u8, HashSize> hash; + }; + static_assert(std::is_trivial_v<BlockHash>); + +public: + IntegrityVerificationStorage() + : m_verification_block_size(0), m_verification_block_order(0), + m_upper_layer_verification_block_size(0), m_upper_layer_verification_block_order(0) {} + virtual ~IntegrityVerificationStorage() override { + this->Finalize(); + } + + void Initialize(VirtualFile hs, VirtualFile ds, s64 verif_block_size, + s64 upper_layer_verif_block_size, bool is_real_data); + void Finalize(); + + virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; + virtual size_t GetSize() const override; + + s64 GetBlockSize() const { + return m_verification_block_size; + } + +private: + static void SetValidationBit(BlockHash* hash) { + ASSERT(hash != nullptr); + hash->hash[HashSize - 1] |= 0x80; + } + + static bool IsValidationBit(const BlockHash* hash) { + ASSERT(hash != nullptr); + return (hash->hash[HashSize - 1] & 0x80) != 0; + } + +private: + VirtualFile m_hash_storage; + VirtualFile m_data_storage; + s64 m_verification_block_size; + s64 m_verification_block_order; + s64 m_upper_layer_verification_block_size; + s64 m_upper_layer_verification_block_order; + bool m_is_real_data; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h b/src/core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h new file mode 100644 index 000000000..c07a127fb --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/file_sys/fssystem/fs_i_storage.h" + +namespace FileSys { + +class MemoryResourceBufferHoldStorage : public IStorage { + YUZU_NON_COPYABLE(MemoryResourceBufferHoldStorage); + YUZU_NON_MOVEABLE(MemoryResourceBufferHoldStorage); + +public: + MemoryResourceBufferHoldStorage(VirtualFile storage, size_t buffer_size) + : m_storage(std::move(storage)), m_buffer(::operator new(buffer_size)), + m_buffer_size(buffer_size) {} + + virtual ~MemoryResourceBufferHoldStorage() { + // If we have a buffer, deallocate it. + if (m_buffer != nullptr) { + ::operator delete(m_buffer); + } + } + + bool IsValid() const { + return m_buffer != nullptr; + } + void* GetBuffer() const { + return m_buffer; + } + +public: + virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { + // Check pre-conditions. + ASSERT(m_storage != nullptr); + + return m_storage->Read(buffer, size, offset); + } + + virtual size_t GetSize() const override { + // Check pre-conditions. + ASSERT(m_storage != nullptr); + + return m_storage->GetSize(); + } + + virtual size_t Write(const u8* buffer, size_t size, size_t offset) override { + // Check pre-conditions. + ASSERT(m_storage != nullptr); + + return m_storage->Write(buffer, size, offset); + } + +private: + VirtualFile m_storage; + void* m_buffer; + size_t m_buffer_size; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp new file mode 100644 index 000000000..0f5432203 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.cpp @@ -0,0 +1,1351 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/file_sys/fssystem/fssystem_aes_ctr_counter_extended_storage.h" +#include "core/file_sys/fssystem/fssystem_aes_ctr_storage.h" +#include "core/file_sys/fssystem/fssystem_aes_xts_storage.h" +#include "core/file_sys/fssystem/fssystem_alignment_matching_storage.h" +#include "core/file_sys/fssystem/fssystem_compressed_storage.h" +#include "core/file_sys/fssystem/fssystem_hierarchical_integrity_verification_storage.h" +#include "core/file_sys/fssystem/fssystem_hierarchical_sha256_storage.h" +#include "core/file_sys/fssystem/fssystem_indirect_storage.h" +#include "core/file_sys/fssystem/fssystem_integrity_romfs_storage.h" +#include "core/file_sys/fssystem/fssystem_memory_resource_buffer_hold_storage.h" +#include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h" +#include "core/file_sys/fssystem/fssystem_sparse_storage.h" +#include "core/file_sys/fssystem/fssystem_switch_storage.h" +#include "core/file_sys/vfs_offset.h" +#include "core/file_sys/vfs_vector.h" + +namespace FileSys { + +namespace { + +constexpr inline s32 IntegrityDataCacheCount = 24; +constexpr inline s32 IntegrityHashCacheCount = 8; + +constexpr inline s32 IntegrityDataCacheCountForMeta = 16; +constexpr inline s32 IntegrityHashCacheCountForMeta = 2; + +class SharedNcaBodyStorage : public IReadOnlyStorage { + YUZU_NON_COPYABLE(SharedNcaBodyStorage); + YUZU_NON_MOVEABLE(SharedNcaBodyStorage); + +private: + VirtualFile m_storage; + std::shared_ptr<NcaReader> m_nca_reader; + +public: + SharedNcaBodyStorage(VirtualFile s, std::shared_ptr<NcaReader> r) + : m_storage(std::move(s)), m_nca_reader(std::move(r)) {} + + virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { + // Validate pre-conditions. + ASSERT(m_storage != nullptr); + + // Read from the base storage. + return m_storage->Read(buffer, size, offset); + } + + virtual size_t GetSize() const override { + // Validate pre-conditions. + ASSERT(m_storage != nullptr); + + return m_storage->GetSize(); + } +}; + +inline s64 GetFsOffset(const NcaReader& reader, s32 fs_index) { + return static_cast<s64>(reader.GetFsOffset(fs_index)); +} + +inline s64 GetFsEndOffset(const NcaReader& reader, s32 fs_index) { + return static_cast<s64>(reader.GetFsEndOffset(fs_index)); +} + +using Sha256DataRegion = NcaFsHeader::Region; +using IntegrityLevelInfo = NcaFsHeader::HashData::IntegrityMetaInfo::LevelHashInfo; +using IntegrityDataInfo = IntegrityLevelInfo::HierarchicalIntegrityVerificationLevelInformation; + +} // namespace + +Result NcaFileSystemDriver::OpenStorageWithContext(VirtualFile* out, + NcaFsHeaderReader* out_header_reader, + s32 fs_index, StorageContext* ctx) { + // Open storage. + R_RETURN(this->OpenStorageImpl(out, out_header_reader, fs_index, ctx)); +} + +Result NcaFileSystemDriver::OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader* out_header_reader, + s32 fs_index, StorageContext* ctx) { + // Validate preconditions. + ASSERT(out != nullptr); + ASSERT(out_header_reader != nullptr); + ASSERT(0 <= fs_index && fs_index < NcaHeader::FsCountMax); + + // Validate the fs index. + R_UNLESS(m_reader->HasFsInfo(fs_index), ResultPartitionNotFound); + + // Initialize our header reader for the fs index. + R_TRY(out_header_reader->Initialize(*m_reader, fs_index)); + + // Declare the storage we're opening. + VirtualFile storage; + + // Process sparse layer. + s64 fs_data_offset = 0; + if (out_header_reader->ExistsSparseLayer()) { + // Get the sparse info. + const auto& sparse_info = out_header_reader->GetSparseInfo(); + + // Create based on whether we have a meta hash layer. + if (out_header_reader->ExistsSparseMetaHashLayer()) { + // Create the sparse storage with verification. + R_TRY(this->CreateSparseStorageWithVerification( + std::addressof(storage), std::addressof(fs_data_offset), + ctx != nullptr ? std::addressof(ctx->current_sparse_storage) : nullptr, + ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, + ctx != nullptr ? std::addressof(ctx->sparse_layer_info_storage) : nullptr, fs_index, + out_header_reader->GetAesCtrUpperIv(), sparse_info, + out_header_reader->GetSparseMetaDataHashDataInfo(), + out_header_reader->GetSparseMetaHashType())); + } else { + // Create the sparse storage. + R_TRY(this->CreateSparseStorage( + std::addressof(storage), std::addressof(fs_data_offset), + ctx != nullptr ? std::addressof(ctx->current_sparse_storage) : nullptr, + ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, + fs_index, out_header_reader->GetAesCtrUpperIv(), sparse_info)); + } + } else { + // Get the data offsets. + fs_data_offset = GetFsOffset(*m_reader, fs_index); + const auto fs_end_offset = GetFsEndOffset(*m_reader, fs_index); + + // Validate that we're within range. + const auto data_size = fs_end_offset - fs_data_offset; + R_UNLESS(data_size > 0, ResultInvalidNcaHeader); + + // Create the body substorage. + R_TRY(this->CreateBodySubStorage(std::addressof(storage), fs_data_offset, data_size)); + + // Potentially save the body substorage to our context. + if (ctx != nullptr) { + ctx->body_substorage = storage; + } + } + + // Process patch layer. + const auto& patch_info = out_header_reader->GetPatchInfo(); + VirtualFile patch_meta_aes_ctr_ex_meta_storage; + VirtualFile patch_meta_indirect_meta_storage; + if (out_header_reader->ExistsPatchMetaHashLayer()) { + // Check the meta hash type. + R_UNLESS(out_header_reader->GetPatchMetaHashType() == + NcaFsHeader::MetaDataHashType::HierarchicalIntegrity, + ResultRomNcaInvalidPatchMetaDataHashType); + + // Create the patch meta storage. + R_TRY(this->CreatePatchMetaStorage( + std::addressof(patch_meta_aes_ctr_ex_meta_storage), + std::addressof(patch_meta_indirect_meta_storage), + ctx != nullptr ? std::addressof(ctx->patch_layer_info_storage) : nullptr, storage, + fs_data_offset, out_header_reader->GetAesCtrUpperIv(), patch_info, + out_header_reader->GetPatchMetaDataHashDataInfo())); + } + + if (patch_info.HasAesCtrExTable()) { + // Check the encryption type. + ASSERT(out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::None || + out_header_reader->GetEncryptionType() == NcaFsHeader::EncryptionType::AesCtrEx || + out_header_reader->GetEncryptionType() == + NcaFsHeader::EncryptionType::AesCtrExSkipLayerHash); + + // Create the ex meta storage. + VirtualFile aes_ctr_ex_storage_meta_storage = patch_meta_aes_ctr_ex_meta_storage; + if (aes_ctr_ex_storage_meta_storage == nullptr) { + // If we don't have a meta storage, we must not have a patch meta hash layer. + ASSERT(!out_header_reader->ExistsPatchMetaHashLayer()); + + R_TRY(this->CreateAesCtrExStorageMetaStorage( + std::addressof(aes_ctr_ex_storage_meta_storage), storage, fs_data_offset, + out_header_reader->GetEncryptionType(), out_header_reader->GetAesCtrUpperIv(), + patch_info)); + } + + // Create the ex storage. + VirtualFile aes_ctr_ex_storage; + R_TRY(this->CreateAesCtrExStorage( + std::addressof(aes_ctr_ex_storage), + ctx != nullptr ? std::addressof(ctx->aes_ctr_ex_storage) : nullptr, std::move(storage), + aes_ctr_ex_storage_meta_storage, fs_data_offset, out_header_reader->GetAesCtrUpperIv(), + patch_info)); + + // Set the base storage as the ex storage. + storage = std::move(aes_ctr_ex_storage); + + // Potentially save storages to our context. + if (ctx != nullptr) { + ctx->aes_ctr_ex_storage_meta_storage = aes_ctr_ex_storage_meta_storage; + ctx->aes_ctr_ex_storage_data_storage = storage; + ctx->fs_data_storage = storage; + } + } else { + // Create the appropriate storage for the encryption type. + switch (out_header_reader->GetEncryptionType()) { + case NcaFsHeader::EncryptionType::None: + // If there's no encryption, use the base storage we made previously. + break; + case NcaFsHeader::EncryptionType::AesXts: + R_TRY(this->CreateAesXtsStorage(std::addressof(storage), std::move(storage), + fs_data_offset)); + break; + case NcaFsHeader::EncryptionType::AesCtr: + R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage), + fs_data_offset, out_header_reader->GetAesCtrUpperIv(), + AlignmentStorageRequirement::None)); + break; + case NcaFsHeader::EncryptionType::AesCtrSkipLayerHash: { + // Create the aes ctr storage. + VirtualFile aes_ctr_storage; + R_TRY(this->CreateAesCtrStorage(std::addressof(aes_ctr_storage), storage, + fs_data_offset, out_header_reader->GetAesCtrUpperIv(), + AlignmentStorageRequirement::None)); + + // Create region switch storage. + R_TRY(this->CreateRegionSwitchStorage(std::addressof(storage), out_header_reader, + std::move(storage), std::move(aes_ctr_storage))); + } break; + default: + R_THROW(ResultInvalidNcaFsHeaderEncryptionType); + } + + // Potentially save storages to our context. + if (ctx != nullptr) { + ctx->fs_data_storage = storage; + } + } + + // Process indirect layer. + if (patch_info.HasIndirectTable()) { + // Create the indirect meta storage. + VirtualFile indirect_storage_meta_storage = patch_meta_indirect_meta_storage; + if (indirect_storage_meta_storage == nullptr) { + // If we don't have a meta storage, we must not have a patch meta hash layer. + ASSERT(!out_header_reader->ExistsPatchMetaHashLayer()); + + R_TRY(this->CreateIndirectStorageMetaStorage( + std::addressof(indirect_storage_meta_storage), storage, patch_info)); + } + + // Potentially save the indirect meta storage to our context. + if (ctx != nullptr) { + ctx->indirect_storage_meta_storage = indirect_storage_meta_storage; + } + + // Get the original indirectable storage. + VirtualFile original_indirectable_storage; + if (m_original_reader != nullptr && m_original_reader->HasFsInfo(fs_index)) { + // Create a driver for the original. + NcaFileSystemDriver original_driver(m_original_reader); + + // Create a header reader for the original. + NcaFsHeaderReader original_header_reader; + R_TRY(original_header_reader.Initialize(*m_original_reader, fs_index)); + + // Open original indirectable storage. + R_TRY(original_driver.OpenIndirectableStorageAsOriginal( + std::addressof(original_indirectable_storage), + std::addressof(original_header_reader), ctx)); + } else if (ctx != nullptr && ctx->external_original_storage != nullptr) { + // Use the external original storage. + original_indirectable_storage = ctx->external_original_storage; + } else { + // Allocate a dummy memory storage as original storage. + original_indirectable_storage = std::make_shared<VectorVfsFile>(); + R_UNLESS(original_indirectable_storage != nullptr, + ResultAllocationMemoryFailedAllocateShared); + } + + // Create the indirect storage. + VirtualFile indirect_storage; + R_TRY(this->CreateIndirectStorage( + std::addressof(indirect_storage), + ctx != nullptr ? std::addressof(ctx->indirect_storage) : nullptr, std::move(storage), + std::move(original_indirectable_storage), std::move(indirect_storage_meta_storage), + patch_info)); + + // Set storage as the indirect storage. + storage = std::move(indirect_storage); + } + + // Check if we're sparse or requested to skip the integrity layer. + if (out_header_reader->ExistsSparseLayer() || (ctx != nullptr && ctx->open_raw_storage)) { + *out = std::move(storage); + R_SUCCEED(); + } + + // Create the non-raw storage. + R_RETURN(this->CreateStorageByRawStorage(out, out_header_reader, std::move(storage), ctx)); +} + +Result NcaFileSystemDriver::CreateStorageByRawStorage(VirtualFile* out, + const NcaFsHeaderReader* header_reader, + VirtualFile raw_storage, + StorageContext* ctx) { + // Initialize storage as raw storage. + VirtualFile storage = std::move(raw_storage); + + // Process hash/integrity layer. + switch (header_reader->GetHashType()) { + case NcaFsHeader::HashType::HierarchicalSha256Hash: + R_TRY(this->CreateSha256Storage(std::addressof(storage), std::move(storage), + header_reader->GetHashData().hierarchical_sha256_data)); + break; + case NcaFsHeader::HashType::HierarchicalIntegrityHash: + R_TRY(this->CreateIntegrityVerificationStorage( + std::addressof(storage), std::move(storage), + header_reader->GetHashData().integrity_meta_info)); + break; + default: + R_THROW(ResultInvalidNcaFsHeaderHashType); + } + + // Process compression layer. + if (header_reader->ExistsCompressionLayer()) { + R_TRY(this->CreateCompressedStorage( + std::addressof(storage), + ctx != nullptr ? std::addressof(ctx->compressed_storage) : nullptr, + ctx != nullptr ? std::addressof(ctx->compressed_storage_meta_storage) : nullptr, + std::move(storage), header_reader->GetCompressionInfo())); + } + + // Set output storage. + *out = std::move(storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::OpenIndirectableStorageAsOriginal( + VirtualFile* out, const NcaFsHeaderReader* header_reader, StorageContext* ctx) { + // Get the fs index. + const auto fs_index = header_reader->GetFsIndex(); + + // Declare the storage we're opening. + VirtualFile storage; + + // Process sparse layer. + s64 fs_data_offset = 0; + if (header_reader->ExistsSparseLayer()) { + // Get the sparse info. + const auto& sparse_info = header_reader->GetSparseInfo(); + + // Create based on whether we have a meta hash layer. + if (header_reader->ExistsSparseMetaHashLayer()) { + // Create the sparse storage with verification. + R_TRY(this->CreateSparseStorageWithVerification( + std::addressof(storage), std::addressof(fs_data_offset), + ctx != nullptr ? std::addressof(ctx->original_sparse_storage) : nullptr, + ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, + ctx != nullptr ? std::addressof(ctx->sparse_layer_info_storage) : nullptr, fs_index, + header_reader->GetAesCtrUpperIv(), sparse_info, + header_reader->GetSparseMetaDataHashDataInfo(), + header_reader->GetSparseMetaHashType())); + } else { + // Create the sparse storage. + R_TRY(this->CreateSparseStorage( + std::addressof(storage), std::addressof(fs_data_offset), + ctx != nullptr ? std::addressof(ctx->original_sparse_storage) : nullptr, + ctx != nullptr ? std::addressof(ctx->sparse_storage_meta_storage) : nullptr, + fs_index, header_reader->GetAesCtrUpperIv(), sparse_info)); + } + } else { + // Get the data offsets. + fs_data_offset = GetFsOffset(*m_reader, fs_index); + const auto fs_end_offset = GetFsEndOffset(*m_reader, fs_index); + + // Validate that we're within range. + const auto data_size = fs_end_offset - fs_data_offset; + R_UNLESS(data_size > 0, ResultInvalidNcaHeader); + + // Create the body substorage. + R_TRY(this->CreateBodySubStorage(std::addressof(storage), fs_data_offset, data_size)); + } + + // Create the appropriate storage for the encryption type. + switch (header_reader->GetEncryptionType()) { + case NcaFsHeader::EncryptionType::None: + // If there's no encryption, use the base storage we made previously. + break; + case NcaFsHeader::EncryptionType::AesXts: + R_TRY( + this->CreateAesXtsStorage(std::addressof(storage), std::move(storage), fs_data_offset)); + break; + case NcaFsHeader::EncryptionType::AesCtr: + R_TRY(this->CreateAesCtrStorage(std::addressof(storage), std::move(storage), fs_data_offset, + header_reader->GetAesCtrUpperIv(), + AlignmentStorageRequirement::CacheBlockSize)); + break; + default: + R_THROW(ResultInvalidNcaFsHeaderEncryptionType); + } + + // Set output storage. + *out = std::move(storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateBodySubStorage(VirtualFile* out, s64 offset, s64 size) { + // Create the body storage. + auto body_storage = + std::make_shared<SharedNcaBodyStorage>(m_reader->GetSharedBodyStorage(), m_reader); + R_UNLESS(body_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Get the body storage size. + s64 body_size = body_storage->GetSize(); + + // Check that we're within range. + R_UNLESS(offset + size <= body_size, ResultNcaBaseStorageOutOfRangeB); + + // Create substorage. + auto body_substorage = std::make_shared<OffsetVfsFile>(std::move(body_storage), size, offset); + R_UNLESS(body_substorage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Set the output storage. + *out = std::move(body_substorage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateAesCtrStorage( + VirtualFile* out, VirtualFile base_storage, s64 offset, const NcaAesCtrUpperIv& upper_iv, + AlignmentStorageRequirement alignment_storage_requirement) { + // Check pre-conditions. + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + + // Create the iv. + std::array<u8, AesCtrStorage::IvSize> iv{}; + AesCtrStorage::MakeIv(iv.data(), sizeof(iv), upper_iv.value, offset); + + // Create the ctr storage. + VirtualFile aes_ctr_storage; + if (m_reader->HasExternalDecryptionKey()) { + aes_ctr_storage = std::make_shared<AesCtrStorage>( + std::move(base_storage), m_reader->GetExternalDecryptionKey(), AesCtrStorage::KeySize, + iv.data(), AesCtrStorage::IvSize); + R_UNLESS(aes_ctr_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + } else { + // Create software decryption storage. + auto sw_storage = std::make_shared<AesCtrStorage>( + base_storage, m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtr), + AesCtrStorage::KeySize, iv.data(), AesCtrStorage::IvSize); + R_UNLESS(sw_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + aes_ctr_storage = std::move(sw_storage); + } + + // Create alignment matching storage. + auto aligned_storage = std::make_shared<AlignmentMatchingStorage<NcaHeader::CtrBlockSize, 1>>( + std::move(aes_ctr_storage)); + R_UNLESS(aligned_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Set the out storage. + *out = std::move(aligned_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateAesXtsStorage(VirtualFile* out, VirtualFile base_storage, + s64 offset) { + // Check pre-conditions. + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + + // Create the iv. + std::array<u8, AesXtsStorage::IvSize> iv{}; + AesXtsStorage::MakeAesXtsIv(iv.data(), sizeof(iv), offset, NcaHeader::XtsBlockSize); + + // Make the aes xts storage. + const auto* const key1 = m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesXts1); + const auto* const key2 = m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesXts2); + auto xts_storage = + std::make_shared<AesXtsStorage>(std::move(base_storage), key1, key2, AesXtsStorage::KeySize, + iv.data(), AesXtsStorage::IvSize, NcaHeader::XtsBlockSize); + R_UNLESS(xts_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Create alignment matching storage. + auto aligned_storage = std::make_shared<AlignmentMatchingStorage<NcaHeader::XtsBlockSize, 1>>( + std::move(xts_storage)); + R_UNLESS(aligned_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Set the out storage. + *out = std::move(xts_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateSparseStorageMetaStorage(VirtualFile* out, + VirtualFile base_storage, s64 offset, + const NcaAesCtrUpperIv& upper_iv, + const NcaSparseInfo& sparse_info) { + // Validate preconditions. + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + + // Get the base storage size. + s64 base_size = base_storage->GetSize(); + + // Get the meta extents. + const auto meta_offset = sparse_info.bucket.offset; + const auto meta_size = sparse_info.bucket.size; + R_UNLESS(meta_offset + meta_size - offset <= base_size, ResultNcaBaseStorageOutOfRangeB); + + // Create the encrypted storage. + auto enc_storage = + std::make_shared<OffsetVfsFile>(std::move(base_storage), meta_size, meta_offset); + R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Create the decrypted storage. + VirtualFile decrypted_storage; + R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), + offset + meta_offset, sparse_info.MakeAesCtrUpperIv(upper_iv), + AlignmentStorageRequirement::None)); + + // Create buffered storage. + std::vector<u8> meta_data(meta_size); + decrypted_storage->Read(meta_data.data(), meta_size, 0); + + auto buffered_storage = std::make_shared<VectorVfsFile>(std::move(meta_data)); + R_UNLESS(buffered_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Set the output. + *out = std::move(buffered_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateSparseStorageCore(std::shared_ptr<SparseStorage>* out, + VirtualFile base_storage, s64 base_size, + VirtualFile meta_storage, + const NcaSparseInfo& sparse_info, + bool external_info) { + // Validate preconditions. + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + ASSERT(meta_storage != nullptr); + + // Read and verify the bucket tree header. + BucketTree::Header header; + std::memcpy(std::addressof(header), sparse_info.bucket.header.data(), sizeof(header)); + R_TRY(header.Verify()); + + // Determine storage extents. + const auto node_offset = 0; + const auto node_size = SparseStorage::QueryNodeStorageSize(header.entry_count); + const auto entry_offset = node_offset + node_size; + const auto entry_size = SparseStorage::QueryEntryStorageSize(header.entry_count); + + // Create the sparse storage. + auto sparse_storage = std::make_shared<SparseStorage>(); + R_UNLESS(sparse_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Sanity check that we can be doing this. + ASSERT(header.entry_count != 0); + + // Initialize the sparse storage. + R_TRY(sparse_storage->Initialize( + std::make_shared<OffsetVfsFile>(meta_storage, node_size, node_offset), + std::make_shared<OffsetVfsFile>(meta_storage, entry_size, entry_offset), + header.entry_count)); + + // If not external, set the data storage. + if (!external_info) { + sparse_storage->SetDataStorage( + std::make_shared<OffsetVfsFile>(std::move(base_storage), base_size, 0)); + } + + // Set the output. + *out = std::move(sparse_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateSparseStorage(VirtualFile* out, s64* out_fs_data_offset, + std::shared_ptr<SparseStorage>* out_sparse_storage, + VirtualFile* out_meta_storage, s32 index, + const NcaAesCtrUpperIv& upper_iv, + const NcaSparseInfo& sparse_info) { + // Validate preconditions. + ASSERT(out != nullptr); + ASSERT(out_fs_data_offset != nullptr); + + // Check the sparse info generation. + R_UNLESS(sparse_info.generation != 0, ResultInvalidNcaHeader); + + // Read and verify the bucket tree header. + BucketTree::Header header; + std::memcpy(std::addressof(header), sparse_info.bucket.header.data(), sizeof(header)); + R_TRY(header.Verify()); + + // Determine the storage extents. + const auto fs_offset = GetFsOffset(*m_reader, index); + const auto fs_end_offset = GetFsEndOffset(*m_reader, index); + const auto fs_size = fs_end_offset - fs_offset; + + // Create the sparse storage. + std::shared_ptr<SparseStorage> sparse_storage; + if (header.entry_count != 0) { + // Create the body substorage. + VirtualFile body_substorage; + R_TRY(this->CreateBodySubStorage(std::addressof(body_substorage), + sparse_info.physical_offset, + sparse_info.GetPhysicalSize())); + + // Create the meta storage. + VirtualFile meta_storage; + R_TRY(this->CreateSparseStorageMetaStorage(std::addressof(meta_storage), body_substorage, + sparse_info.physical_offset, upper_iv, + sparse_info)); + + // Potentially set the output meta storage. + if (out_meta_storage != nullptr) { + *out_meta_storage = meta_storage; + } + + // Create the sparse storage. + R_TRY(this->CreateSparseStorageCore(std::addressof(sparse_storage), body_substorage, + sparse_info.GetPhysicalSize(), std::move(meta_storage), + sparse_info, false)); + } else { + // If there are no entries, there's nothing to actually do. + sparse_storage = std::make_shared<SparseStorage>(); + R_UNLESS(sparse_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + sparse_storage->Initialize(fs_size); + } + + // Potentially set the output sparse storage. + if (out_sparse_storage != nullptr) { + *out_sparse_storage = sparse_storage; + } + + // Set the output fs data offset. + *out_fs_data_offset = fs_offset; + + // Set the output storage. + *out = std::move(sparse_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateSparseStorageMetaStorageWithVerification( + VirtualFile* out, VirtualFile* out_layer_info_storage, VirtualFile base_storage, s64 offset, + const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info, + const NcaMetaDataHashDataInfo& meta_data_hash_data_info) { + // Validate preconditions. + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + + // Get the base storage size. + s64 base_size = base_storage->GetSize(); + + // Get the meta extents. + const auto meta_offset = sparse_info.bucket.offset; + const auto meta_size = sparse_info.bucket.size; + R_UNLESS(meta_offset + meta_size - offset <= base_size, ResultNcaBaseStorageOutOfRangeB); + + // Get the meta data hash data extents. + const s64 meta_data_hash_data_offset = meta_data_hash_data_info.offset; + const s64 meta_data_hash_data_size = + Common::AlignUp<s64>(meta_data_hash_data_info.size, NcaHeader::CtrBlockSize); + R_UNLESS(meta_data_hash_data_offset + meta_data_hash_data_size <= base_size, + ResultNcaBaseStorageOutOfRangeB); + + // Check that the meta is before the hash data. + R_UNLESS(meta_offset + meta_size <= meta_data_hash_data_offset, + ResultRomNcaInvalidSparseMetaDataHashDataOffset); + + // Check that offsets are appropriately aligned. + R_UNLESS(Common::IsAligned<s64>(meta_data_hash_data_offset, NcaHeader::CtrBlockSize), + ResultRomNcaInvalidSparseMetaDataHashDataOffset); + R_UNLESS(Common::IsAligned<s64>(meta_offset, NcaHeader::CtrBlockSize), + ResultInvalidNcaFsHeader); + + // Create the meta storage. + auto enc_storage = std::make_shared<OffsetVfsFile>( + std::move(base_storage), + meta_data_hash_data_offset + meta_data_hash_data_size - meta_offset, meta_offset); + R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Create the decrypted storage. + VirtualFile decrypted_storage; + R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), + offset + meta_offset, sparse_info.MakeAesCtrUpperIv(upper_iv), + AlignmentStorageRequirement::None)); + + // Create the verification storage. + VirtualFile integrity_storage; + Result rc = this->CreateIntegrityVerificationStorageForMeta( + std::addressof(integrity_storage), out_layer_info_storage, std::move(decrypted_storage), + meta_offset, meta_data_hash_data_info); + if (rc == ResultInvalidNcaMetaDataHashDataSize) { + R_THROW(ResultRomNcaInvalidSparseMetaDataHashDataSize); + } + if (rc == ResultInvalidNcaMetaDataHashDataHash) { + R_THROW(ResultRomNcaInvalidSparseMetaDataHashDataHash); + } + R_TRY(rc); + + // Create the meta storage. + auto meta_storage = std::make_shared<OffsetVfsFile>(std::move(integrity_storage), meta_size, 0); + R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Set the output. + *out = std::move(meta_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateSparseStorageWithVerification( + VirtualFile* out, s64* out_fs_data_offset, std::shared_ptr<SparseStorage>* out_sparse_storage, + VirtualFile* out_meta_storage, VirtualFile* out_layer_info_storage, s32 index, + const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info, + const NcaMetaDataHashDataInfo& meta_data_hash_data_info, + NcaFsHeader::MetaDataHashType meta_data_hash_type) { + // Validate preconditions. + ASSERT(out != nullptr); + ASSERT(out_fs_data_offset != nullptr); + + // Check the sparse info generation. + R_UNLESS(sparse_info.generation != 0, ResultInvalidNcaHeader); + + // Read and verify the bucket tree header. + BucketTree::Header header; + std::memcpy(std::addressof(header), sparse_info.bucket.header.data(), sizeof(header)); + R_TRY(header.Verify()); + + // Determine the storage extents. + const auto fs_offset = GetFsOffset(*m_reader, index); + const auto fs_end_offset = GetFsEndOffset(*m_reader, index); + const auto fs_size = fs_end_offset - fs_offset; + + // Create the sparse storage. + std::shared_ptr<SparseStorage> sparse_storage; + if (header.entry_count != 0) { + // Create the body substorage. + VirtualFile body_substorage; + R_TRY(this->CreateBodySubStorage( + std::addressof(body_substorage), sparse_info.physical_offset, + Common::AlignUp<s64>(static_cast<s64>(meta_data_hash_data_info.offset) + + static_cast<s64>(meta_data_hash_data_info.size), + NcaHeader::CtrBlockSize))); + + // Check the meta data hash type. + R_UNLESS(meta_data_hash_type == NcaFsHeader::MetaDataHashType::HierarchicalIntegrity, + ResultRomNcaInvalidSparseMetaDataHashType); + + // Create the meta storage. + VirtualFile meta_storage; + R_TRY(this->CreateSparseStorageMetaStorageWithVerification( + std::addressof(meta_storage), out_layer_info_storage, body_substorage, + sparse_info.physical_offset, upper_iv, sparse_info, meta_data_hash_data_info)); + + // Potentially set the output meta storage. + if (out_meta_storage != nullptr) { + *out_meta_storage = meta_storage; + } + + // Create the sparse storage. + R_TRY(this->CreateSparseStorageCore(std::addressof(sparse_storage), body_substorage, + sparse_info.GetPhysicalSize(), std::move(meta_storage), + sparse_info, false)); + } else { + // If there are no entries, there's nothing to actually do. + sparse_storage = std::make_shared<SparseStorage>(); + R_UNLESS(sparse_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + sparse_storage->Initialize(fs_size); + } + + // Potentially set the output sparse storage. + if (out_sparse_storage != nullptr) { + *out_sparse_storage = sparse_storage; + } + + // Set the output fs data offset. + *out_fs_data_offset = fs_offset; + + // Set the output storage. + *out = std::move(sparse_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateAesCtrExStorageMetaStorage( + VirtualFile* out, VirtualFile base_storage, s64 offset, + NcaFsHeader::EncryptionType encryption_type, const NcaAesCtrUpperIv& upper_iv, + const NcaPatchInfo& patch_info) { + // Validate preconditions. + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + ASSERT(encryption_type == NcaFsHeader::EncryptionType::None || + encryption_type == NcaFsHeader::EncryptionType::AesCtrEx || + encryption_type == NcaFsHeader::EncryptionType::AesCtrExSkipLayerHash); + ASSERT(patch_info.HasAesCtrExTable()); + + // Validate patch info extents. + R_UNLESS(patch_info.indirect_size > 0, ResultInvalidNcaPatchInfoIndirectSize); + R_UNLESS(patch_info.aes_ctr_ex_size > 0, ResultInvalidNcaPatchInfoAesCtrExSize); + R_UNLESS(patch_info.indirect_size + patch_info.indirect_offset <= patch_info.aes_ctr_ex_offset, + ResultInvalidNcaPatchInfoAesCtrExOffset); + + // Get the base storage size. + s64 base_size = base_storage->GetSize(); + + // Get and validate the meta extents. + const s64 meta_offset = patch_info.aes_ctr_ex_offset; + const s64 meta_size = + Common::AlignUp(static_cast<s64>(patch_info.aes_ctr_ex_size), NcaHeader::XtsBlockSize); + R_UNLESS(meta_offset + meta_size <= base_size, ResultNcaBaseStorageOutOfRangeB); + + // Create the encrypted storage. + auto enc_storage = + std::make_shared<OffsetVfsFile>(std::move(base_storage), meta_size, meta_offset); + R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Create the decrypted storage. + VirtualFile decrypted_storage; + if (encryption_type != NcaFsHeader::EncryptionType::None) { + R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), + offset + meta_offset, upper_iv, + AlignmentStorageRequirement::None)); + } else { + // If encryption type is none, don't do any decryption. + decrypted_storage = std::move(enc_storage); + } + + // Create meta storage. + auto meta_storage = std::make_shared<OffsetVfsFile>(decrypted_storage, meta_size, 0); + R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Create buffered storage. + std::vector<u8> meta_data(meta_size); + meta_storage->Read(meta_data.data(), meta_size, 0); + + auto buffered_storage = std::make_shared<VectorVfsFile>(std::move(meta_data)); + R_UNLESS(buffered_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Set the output. + *out = std::move(buffered_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateAesCtrExStorage( + VirtualFile* out, std::shared_ptr<AesCtrCounterExtendedStorage>* out_ext, + VirtualFile base_storage, VirtualFile meta_storage, s64 counter_offset, + const NcaAesCtrUpperIv& upper_iv, const NcaPatchInfo& patch_info) { + // Validate pre-conditions. + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + ASSERT(meta_storage != nullptr); + ASSERT(patch_info.HasAesCtrExTable()); + + // Read the bucket tree header. + BucketTree::Header header; + std::memcpy(std::addressof(header), patch_info.aes_ctr_ex_header.data(), sizeof(header)); + R_TRY(header.Verify()); + + // Determine the bucket extents. + const auto entry_count = header.entry_count; + const s64 data_offset = 0; + const s64 data_size = patch_info.aes_ctr_ex_offset; + const s64 node_offset = 0; + const s64 node_size = AesCtrCounterExtendedStorage::QueryNodeStorageSize(entry_count); + const s64 entry_offset = node_offset + node_size; + const s64 entry_size = AesCtrCounterExtendedStorage::QueryEntryStorageSize(entry_count); + + // Create bucket storages. + auto data_storage = + std::make_shared<OffsetVfsFile>(std::move(base_storage), data_size, data_offset); + auto node_storage = std::make_shared<OffsetVfsFile>(meta_storage, node_size, node_offset); + auto entry_storage = std::make_shared<OffsetVfsFile>(meta_storage, entry_size, entry_offset); + + // Get the secure value. + const auto secure_value = upper_iv.part.secure_value; + + // Create the aes ctr ex storage. + VirtualFile aes_ctr_ex_storage; + if (m_reader->HasExternalDecryptionKey()) { + // Create the decryptor. + std::unique_ptr<AesCtrCounterExtendedStorage::IDecryptor> decryptor; + R_TRY(AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::addressof(decryptor))); + + // Create the aes ctr ex storage. + auto impl_storage = std::make_shared<AesCtrCounterExtendedStorage>(); + R_UNLESS(impl_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Initialize the aes ctr ex storage. + R_TRY(impl_storage->Initialize(m_reader->GetExternalDecryptionKey(), AesCtrStorage::KeySize, + secure_value, counter_offset, data_storage, node_storage, + entry_storage, entry_count, std::move(decryptor))); + + // Potentially set the output implementation storage. + if (out_ext != nullptr) { + *out_ext = impl_storage; + } + + // Set the implementation storage. + aes_ctr_ex_storage = std::move(impl_storage); + } else { + // Create the software decryptor. + std::unique_ptr<AesCtrCounterExtendedStorage::IDecryptor> sw_decryptor; + R_TRY(AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::addressof(sw_decryptor))); + + // Make the software storage. + auto sw_storage = std::make_shared<AesCtrCounterExtendedStorage>(); + R_UNLESS(sw_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Initialize the software storage. + R_TRY(sw_storage->Initialize(m_reader->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtr), + AesCtrStorage::KeySize, secure_value, counter_offset, + data_storage, node_storage, entry_storage, entry_count, + std::move(sw_decryptor))); + + // Potentially set the output implementation storage. + if (out_ext != nullptr) { + *out_ext = sw_storage; + } + + // Set the implementation storage. + aes_ctr_ex_storage = std::move(sw_storage); + } + + // Create an alignment-matching storage. + using AlignedStorage = AlignmentMatchingStorage<NcaHeader::CtrBlockSize, 1>; + auto aligned_storage = std::make_shared<AlignedStorage>(std::move(aes_ctr_ex_storage)); + R_UNLESS(aligned_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Set the output. + *out = std::move(aligned_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateIndirectStorageMetaStorage(VirtualFile* out, + VirtualFile base_storage, + const NcaPatchInfo& patch_info) { + // Validate preconditions. + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + ASSERT(patch_info.HasIndirectTable()); + + // Get the base storage size. + s64 base_size = base_storage->GetSize(); + + // Check that we're within range. + R_UNLESS(patch_info.indirect_offset + patch_info.indirect_size <= base_size, + ResultNcaBaseStorageOutOfRangeE); + + // Create the meta storage. + auto meta_storage = std::make_shared<OffsetVfsFile>(base_storage, patch_info.indirect_size, + patch_info.indirect_offset); + R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Create buffered storage. + std::vector<u8> meta_data(patch_info.indirect_size); + meta_storage->Read(meta_data.data(), patch_info.indirect_size, 0); + + auto buffered_storage = std::make_shared<VectorVfsFile>(std::move(meta_data)); + R_UNLESS(buffered_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Set the output. + *out = std::move(buffered_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateIndirectStorage( + VirtualFile* out, std::shared_ptr<IndirectStorage>* out_ind, VirtualFile base_storage, + VirtualFile original_data_storage, VirtualFile meta_storage, const NcaPatchInfo& patch_info) { + // Validate preconditions. + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + ASSERT(meta_storage != nullptr); + ASSERT(patch_info.HasIndirectTable()); + + // Read the bucket tree header. + BucketTree::Header header; + std::memcpy(std::addressof(header), patch_info.indirect_header.data(), sizeof(header)); + R_TRY(header.Verify()); + + // Determine the storage sizes. + const auto node_size = IndirectStorage::QueryNodeStorageSize(header.entry_count); + const auto entry_size = IndirectStorage::QueryEntryStorageSize(header.entry_count); + R_UNLESS(node_size + entry_size <= patch_info.indirect_size, + ResultInvalidNcaIndirectStorageOutOfRange); + + // Get the indirect data size. + const s64 indirect_data_size = patch_info.indirect_offset; + ASSERT(Common::IsAligned(indirect_data_size, NcaHeader::XtsBlockSize)); + + // Create the indirect data storage. + auto indirect_data_storage = + std::make_shared<OffsetVfsFile>(base_storage, indirect_data_size, 0); + R_UNLESS(indirect_data_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Create the indirect storage. + auto indirect_storage = std::make_shared<IndirectStorage>(); + R_UNLESS(indirect_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Initialize the indirect storage. + R_TRY(indirect_storage->Initialize( + std::make_shared<OffsetVfsFile>(meta_storage, node_size, 0), + std::make_shared<OffsetVfsFile>(meta_storage, entry_size, node_size), header.entry_count)); + + // Get the original data size. + s64 original_data_size = original_data_storage->GetSize(); + + // Set the indirect storages. + indirect_storage->SetStorage( + 0, std::make_shared<OffsetVfsFile>(original_data_storage, original_data_size, 0)); + indirect_storage->SetStorage( + 1, std::make_shared<OffsetVfsFile>(indirect_data_storage, indirect_data_size, 0)); + + // If necessary, set the output indirect storage. + if (out_ind != nullptr) { + *out_ind = indirect_storage; + } + + // Set the output. + *out = std::move(indirect_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreatePatchMetaStorage( + VirtualFile* out_aes_ctr_ex_meta, VirtualFile* out_indirect_meta, + VirtualFile* out_layer_info_storage, VirtualFile base_storage, s64 offset, + const NcaAesCtrUpperIv& upper_iv, const NcaPatchInfo& patch_info, + const NcaMetaDataHashDataInfo& meta_data_hash_data_info) { + // Validate preconditions. + ASSERT(out_aes_ctr_ex_meta != nullptr); + ASSERT(out_indirect_meta != nullptr); + ASSERT(base_storage != nullptr); + ASSERT(patch_info.HasAesCtrExTable()); + ASSERT(patch_info.HasIndirectTable()); + ASSERT(Common::IsAligned<s64>(patch_info.aes_ctr_ex_size, NcaHeader::XtsBlockSize)); + + // Validate patch info extents. + R_UNLESS(patch_info.indirect_size > 0, ResultInvalidNcaPatchInfoIndirectSize); + R_UNLESS(patch_info.aes_ctr_ex_size >= 0, ResultInvalidNcaPatchInfoAesCtrExSize); + R_UNLESS(patch_info.indirect_size + patch_info.indirect_offset <= patch_info.aes_ctr_ex_offset, + ResultInvalidNcaPatchInfoAesCtrExOffset); + R_UNLESS(patch_info.aes_ctr_ex_offset + patch_info.aes_ctr_ex_size <= + meta_data_hash_data_info.offset, + ResultRomNcaInvalidPatchMetaDataHashDataOffset); + + // Get the base storage size. + s64 base_size = base_storage->GetSize(); + + // Check that extents remain within range. + R_UNLESS(patch_info.indirect_offset + patch_info.indirect_size <= base_size, + ResultNcaBaseStorageOutOfRangeE); + R_UNLESS(patch_info.aes_ctr_ex_offset + patch_info.aes_ctr_ex_size <= base_size, + ResultNcaBaseStorageOutOfRangeB); + + // Check that metadata hash data extents remain within range. + const s64 meta_data_hash_data_offset = meta_data_hash_data_info.offset; + const s64 meta_data_hash_data_size = + Common::AlignUp<s64>(meta_data_hash_data_info.size, NcaHeader::CtrBlockSize); + R_UNLESS(meta_data_hash_data_offset + meta_data_hash_data_size <= base_size, + ResultNcaBaseStorageOutOfRangeB); + + // Create the encrypted storage. + auto enc_storage = std::make_shared<OffsetVfsFile>( + std::move(base_storage), + meta_data_hash_data_offset + meta_data_hash_data_size - patch_info.indirect_offset, + patch_info.indirect_offset); + R_UNLESS(enc_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Create the decrypted storage. + VirtualFile decrypted_storage; + R_TRY(this->CreateAesCtrStorage(std::addressof(decrypted_storage), std::move(enc_storage), + offset + patch_info.indirect_offset, upper_iv, + AlignmentStorageRequirement::None)); + + // Create the verification storage. + VirtualFile integrity_storage; + Result rc = this->CreateIntegrityVerificationStorageForMeta( + std::addressof(integrity_storage), out_layer_info_storage, std::move(decrypted_storage), + patch_info.indirect_offset, meta_data_hash_data_info); + if (rc == ResultInvalidNcaMetaDataHashDataSize) { + R_THROW(ResultRomNcaInvalidPatchMetaDataHashDataSize); + } + if (rc == ResultInvalidNcaMetaDataHashDataHash) { + R_THROW(ResultRomNcaInvalidPatchMetaDataHashDataHash); + } + R_TRY(rc); + + // Create the indirect meta storage. + auto indirect_meta_storage = + std::make_shared<OffsetVfsFile>(integrity_storage, patch_info.indirect_size, + patch_info.indirect_offset - patch_info.indirect_offset); + R_UNLESS(indirect_meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Create the aes ctr ex meta storage. + auto aes_ctr_ex_meta_storage = + std::make_shared<OffsetVfsFile>(integrity_storage, patch_info.aes_ctr_ex_size, + patch_info.aes_ctr_ex_offset - patch_info.indirect_offset); + R_UNLESS(aes_ctr_ex_meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Set the output. + *out_aes_ctr_ex_meta = std::move(aes_ctr_ex_meta_storage); + *out_indirect_meta = std::move(indirect_meta_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateSha256Storage( + VirtualFile* out, VirtualFile base_storage, + const NcaFsHeader::HashData::HierarchicalSha256Data& hash_data) { + // Validate preconditions. + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + + // Define storage types. + using VerificationStorage = HierarchicalSha256Storage; + + // Validate the hash data. + R_UNLESS(Common::IsPowerOfTwo(hash_data.hash_block_size), + ResultInvalidHierarchicalSha256BlockSize); + R_UNLESS(hash_data.hash_layer_count == VerificationStorage::LayerCount - 1, + ResultInvalidHierarchicalSha256LayerCount); + + // Get the regions. + const auto& hash_region = hash_data.hash_layer_region[0]; + const auto& data_region = hash_data.hash_layer_region[1]; + + // Determine buffer sizes. + constexpr s32 CacheBlockCount = 2; + const auto hash_buffer_size = static_cast<size_t>(hash_region.size); + const auto cache_buffer_size = CacheBlockCount * hash_data.hash_block_size; + const auto total_buffer_size = hash_buffer_size + cache_buffer_size; + + // Make a buffer holder storage. + auto buffer_hold_storage = std::make_shared<MemoryResourceBufferHoldStorage>( + std::move(base_storage), total_buffer_size); + R_UNLESS(buffer_hold_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + R_UNLESS(buffer_hold_storage->IsValid(), ResultAllocationMemoryFailedInNcaFileSystemDriverI); + + // Get storage size. + s64 base_size = buffer_hold_storage->GetSize(); + + // Check that we're within range. + R_UNLESS(hash_region.offset + hash_region.size <= base_size, ResultNcaBaseStorageOutOfRangeC); + R_UNLESS(data_region.offset + data_region.size <= base_size, ResultNcaBaseStorageOutOfRangeC); + + // Create the master hash storage. + auto master_hash_storage = + std::make_shared<ArrayVfsFile<sizeof(Hash)>>(hash_data.fs_data_master_hash.value); + + // Make the verification storage. + auto verification_storage = std::make_shared<VerificationStorage>(); + R_UNLESS(verification_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Make layer storages. + std::array<VirtualFile, VerificationStorage::LayerCount> layer_storages{ + std::make_shared<OffsetVfsFile>(master_hash_storage, sizeof(Hash), 0), + std::make_shared<OffsetVfsFile>(buffer_hold_storage, hash_region.size, hash_region.offset), + std::make_shared<OffsetVfsFile>(buffer_hold_storage, data_region.size, data_region.offset), + }; + + // Initialize the verification storage. + R_TRY(verification_storage->Initialize(layer_storages.data(), VerificationStorage::LayerCount, + hash_data.hash_block_size, + buffer_hold_storage->GetBuffer(), hash_buffer_size)); + + // Set the output. + *out = std::move(verification_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateIntegrityVerificationStorage( + VirtualFile* out, VirtualFile base_storage, + const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info) { + R_RETURN(this->CreateIntegrityVerificationStorageImpl( + out, base_storage, meta_info, 0, IntegrityDataCacheCount, IntegrityHashCacheCount, + HierarchicalIntegrityVerificationStorage::GetDefaultDataCacheBufferLevel( + meta_info.level_hash_info.max_layers))); +} + +Result NcaFileSystemDriver::CreateIntegrityVerificationStorageForMeta( + VirtualFile* out, VirtualFile* out_layer_info_storage, VirtualFile base_storage, s64 offset, + const NcaMetaDataHashDataInfo& meta_data_hash_data_info) { + // Validate preconditions. + ASSERT(out != nullptr); + + // Check the meta data hash data size. + R_UNLESS(meta_data_hash_data_info.size == sizeof(NcaMetaDataHashData), + ResultInvalidNcaMetaDataHashDataSize); + + // Read the meta data hash data. + NcaMetaDataHashData meta_data_hash_data; + base_storage->ReadObject(std::addressof(meta_data_hash_data), + meta_data_hash_data_info.offset - offset); + + // Set the out layer info storage, if necessary. + if (out_layer_info_storage != nullptr) { + auto layer_info_storage = std::make_shared<OffsetVfsFile>( + base_storage, + meta_data_hash_data_info.offset + meta_data_hash_data_info.size - + meta_data_hash_data.layer_info_offset, + meta_data_hash_data.layer_info_offset - offset); + R_UNLESS(layer_info_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + *out_layer_info_storage = std::move(layer_info_storage); + } + + // Create the meta storage. + auto meta_storage = std::make_shared<OffsetVfsFile>( + std::move(base_storage), meta_data_hash_data_info.offset - offset, 0); + R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Create the integrity verification storage. + R_RETURN(this->CreateIntegrityVerificationStorageImpl( + out, std::move(meta_storage), meta_data_hash_data.integrity_meta_info, + meta_data_hash_data.layer_info_offset - offset, IntegrityDataCacheCountForMeta, + IntegrityHashCacheCountForMeta, 0)); +} + +Result NcaFileSystemDriver::CreateIntegrityVerificationStorageImpl( + VirtualFile* out, VirtualFile base_storage, + const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info, s64 layer_info_offset, + int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level) { + // Validate preconditions. + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + ASSERT(layer_info_offset >= 0); + + // Define storage types. + using VerificationStorage = HierarchicalIntegrityVerificationStorage; + using StorageInfo = VerificationStorage::HierarchicalStorageInformation; + + // Validate the meta info. + HierarchicalIntegrityVerificationInformation level_hash_info; + std::memcpy(std::addressof(level_hash_info), std::addressof(meta_info.level_hash_info), + sizeof(level_hash_info)); + + R_UNLESS(IntegrityMinLayerCount <= level_hash_info.max_layers, + ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount); + R_UNLESS(level_hash_info.max_layers <= IntegrityMaxLayerCount, + ResultInvalidNcaHierarchicalIntegrityVerificationLayerCount); + + // Get the base storage size. + s64 base_storage_size = base_storage->GetSize(); + + // Create storage info. + StorageInfo storage_info; + for (s32 i = 0; i < static_cast<s32>(level_hash_info.max_layers - 2); ++i) { + const auto& layer_info = level_hash_info.info[i]; + R_UNLESS(layer_info_offset + layer_info.offset + layer_info.size <= base_storage_size, + ResultNcaBaseStorageOutOfRangeD); + + storage_info[i + 1] = std::make_shared<OffsetVfsFile>( + base_storage, layer_info.size, layer_info_offset + layer_info.offset); + } + + // Set the last layer info. + const auto& layer_info = level_hash_info.info[level_hash_info.max_layers - 2]; + const s64 last_layer_info_offset = layer_info_offset > 0 ? 0LL : layer_info.offset.Get(); + R_UNLESS(last_layer_info_offset + layer_info.size <= base_storage_size, + ResultNcaBaseStorageOutOfRangeD); + if (layer_info_offset > 0) { + R_UNLESS(last_layer_info_offset + layer_info.size <= layer_info_offset, + ResultRomNcaInvalidIntegrityLayerInfoOffset); + } + storage_info.SetDataStorage(std::make_shared<OffsetVfsFile>( + std::move(base_storage), layer_info.size, last_layer_info_offset)); + + // Make the integrity romfs storage. + auto integrity_storage = std::make_shared<IntegrityRomFsStorage>(); + R_UNLESS(integrity_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Initialize the integrity storage. + R_TRY(integrity_storage->Initialize(level_hash_info, meta_info.master_hash, storage_info, + max_data_cache_entries, max_hash_cache_entries, + buffer_level)); + + // Set the output. + *out = std::move(integrity_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateRegionSwitchStorage(VirtualFile* out, + const NcaFsHeaderReader* header_reader, + VirtualFile inside_storage, + VirtualFile outside_storage) { + // Check pre-conditions. + ASSERT(header_reader->GetHashType() == NcaFsHeader::HashType::HierarchicalIntegrityHash); + + // Create the region. + RegionSwitchStorage::Region region = {}; + R_TRY(header_reader->GetHashTargetOffset(std::addressof(region.size))); + + // Create the region switch storage. + auto region_switch_storage = std::make_shared<RegionSwitchStorage>( + std::move(inside_storage), std::move(outside_storage), region); + R_UNLESS(region_switch_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Set the output. + *out = std::move(region_switch_storage); + R_SUCCEED(); +} + +Result NcaFileSystemDriver::CreateCompressedStorage(VirtualFile* out, + std::shared_ptr<CompressedStorage>* out_cmp, + VirtualFile* out_meta, VirtualFile base_storage, + const NcaCompressionInfo& compression_info) { + R_RETURN(this->CreateCompressedStorage(out, out_cmp, out_meta, std::move(base_storage), + compression_info, m_reader->GetDecompressor())); +} + +Result NcaFileSystemDriver::CreateCompressedStorage(VirtualFile* out, + std::shared_ptr<CompressedStorage>* out_cmp, + VirtualFile* out_meta, VirtualFile base_storage, + const NcaCompressionInfo& compression_info, + GetDecompressorFunction get_decompressor) { + // Check pre-conditions. + ASSERT(out != nullptr); + ASSERT(base_storage != nullptr); + ASSERT(get_decompressor != nullptr); + + // Read and verify the bucket tree header. + BucketTree::Header header; + std::memcpy(std::addressof(header), compression_info.bucket.header.data(), sizeof(header)); + R_TRY(header.Verify()); + + // Determine the storage extents. + const auto table_offset = compression_info.bucket.offset; + const auto table_size = compression_info.bucket.size; + const auto node_size = CompressedStorage::QueryNodeStorageSize(header.entry_count); + const auto entry_size = CompressedStorage::QueryEntryStorageSize(header.entry_count); + R_UNLESS(node_size + entry_size <= table_size, ResultInvalidCompressedStorageSize); + + // If we should, set the output meta storage. + if (out_meta != nullptr) { + auto meta_storage = std::make_shared<OffsetVfsFile>(base_storage, table_size, table_offset); + R_UNLESS(meta_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + *out_meta = std::move(meta_storage); + } + + // Allocate the compressed storage. + auto compressed_storage = std::make_shared<CompressedStorage>(); + R_UNLESS(compressed_storage != nullptr, ResultAllocationMemoryFailedAllocateShared); + + // Initialize the compressed storage. + R_TRY(compressed_storage->Initialize( + std::make_shared<OffsetVfsFile>(base_storage, table_offset, 0), + std::make_shared<OffsetVfsFile>(base_storage, node_size, table_offset), + std::make_shared<OffsetVfsFile>(base_storage, entry_size, table_offset + node_size), + header.entry_count, 64_KiB, 640_KiB, get_decompressor, 16_KiB, 16_KiB, 32)); + + // Potentially set the output compressed storage. + if (out_cmp) { + *out_cmp = compressed_storage; + } + + // Set the output. + *out = std::move(compressed_storage); + R_SUCCEED(); +} + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.h b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.h new file mode 100644 index 000000000..5771a21fc --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_nca_file_system_driver.h @@ -0,0 +1,364 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/file_sys/fssystem/fssystem_compression_common.h" +#include "core/file_sys/fssystem/fssystem_nca_header.h" +#include "core/file_sys/vfs.h" + +namespace FileSys { + +class CompressedStorage; +class AesCtrCounterExtendedStorage; +class IndirectStorage; +class SparseStorage; + +struct NcaCryptoConfiguration; + +using KeyGenerationFunction = void (*)(void* dst_key, size_t dst_key_size, const void* src_key, + size_t src_key_size, s32 key_type); +using VerifySign1Function = bool (*)(const void* sig, size_t sig_size, const void* data, + size_t data_size, u8 generation); + +struct NcaCryptoConfiguration { + static constexpr size_t Rsa2048KeyModulusSize = 2048 / 8; + static constexpr size_t Rsa2048KeyPublicExponentSize = 3; + static constexpr size_t Rsa2048KeyPrivateExponentSize = Rsa2048KeyModulusSize; + + static constexpr size_t Aes128KeySize = 128 / 8; + + static constexpr size_t Header1SignatureKeyGenerationMax = 1; + + static constexpr s32 KeyAreaEncryptionKeyIndexCount = 3; + static constexpr s32 HeaderEncryptionKeyCount = 2; + + static constexpr u8 KeyAreaEncryptionKeyIndexZeroKey = 0xFF; + + static constexpr size_t KeyGenerationMax = 32; + + std::array<const u8*, Header1SignatureKeyGenerationMax + 1> header_1_sign_key_moduli; + std::array<u8, Rsa2048KeyPublicExponentSize> header_1_sign_key_public_exponent; + std::array<std::array<u8, Aes128KeySize>, KeyAreaEncryptionKeyIndexCount> + key_area_encryption_key_source; + std::array<u8, Aes128KeySize> header_encryption_key_source; + std::array<std::array<u8, Aes128KeySize>, HeaderEncryptionKeyCount> + header_encrypted_encryption_keys; + KeyGenerationFunction generate_key; + VerifySign1Function verify_sign1; + bool is_plaintext_header_available; + bool is_available_sw_key; +}; +static_assert(std::is_trivial_v<NcaCryptoConfiguration>); + +struct NcaCompressionConfiguration { + GetDecompressorFunction get_decompressor; +}; +static_assert(std::is_trivial_v<NcaCompressionConfiguration>); + +constexpr inline s32 KeyAreaEncryptionKeyCount = + NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount * + NcaCryptoConfiguration::KeyGenerationMax; + +enum class KeyType : s32 { + ZeroKey = -2, + InvalidKey = -1, + NcaHeaderKey1 = KeyAreaEncryptionKeyCount + 0, + NcaHeaderKey2 = KeyAreaEncryptionKeyCount + 1, + NcaExternalKey = KeyAreaEncryptionKeyCount + 2, + SaveDataDeviceUniqueMac = KeyAreaEncryptionKeyCount + 3, + SaveDataSeedUniqueMac = KeyAreaEncryptionKeyCount + 4, + SaveDataTransferMac = KeyAreaEncryptionKeyCount + 5, +}; + +constexpr inline bool IsInvalidKeyTypeValue(s32 key_type) { + return key_type < 0; +} + +constexpr inline s32 GetKeyTypeValue(u8 key_index, u8 key_generation) { + if (key_index == NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexZeroKey) { + return static_cast<s32>(KeyType::ZeroKey); + } + + if (key_index >= NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount) { + return static_cast<s32>(KeyType::InvalidKey); + } + + return NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount * key_generation + key_index; +} + +class NcaReader { + YUZU_NON_COPYABLE(NcaReader); + YUZU_NON_MOVEABLE(NcaReader); + +public: + NcaReader(); + ~NcaReader(); + + Result Initialize(VirtualFile base_storage, const NcaCryptoConfiguration& crypto_cfg, + const NcaCompressionConfiguration& compression_cfg); + + VirtualFile GetSharedBodyStorage(); + u32 GetMagic() const; + NcaHeader::DistributionType GetDistributionType() const; + NcaHeader::ContentType GetContentType() const; + u8 GetHeaderSign1KeyGeneration() const; + u8 GetKeyGeneration() const; + u8 GetKeyIndex() const; + u64 GetContentSize() const; + u64 GetProgramId() const; + u32 GetContentIndex() const; + u32 GetSdkAddonVersion() const; + void GetRightsId(u8* dst, size_t dst_size) const; + bool HasFsInfo(s32 index) const; + s32 GetFsCount() const; + const Hash& GetFsHeaderHash(s32 index) const; + void GetFsHeaderHash(Hash* dst, s32 index) const; + void GetFsInfo(NcaHeader::FsInfo* dst, s32 index) const; + u64 GetFsOffset(s32 index) const; + u64 GetFsEndOffset(s32 index) const; + u64 GetFsSize(s32 index) const; + void GetEncryptedKey(void* dst, size_t size) const; + const void* GetDecryptionKey(s32 index) const; + bool HasValidInternalKey() const; + bool HasInternalDecryptionKeyForAesHw() const; + bool IsSoftwareAesPrioritized() const; + void PrioritizeSoftwareAes(); + bool IsAvailableSwKey() const; + bool HasExternalDecryptionKey() const; + const void* GetExternalDecryptionKey() const; + void SetExternalDecryptionKey(const void* src, size_t size); + void GetRawData(void* dst, size_t dst_size) const; + NcaHeader::EncryptionType GetEncryptionType() const; + Result ReadHeader(NcaFsHeader* dst, s32 index) const; + + GetDecompressorFunction GetDecompressor() const; + + bool GetHeaderSign1Valid() const; + + void GetHeaderSign2(void* dst, size_t size) const; + +private: + NcaHeader m_header; + std::array<std::array<u8, NcaCryptoConfiguration::Aes128KeySize>, + NcaHeader::DecryptionKey_Count> + m_decryption_keys; + VirtualFile m_body_storage; + VirtualFile m_header_storage; + std::array<u8, NcaCryptoConfiguration::Aes128KeySize> m_external_decryption_key; + bool m_is_software_aes_prioritized; + bool m_is_available_sw_key; + NcaHeader::EncryptionType m_header_encryption_type; + bool m_is_header_sign1_signature_valid; + GetDecompressorFunction m_get_decompressor; +}; + +class NcaFsHeaderReader { + YUZU_NON_COPYABLE(NcaFsHeaderReader); + YUZU_NON_MOVEABLE(NcaFsHeaderReader); + +public: + NcaFsHeaderReader() : m_fs_index(-1) { + std::memset(std::addressof(m_data), 0, sizeof(m_data)); + } + + Result Initialize(const NcaReader& reader, s32 index); + bool IsInitialized() const { + return m_fs_index >= 0; + } + + void GetRawData(void* dst, size_t dst_size) const; + + NcaFsHeader::HashData& GetHashData(); + const NcaFsHeader::HashData& GetHashData() const; + u16 GetVersion() const; + s32 GetFsIndex() const; + NcaFsHeader::FsType GetFsType() const; + NcaFsHeader::HashType GetHashType() const; + NcaFsHeader::EncryptionType GetEncryptionType() const; + NcaPatchInfo& GetPatchInfo(); + const NcaPatchInfo& GetPatchInfo() const; + const NcaAesCtrUpperIv GetAesCtrUpperIv() const; + + bool IsSkipLayerHashEncryption() const; + Result GetHashTargetOffset(s64* out) const; + + bool ExistsSparseLayer() const; + NcaSparseInfo& GetSparseInfo(); + const NcaSparseInfo& GetSparseInfo() const; + + bool ExistsCompressionLayer() const; + NcaCompressionInfo& GetCompressionInfo(); + const NcaCompressionInfo& GetCompressionInfo() const; + + bool ExistsPatchMetaHashLayer() const; + NcaMetaDataHashDataInfo& GetPatchMetaDataHashDataInfo(); + const NcaMetaDataHashDataInfo& GetPatchMetaDataHashDataInfo() const; + NcaFsHeader::MetaDataHashType GetPatchMetaHashType() const; + + bool ExistsSparseMetaHashLayer() const; + NcaMetaDataHashDataInfo& GetSparseMetaDataHashDataInfo(); + const NcaMetaDataHashDataInfo& GetSparseMetaDataHashDataInfo() const; + NcaFsHeader::MetaDataHashType GetSparseMetaHashType() const; + +private: + NcaFsHeader m_data; + s32 m_fs_index; +}; + +class NcaFileSystemDriver { + YUZU_NON_COPYABLE(NcaFileSystemDriver); + YUZU_NON_MOVEABLE(NcaFileSystemDriver); + +public: + struct StorageContext { + bool open_raw_storage; + VirtualFile body_substorage; + std::shared_ptr<SparseStorage> current_sparse_storage; + VirtualFile sparse_storage_meta_storage; + std::shared_ptr<SparseStorage> original_sparse_storage; + void* external_current_sparse_storage; + void* external_original_sparse_storage; + VirtualFile aes_ctr_ex_storage_meta_storage; + VirtualFile aes_ctr_ex_storage_data_storage; + std::shared_ptr<AesCtrCounterExtendedStorage> aes_ctr_ex_storage; + VirtualFile indirect_storage_meta_storage; + std::shared_ptr<IndirectStorage> indirect_storage; + VirtualFile fs_data_storage; + VirtualFile compressed_storage_meta_storage; + std::shared_ptr<CompressedStorage> compressed_storage; + + VirtualFile patch_layer_info_storage; + VirtualFile sparse_layer_info_storage; + + VirtualFile external_original_storage; + }; + +private: + enum class AlignmentStorageRequirement { + CacheBlockSize = 0, + None = 1, + }; + +public: + static Result SetupFsHeaderReader(NcaFsHeaderReader* out, const NcaReader& reader, + s32 fs_index); + +public: + NcaFileSystemDriver(std::shared_ptr<NcaReader> reader) : m_original_reader(), m_reader(reader) { + ASSERT(m_reader != nullptr); + } + + NcaFileSystemDriver(std::shared_ptr<NcaReader> original_reader, + std::shared_ptr<NcaReader> reader) + : m_original_reader(original_reader), m_reader(reader) { + ASSERT(m_reader != nullptr); + } + + Result OpenStorageWithContext(VirtualFile* out, NcaFsHeaderReader* out_header_reader, + s32 fs_index, StorageContext* ctx); + + Result OpenStorage(VirtualFile* out, NcaFsHeaderReader* out_header_reader, s32 fs_index) { + // Create a storage context. + StorageContext ctx{}; + + // Open the storage. + R_RETURN(OpenStorageWithContext(out, out_header_reader, fs_index, std::addressof(ctx))); + } + +public: + Result CreateStorageByRawStorage(VirtualFile* out, const NcaFsHeaderReader* header_reader, + VirtualFile raw_storage, StorageContext* ctx); + +private: + Result OpenStorageImpl(VirtualFile* out, NcaFsHeaderReader* out_header_reader, s32 fs_index, + StorageContext* ctx); + + Result OpenIndirectableStorageAsOriginal(VirtualFile* out, + const NcaFsHeaderReader* header_reader, + StorageContext* ctx); + + Result CreateBodySubStorage(VirtualFile* out, s64 offset, s64 size); + + Result CreateAesCtrStorage(VirtualFile* out, VirtualFile base_storage, s64 offset, + const NcaAesCtrUpperIv& upper_iv, + AlignmentStorageRequirement alignment_storage_requirement); + Result CreateAesXtsStorage(VirtualFile* out, VirtualFile base_storage, s64 offset); + + Result CreateSparseStorageMetaStorage(VirtualFile* out, VirtualFile base_storage, s64 offset, + const NcaAesCtrUpperIv& upper_iv, + const NcaSparseInfo& sparse_info); + Result CreateSparseStorageCore(std::shared_ptr<SparseStorage>* out, VirtualFile base_storage, + s64 base_size, VirtualFile meta_storage, + const NcaSparseInfo& sparse_info, bool external_info); + Result CreateSparseStorage(VirtualFile* out, s64* out_fs_data_offset, + std::shared_ptr<SparseStorage>* out_sparse_storage, + VirtualFile* out_meta_storage, s32 index, + const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info); + + Result CreateSparseStorageMetaStorageWithVerification( + VirtualFile* out, VirtualFile* out_verification, VirtualFile base_storage, s64 offset, + const NcaAesCtrUpperIv& upper_iv, const NcaSparseInfo& sparse_info, + const NcaMetaDataHashDataInfo& meta_data_hash_data_info); + Result CreateSparseStorageWithVerification( + VirtualFile* out, s64* out_fs_data_offset, + std::shared_ptr<SparseStorage>* out_sparse_storage, VirtualFile* out_meta_storage, + VirtualFile* out_verification, s32 index, const NcaAesCtrUpperIv& upper_iv, + const NcaSparseInfo& sparse_info, const NcaMetaDataHashDataInfo& meta_data_hash_data_info, + NcaFsHeader::MetaDataHashType meta_data_hash_type); + + Result CreateAesCtrExStorageMetaStorage(VirtualFile* out, VirtualFile base_storage, s64 offset, + NcaFsHeader::EncryptionType encryption_type, + const NcaAesCtrUpperIv& upper_iv, + const NcaPatchInfo& patch_info); + Result CreateAesCtrExStorage(VirtualFile* out, + std::shared_ptr<AesCtrCounterExtendedStorage>* out_ext, + VirtualFile base_storage, VirtualFile meta_storage, + s64 counter_offset, const NcaAesCtrUpperIv& upper_iv, + const NcaPatchInfo& patch_info); + + Result CreateIndirectStorageMetaStorage(VirtualFile* out, VirtualFile base_storage, + const NcaPatchInfo& patch_info); + Result CreateIndirectStorage(VirtualFile* out, std::shared_ptr<IndirectStorage>* out_ind, + VirtualFile base_storage, VirtualFile original_data_storage, + VirtualFile meta_storage, const NcaPatchInfo& patch_info); + + Result CreatePatchMetaStorage(VirtualFile* out_aes_ctr_ex_meta, VirtualFile* out_indirect_meta, + VirtualFile* out_verification, VirtualFile base_storage, + s64 offset, const NcaAesCtrUpperIv& upper_iv, + const NcaPatchInfo& patch_info, + const NcaMetaDataHashDataInfo& meta_data_hash_data_info); + + Result CreateSha256Storage(VirtualFile* out, VirtualFile base_storage, + const NcaFsHeader::HashData::HierarchicalSha256Data& sha256_data); + + Result CreateIntegrityVerificationStorage( + VirtualFile* out, VirtualFile base_storage, + const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info); + Result CreateIntegrityVerificationStorageForMeta( + VirtualFile* out, VirtualFile* out_verification, VirtualFile base_storage, s64 offset, + const NcaMetaDataHashDataInfo& meta_data_hash_data_info); + Result CreateIntegrityVerificationStorageImpl( + VirtualFile* out, VirtualFile base_storage, + const NcaFsHeader::HashData::IntegrityMetaInfo& meta_info, s64 layer_info_offset, + int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level); + + Result CreateRegionSwitchStorage(VirtualFile* out, const NcaFsHeaderReader* header_reader, + VirtualFile inside_storage, VirtualFile outside_storage); + + Result CreateCompressedStorage(VirtualFile* out, std::shared_ptr<CompressedStorage>* out_cmp, + VirtualFile* out_meta, VirtualFile base_storage, + const NcaCompressionInfo& compression_info); + +public: + Result CreateCompressedStorage(VirtualFile* out, std::shared_ptr<CompressedStorage>* out_cmp, + VirtualFile* out_meta, VirtualFile base_storage, + const NcaCompressionInfo& compression_info, + GetDecompressorFunction get_decompressor); + +private: + std::shared_ptr<NcaReader> m_original_reader; + std::shared_ptr<NcaReader> m_reader; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_nca_header.cpp b/src/core/file_sys/fssystem/fssystem_nca_header.cpp new file mode 100644 index 000000000..bf5742d39 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_nca_header.cpp @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/file_sys/fssystem/fssystem_nca_header.h" + +namespace FileSys { + +u8 NcaHeader::GetProperKeyGeneration() const { + return std::max(this->key_generation, this->key_generation_2); +} + +bool NcaPatchInfo::HasIndirectTable() const { + return this->indirect_size != 0; +} + +bool NcaPatchInfo::HasAesCtrExTable() const { + return this->aes_ctr_ex_size != 0; +} + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_nca_header.h b/src/core/file_sys/fssystem/fssystem_nca_header.h new file mode 100644 index 000000000..a02c5d881 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_nca_header.h @@ -0,0 +1,338 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/literals.h" + +#include "core/file_sys/errors.h" +#include "core/file_sys/fssystem/fs_types.h" + +namespace FileSys { + +using namespace Common::Literals; + +struct Hash { + static constexpr std::size_t Size = 256 / 8; + std::array<u8, Size> value; +}; +static_assert(sizeof(Hash) == Hash::Size); +static_assert(std::is_trivial_v<Hash>); + +using NcaDigest = Hash; + +struct NcaHeader { + enum class ContentType : u8 { + Program = 0, + Meta = 1, + Control = 2, + Manual = 3, + Data = 4, + PublicData = 5, + + Start = Program, + End = PublicData, + }; + + enum class DistributionType : u8 { + Download = 0, + GameCard = 1, + + Start = Download, + End = GameCard, + }; + + enum class EncryptionType : u8 { + Auto = 0, + None = 1, + }; + + enum DecryptionKey { + DecryptionKey_AesXts = 0, + DecryptionKey_AesXts1 = DecryptionKey_AesXts, + DecryptionKey_AesXts2 = 1, + DecryptionKey_AesCtr = 2, + DecryptionKey_AesCtrEx = 3, + DecryptionKey_AesCtrHw = 4, + DecryptionKey_Count, + }; + + struct FsInfo { + u32 start_sector; + u32 end_sector; + u32 hash_sectors; + u32 reserved; + }; + static_assert(sizeof(FsInfo) == 0x10); + static_assert(std::is_trivial_v<FsInfo>); + + static constexpr u32 Magic0 = Common::MakeMagic('N', 'C', 'A', '0'); + static constexpr u32 Magic1 = Common::MakeMagic('N', 'C', 'A', '1'); + static constexpr u32 Magic2 = Common::MakeMagic('N', 'C', 'A', '2'); + static constexpr u32 Magic3 = Common::MakeMagic('N', 'C', 'A', '3'); + + static constexpr u32 Magic = Magic3; + + static constexpr std::size_t Size = 1_KiB; + static constexpr s32 FsCountMax = 4; + static constexpr std::size_t HeaderSignCount = 2; + static constexpr std::size_t HeaderSignSize = 0x100; + static constexpr std::size_t EncryptedKeyAreaSize = 0x100; + static constexpr std::size_t SectorSize = 0x200; + static constexpr std::size_t SectorShift = 9; + static constexpr std::size_t RightsIdSize = 0x10; + static constexpr std::size_t XtsBlockSize = 0x200; + static constexpr std::size_t CtrBlockSize = 0x10; + + static_assert(SectorSize == (1 << SectorShift)); + + // Data members. + std::array<u8, HeaderSignSize> header_sign_1; + std::array<u8, HeaderSignSize> header_sign_2; + u32 magic; + DistributionType distribution_type; + ContentType content_type; + u8 key_generation; + u8 key_index; + u64 content_size; + u64 program_id; + u32 content_index; + u32 sdk_addon_version; + u8 key_generation_2; + u8 header1_signature_key_generation; + std::array<u8, 2> reserved_222; + std::array<u32, 3> reserved_224; + std::array<u8, RightsIdSize> rights_id; + std::array<FsInfo, FsCountMax> fs_info; + std::array<Hash, FsCountMax> fs_header_hash; + std::array<u8, EncryptedKeyAreaSize> encrypted_key_area; + + static constexpr u64 SectorToByte(u32 sector) { + return static_cast<u64>(sector) << SectorShift; + } + + static constexpr u32 ByteToSector(u64 byte) { + return static_cast<u32>(byte >> SectorShift); + } + + u8 GetProperKeyGeneration() const; +}; +static_assert(sizeof(NcaHeader) == NcaHeader::Size); +static_assert(std::is_trivial_v<NcaHeader>); + +struct NcaBucketInfo { + static constexpr size_t HeaderSize = 0x10; + Int64 offset; + Int64 size; + std::array<u8, HeaderSize> header; +}; +static_assert(std::is_trivial_v<NcaBucketInfo>); + +struct NcaPatchInfo { + static constexpr size_t Size = 0x40; + static constexpr size_t Offset = 0x100; + + Int64 indirect_offset; + Int64 indirect_size; + std::array<u8, NcaBucketInfo::HeaderSize> indirect_header; + Int64 aes_ctr_ex_offset; + Int64 aes_ctr_ex_size; + std::array<u8, NcaBucketInfo::HeaderSize> aes_ctr_ex_header; + + bool HasIndirectTable() const; + bool HasAesCtrExTable() const; +}; +static_assert(std::is_trivial_v<NcaPatchInfo>); + +union NcaAesCtrUpperIv { + u64 value; + struct { + u32 generation; + u32 secure_value; + } part; +}; +static_assert(std::is_trivial_v<NcaAesCtrUpperIv>); + +struct NcaSparseInfo { + NcaBucketInfo bucket; + Int64 physical_offset; + u16 generation; + std::array<u8, 6> reserved; + + s64 GetPhysicalSize() const { + return this->bucket.offset + this->bucket.size; + } + + u32 GetGeneration() const { + return static_cast<u32>(this->generation) << 16; + } + + const NcaAesCtrUpperIv MakeAesCtrUpperIv(NcaAesCtrUpperIv upper_iv) const { + NcaAesCtrUpperIv sparse_upper_iv = upper_iv; + sparse_upper_iv.part.generation = this->GetGeneration(); + return sparse_upper_iv; + } +}; +static_assert(std::is_trivial_v<NcaSparseInfo>); + +struct NcaCompressionInfo { + NcaBucketInfo bucket; + std::array<u8, 8> resreved; +}; +static_assert(std::is_trivial_v<NcaCompressionInfo>); + +struct NcaMetaDataHashDataInfo { + Int64 offset; + Int64 size; + Hash hash; +}; +static_assert(std::is_trivial_v<NcaMetaDataHashDataInfo>); + +struct NcaFsHeader { + static constexpr size_t Size = 0x200; + static constexpr size_t HashDataOffset = 0x8; + + struct Region { + Int64 offset; + Int64 size; + }; + static_assert(std::is_trivial_v<Region>); + + enum class FsType : u8 { + RomFs = 0, + PartitionFs = 1, + }; + + enum class EncryptionType : u8 { + Auto = 0, + None = 1, + AesXts = 2, + AesCtr = 3, + AesCtrEx = 4, + AesCtrSkipLayerHash = 5, + AesCtrExSkipLayerHash = 6, + }; + + enum class HashType : u8 { + Auto = 0, + None = 1, + HierarchicalSha256Hash = 2, + HierarchicalIntegrityHash = 3, + AutoSha3 = 4, + HierarchicalSha3256Hash = 5, + HierarchicalIntegritySha3Hash = 6, + }; + + enum class MetaDataHashType : u8 { + None = 0, + HierarchicalIntegrity = 1, + }; + + union HashData { + struct HierarchicalSha256Data { + static constexpr size_t HashLayerCountMax = 5; + static const size_t MasterHashOffset; + + Hash fs_data_master_hash; + s32 hash_block_size; + s32 hash_layer_count; + std::array<Region, HashLayerCountMax> hash_layer_region; + } hierarchical_sha256_data; + static_assert(std::is_trivial_v<HierarchicalSha256Data>); + + struct IntegrityMetaInfo { + static const size_t MasterHashOffset; + + u32 magic; + u32 version; + u32 master_hash_size; + + struct LevelHashInfo { + u32 max_layers; + + struct HierarchicalIntegrityVerificationLevelInformation { + static constexpr size_t IntegrityMaxLayerCount = 7; + Int64 offset; + Int64 size; + s32 block_order; + std::array<u8, 4> reserved; + }; + std::array< + HierarchicalIntegrityVerificationLevelInformation, + HierarchicalIntegrityVerificationLevelInformation::IntegrityMaxLayerCount - 1> + info; + + struct SignatureSalt { + static constexpr size_t Size = 0x20; + std::array<u8, Size> value; + }; + SignatureSalt seed; + } level_hash_info; + + Hash master_hash; + } integrity_meta_info; + static_assert(std::is_trivial_v<IntegrityMetaInfo>); + + std::array<u8, NcaPatchInfo::Offset - HashDataOffset> padding; + }; + + u16 version; + FsType fs_type; + HashType hash_type; + EncryptionType encryption_type; + MetaDataHashType meta_data_hash_type; + std::array<u8, 2> reserved; + HashData hash_data; + NcaPatchInfo patch_info; + NcaAesCtrUpperIv aes_ctr_upper_iv; + NcaSparseInfo sparse_info; + NcaCompressionInfo compression_info; + NcaMetaDataHashDataInfo meta_data_hash_data_info; + std::array<u8, 0x30> pad; + + bool IsSkipLayerHashEncryption() const { + return this->encryption_type == EncryptionType::AesCtrSkipLayerHash || + this->encryption_type == EncryptionType::AesCtrExSkipLayerHash; + } + + Result GetHashTargetOffset(s64* out) const { + switch (this->hash_type) { + case HashType::HierarchicalIntegrityHash: + case HashType::HierarchicalIntegritySha3Hash: + *out = this->hash_data.integrity_meta_info.level_hash_info + .info[this->hash_data.integrity_meta_info.level_hash_info.max_layers - 2] + .offset; + R_SUCCEED(); + case HashType::HierarchicalSha256Hash: + case HashType::HierarchicalSha3256Hash: + *out = + this->hash_data.hierarchical_sha256_data + .hash_layer_region[this->hash_data.hierarchical_sha256_data.hash_layer_count - + 1] + .offset; + R_SUCCEED(); + default: + R_THROW(ResultInvalidNcaFsHeader); + } + } +}; +static_assert(sizeof(NcaFsHeader) == NcaFsHeader::Size); +static_assert(std::is_trivial_v<NcaFsHeader>); +static_assert(offsetof(NcaFsHeader, patch_info) == NcaPatchInfo::Offset); + +inline constexpr const size_t NcaFsHeader::HashData::HierarchicalSha256Data::MasterHashOffset = + offsetof(NcaFsHeader, hash_data.hierarchical_sha256_data.fs_data_master_hash); +inline constexpr const size_t NcaFsHeader::HashData::IntegrityMetaInfo::MasterHashOffset = + offsetof(NcaFsHeader, hash_data.integrity_meta_info.master_hash); + +struct NcaMetaDataHashData { + s64 layer_info_offset; + NcaFsHeader::HashData::IntegrityMetaInfo integrity_meta_info; +}; +static_assert(sizeof(NcaMetaDataHashData) == + sizeof(NcaFsHeader::HashData::IntegrityMetaInfo) + sizeof(s64)); +static_assert(std::is_trivial_v<NcaMetaDataHashData>); + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_nca_reader.cpp b/src/core/file_sys/fssystem/fssystem_nca_reader.cpp new file mode 100644 index 000000000..a3714ab37 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_nca_reader.cpp @@ -0,0 +1,531 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/file_sys/fssystem/fssystem_aes_xts_storage.h" +#include "core/file_sys/fssystem/fssystem_nca_file_system_driver.h" +#include "core/file_sys/vfs_offset.h" + +namespace FileSys { + +namespace { + +constexpr inline u32 SdkAddonVersionMin = 0x000B0000; +constexpr inline size_t Aes128KeySize = 0x10; +constexpr const std::array<u8, Aes128KeySize> ZeroKey{}; + +constexpr Result CheckNcaMagic(u32 magic) { + // Verify the magic is not a deprecated one. + R_UNLESS(magic != NcaHeader::Magic0, ResultUnsupportedSdkVersion); + R_UNLESS(magic != NcaHeader::Magic1, ResultUnsupportedSdkVersion); + R_UNLESS(magic != NcaHeader::Magic2, ResultUnsupportedSdkVersion); + + // Verify the magic is the current one. + R_UNLESS(magic == NcaHeader::Magic3, ResultInvalidNcaSignature); + + R_SUCCEED(); +} + +} // namespace + +NcaReader::NcaReader() + : m_body_storage(), m_header_storage(), m_is_software_aes_prioritized(false), + m_is_available_sw_key(false), m_header_encryption_type(NcaHeader::EncryptionType::Auto), + m_get_decompressor() { + std::memset(std::addressof(m_header), 0, sizeof(m_header)); + std::memset(std::addressof(m_decryption_keys), 0, sizeof(m_decryption_keys)); + std::memset(std::addressof(m_external_decryption_key), 0, sizeof(m_external_decryption_key)); +} + +NcaReader::~NcaReader() {} + +Result NcaReader::Initialize(VirtualFile base_storage, const NcaCryptoConfiguration& crypto_cfg, + const NcaCompressionConfiguration& compression_cfg) { + // Validate preconditions. + ASSERT(base_storage != nullptr); + ASSERT(m_body_storage == nullptr); + + // Create the work header storage storage. + VirtualFile work_header_storage; + + // We need to be able to generate keys. + R_UNLESS(crypto_cfg.generate_key != nullptr, ResultInvalidArgument); + + // Generate keys for header. + using AesXtsStorageForNcaHeader = AesXtsStorage; + + constexpr std::array<s32, NcaCryptoConfiguration::HeaderEncryptionKeyCount> + HeaderKeyTypeValues = { + static_cast<s32>(KeyType::NcaHeaderKey1), + static_cast<s32>(KeyType::NcaHeaderKey2), + }; + + std::array<std::array<u8, NcaCryptoConfiguration::Aes128KeySize>, + NcaCryptoConfiguration::HeaderEncryptionKeyCount> + header_decryption_keys; + for (size_t i = 0; i < NcaCryptoConfiguration::HeaderEncryptionKeyCount; i++) { + crypto_cfg.generate_key(header_decryption_keys[i].data(), + AesXtsStorageForNcaHeader::KeySize, + crypto_cfg.header_encrypted_encryption_keys[i].data(), + AesXtsStorageForNcaHeader::KeySize, HeaderKeyTypeValues[i]); + } + + // Create the header storage. + std::array<u8, AesXtsStorageForNcaHeader::IvSize> header_iv = {}; + work_header_storage = std::make_unique<AesXtsStorageForNcaHeader>( + base_storage, header_decryption_keys[0].data(), header_decryption_keys[1].data(), + AesXtsStorageForNcaHeader::KeySize, header_iv.data(), AesXtsStorageForNcaHeader::IvSize, + NcaHeader::XtsBlockSize); + + // Check that we successfully created the storage. + R_UNLESS(work_header_storage != nullptr, ResultAllocationMemoryFailedInNcaReaderA); + + // Read the header. + work_header_storage->ReadObject(std::addressof(m_header), 0); + + // Validate the magic. + if (const Result magic_result = CheckNcaMagic(m_header.magic); R_FAILED(magic_result)) { + // Try to use a plaintext header. + base_storage->ReadObject(std::addressof(m_header), 0); + R_UNLESS(R_SUCCEEDED(CheckNcaMagic(m_header.magic)), magic_result); + + // Configure to use the plaintext header. + auto base_storage_size = base_storage->GetSize(); + work_header_storage = std::make_shared<OffsetVfsFile>(base_storage, base_storage_size, 0); + R_UNLESS(work_header_storage != nullptr, ResultAllocationMemoryFailedInNcaReaderA); + + // Set encryption type as plaintext. + m_header_encryption_type = NcaHeader::EncryptionType::None; + } + + // Verify the header sign1. + if (crypto_cfg.verify_sign1 != nullptr) { + const u8* sig = m_header.header_sign_1.data(); + const size_t sig_size = NcaHeader::HeaderSignSize; + const u8* msg = + static_cast<const u8*>(static_cast<const void*>(std::addressof(m_header.magic))); + const size_t msg_size = + NcaHeader::Size - NcaHeader::HeaderSignSize * NcaHeader::HeaderSignCount; + + m_is_header_sign1_signature_valid = crypto_cfg.verify_sign1( + sig, sig_size, msg, msg_size, m_header.header1_signature_key_generation); + + if (!m_is_header_sign1_signature_valid) { + LOG_WARNING(Common_Filesystem, "Invalid NCA header sign1"); + } + } + + // Validate the sdk version. + R_UNLESS(m_header.sdk_addon_version >= SdkAddonVersionMin, ResultUnsupportedSdkVersion); + + // Validate the key index. + R_UNLESS(m_header.key_index < NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount || + m_header.key_index == NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexZeroKey, + ResultInvalidNcaKeyIndex); + + // Check if we have a rights id. + constexpr const std::array<u8, NcaHeader::RightsIdSize> ZeroRightsId{}; + if (std::memcmp(ZeroRightsId.data(), m_header.rights_id.data(), NcaHeader::RightsIdSize) == 0) { + // If we don't, then we don't have an external key, so we need to generate decryption keys. + crypto_cfg.generate_key( + m_decryption_keys[NcaHeader::DecryptionKey_AesCtr].data(), Aes128KeySize, + m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesCtr * Aes128KeySize, + Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration())); + crypto_cfg.generate_key( + m_decryption_keys[NcaHeader::DecryptionKey_AesXts1].data(), Aes128KeySize, + m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesXts1 * Aes128KeySize, + Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration())); + crypto_cfg.generate_key( + m_decryption_keys[NcaHeader::DecryptionKey_AesXts2].data(), Aes128KeySize, + m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesXts2 * Aes128KeySize, + Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration())); + crypto_cfg.generate_key( + m_decryption_keys[NcaHeader::DecryptionKey_AesCtrEx].data(), Aes128KeySize, + m_header.encrypted_key_area.data() + NcaHeader::DecryptionKey_AesCtrEx * Aes128KeySize, + Aes128KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration())); + + // Copy the hardware speed emulation key. + std::memcpy(m_decryption_keys[NcaHeader::DecryptionKey_AesCtrHw].data(), + m_header.encrypted_key_area.data() + + NcaHeader::DecryptionKey_AesCtrHw * Aes128KeySize, + Aes128KeySize); + } + + // Clear the external decryption key. + std::memset(m_external_decryption_key.data(), 0, m_external_decryption_key.size()); + + // Set software key availability. + m_is_available_sw_key = crypto_cfg.is_available_sw_key; + + // Set our decompressor function getter. + m_get_decompressor = compression_cfg.get_decompressor; + + // Set our storages. + m_header_storage = std::move(work_header_storage); + m_body_storage = std::move(base_storage); + + R_SUCCEED(); +} + +VirtualFile NcaReader::GetSharedBodyStorage() { + ASSERT(m_body_storage != nullptr); + return m_body_storage; +} + +u32 NcaReader::GetMagic() const { + ASSERT(m_body_storage != nullptr); + return m_header.magic; +} + +NcaHeader::DistributionType NcaReader::GetDistributionType() const { + ASSERT(m_body_storage != nullptr); + return m_header.distribution_type; +} + +NcaHeader::ContentType NcaReader::GetContentType() const { + ASSERT(m_body_storage != nullptr); + return m_header.content_type; +} + +u8 NcaReader::GetHeaderSign1KeyGeneration() const { + ASSERT(m_body_storage != nullptr); + return m_header.header1_signature_key_generation; +} + +u8 NcaReader::GetKeyGeneration() const { + ASSERT(m_body_storage != nullptr); + return m_header.GetProperKeyGeneration(); +} + +u8 NcaReader::GetKeyIndex() const { + ASSERT(m_body_storage != nullptr); + return m_header.key_index; +} + +u64 NcaReader::GetContentSize() const { + ASSERT(m_body_storage != nullptr); + return m_header.content_size; +} + +u64 NcaReader::GetProgramId() const { + ASSERT(m_body_storage != nullptr); + return m_header.program_id; +} + +u32 NcaReader::GetContentIndex() const { + ASSERT(m_body_storage != nullptr); + return m_header.content_index; +} + +u32 NcaReader::GetSdkAddonVersion() const { + ASSERT(m_body_storage != nullptr); + return m_header.sdk_addon_version; +} + +void NcaReader::GetRightsId(u8* dst, size_t dst_size) const { + ASSERT(dst != nullptr); + ASSERT(dst_size >= NcaHeader::RightsIdSize); + + std::memcpy(dst, m_header.rights_id.data(), NcaHeader::RightsIdSize); +} + +bool NcaReader::HasFsInfo(s32 index) const { + ASSERT(0 <= index && index < NcaHeader::FsCountMax); + return m_header.fs_info[index].start_sector != 0 || m_header.fs_info[index].end_sector != 0; +} + +s32 NcaReader::GetFsCount() const { + ASSERT(m_body_storage != nullptr); + for (s32 i = 0; i < NcaHeader::FsCountMax; i++) { + if (!this->HasFsInfo(i)) { + return i; + } + } + return NcaHeader::FsCountMax; +} + +const Hash& NcaReader::GetFsHeaderHash(s32 index) const { + ASSERT(m_body_storage != nullptr); + ASSERT(0 <= index && index < NcaHeader::FsCountMax); + return m_header.fs_header_hash[index]; +} + +void NcaReader::GetFsHeaderHash(Hash* dst, s32 index) const { + ASSERT(m_body_storage != nullptr); + ASSERT(0 <= index && index < NcaHeader::FsCountMax); + ASSERT(dst != nullptr); + std::memcpy(dst, std::addressof(m_header.fs_header_hash[index]), sizeof(*dst)); +} + +void NcaReader::GetFsInfo(NcaHeader::FsInfo* dst, s32 index) const { + ASSERT(m_body_storage != nullptr); + ASSERT(0 <= index && index < NcaHeader::FsCountMax); + ASSERT(dst != nullptr); + std::memcpy(dst, std::addressof(m_header.fs_info[index]), sizeof(*dst)); +} + +u64 NcaReader::GetFsOffset(s32 index) const { + ASSERT(m_body_storage != nullptr); + ASSERT(0 <= index && index < NcaHeader::FsCountMax); + return NcaHeader::SectorToByte(m_header.fs_info[index].start_sector); +} + +u64 NcaReader::GetFsEndOffset(s32 index) const { + ASSERT(m_body_storage != nullptr); + ASSERT(0 <= index && index < NcaHeader::FsCountMax); + return NcaHeader::SectorToByte(m_header.fs_info[index].end_sector); +} + +u64 NcaReader::GetFsSize(s32 index) const { + ASSERT(m_body_storage != nullptr); + ASSERT(0 <= index && index < NcaHeader::FsCountMax); + return NcaHeader::SectorToByte(m_header.fs_info[index].end_sector - + m_header.fs_info[index].start_sector); +} + +void NcaReader::GetEncryptedKey(void* dst, size_t size) const { + ASSERT(m_body_storage != nullptr); + ASSERT(dst != nullptr); + ASSERT(size >= NcaHeader::EncryptedKeyAreaSize); + + std::memcpy(dst, m_header.encrypted_key_area.data(), NcaHeader::EncryptedKeyAreaSize); +} + +const void* NcaReader::GetDecryptionKey(s32 index) const { + ASSERT(m_body_storage != nullptr); + ASSERT(0 <= index && index < NcaHeader::DecryptionKey_Count); + return m_decryption_keys[index].data(); +} + +bool NcaReader::HasValidInternalKey() const { + for (s32 i = 0; i < NcaHeader::DecryptionKey_Count; i++) { + if (std::memcmp(ZeroKey.data(), m_header.encrypted_key_area.data() + i * Aes128KeySize, + Aes128KeySize) != 0) { + return true; + } + } + return false; +} + +bool NcaReader::HasInternalDecryptionKeyForAesHw() const { + return std::memcmp(ZeroKey.data(), this->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtrHw), + Aes128KeySize) != 0; +} + +bool NcaReader::IsSoftwareAesPrioritized() const { + return m_is_software_aes_prioritized; +} + +void NcaReader::PrioritizeSoftwareAes() { + m_is_software_aes_prioritized = true; +} + +bool NcaReader::IsAvailableSwKey() const { + return m_is_available_sw_key; +} + +bool NcaReader::HasExternalDecryptionKey() const { + return std::memcmp(ZeroKey.data(), this->GetExternalDecryptionKey(), Aes128KeySize) != 0; +} + +const void* NcaReader::GetExternalDecryptionKey() const { + return m_external_decryption_key.data(); +} + +void NcaReader::SetExternalDecryptionKey(const void* src, size_t size) { + ASSERT(src != nullptr); + ASSERT(size == sizeof(m_external_decryption_key)); + + std::memcpy(m_external_decryption_key.data(), src, sizeof(m_external_decryption_key)); +} + +void NcaReader::GetRawData(void* dst, size_t dst_size) const { + ASSERT(m_body_storage != nullptr); + ASSERT(dst != nullptr); + ASSERT(dst_size >= sizeof(NcaHeader)); + + std::memcpy(dst, std::addressof(m_header), sizeof(NcaHeader)); +} + +GetDecompressorFunction NcaReader::GetDecompressor() const { + ASSERT(m_get_decompressor != nullptr); + return m_get_decompressor; +} + +NcaHeader::EncryptionType NcaReader::GetEncryptionType() const { + return m_header_encryption_type; +} + +Result NcaReader::ReadHeader(NcaFsHeader* dst, s32 index) const { + ASSERT(dst != nullptr); + ASSERT(0 <= index && index < NcaHeader::FsCountMax); + + const s64 offset = sizeof(NcaHeader) + sizeof(NcaFsHeader) * index; + m_header_storage->ReadObject(dst, offset); + + R_SUCCEED(); +} + +bool NcaReader::GetHeaderSign1Valid() const { + return m_is_header_sign1_signature_valid; +} + +void NcaReader::GetHeaderSign2(void* dst, size_t size) const { + ASSERT(dst != nullptr); + ASSERT(size == NcaHeader::HeaderSignSize); + + std::memcpy(dst, m_header.header_sign_2.data(), size); +} + +Result NcaFsHeaderReader::Initialize(const NcaReader& reader, s32 index) { + // Reset ourselves to uninitialized. + m_fs_index = -1; + + // Read the header. + R_TRY(reader.ReadHeader(std::addressof(m_data), index)); + + // Set our index. + m_fs_index = index; + R_SUCCEED(); +} + +void NcaFsHeaderReader::GetRawData(void* dst, size_t dst_size) const { + ASSERT(this->IsInitialized()); + ASSERT(dst != nullptr); + ASSERT(dst_size >= sizeof(NcaFsHeader)); + + std::memcpy(dst, std::addressof(m_data), sizeof(NcaFsHeader)); +} + +NcaFsHeader::HashData& NcaFsHeaderReader::GetHashData() { + ASSERT(this->IsInitialized()); + return m_data.hash_data; +} + +const NcaFsHeader::HashData& NcaFsHeaderReader::GetHashData() const { + ASSERT(this->IsInitialized()); + return m_data.hash_data; +} + +u16 NcaFsHeaderReader::GetVersion() const { + ASSERT(this->IsInitialized()); + return m_data.version; +} + +s32 NcaFsHeaderReader::GetFsIndex() const { + ASSERT(this->IsInitialized()); + return m_fs_index; +} + +NcaFsHeader::FsType NcaFsHeaderReader::GetFsType() const { + ASSERT(this->IsInitialized()); + return m_data.fs_type; +} + +NcaFsHeader::HashType NcaFsHeaderReader::GetHashType() const { + ASSERT(this->IsInitialized()); + return m_data.hash_type; +} + +NcaFsHeader::EncryptionType NcaFsHeaderReader::GetEncryptionType() const { + ASSERT(this->IsInitialized()); + return m_data.encryption_type; +} + +NcaPatchInfo& NcaFsHeaderReader::GetPatchInfo() { + ASSERT(this->IsInitialized()); + return m_data.patch_info; +} + +const NcaPatchInfo& NcaFsHeaderReader::GetPatchInfo() const { + ASSERT(this->IsInitialized()); + return m_data.patch_info; +} + +const NcaAesCtrUpperIv NcaFsHeaderReader::GetAesCtrUpperIv() const { + ASSERT(this->IsInitialized()); + return m_data.aes_ctr_upper_iv; +} + +bool NcaFsHeaderReader::IsSkipLayerHashEncryption() const { + ASSERT(this->IsInitialized()); + return m_data.IsSkipLayerHashEncryption(); +} + +Result NcaFsHeaderReader::GetHashTargetOffset(s64* out) const { + ASSERT(out != nullptr); + ASSERT(this->IsInitialized()); + + R_RETURN(m_data.GetHashTargetOffset(out)); +} + +bool NcaFsHeaderReader::ExistsSparseLayer() const { + ASSERT(this->IsInitialized()); + return m_data.sparse_info.generation != 0; +} + +NcaSparseInfo& NcaFsHeaderReader::GetSparseInfo() { + ASSERT(this->IsInitialized()); + return m_data.sparse_info; +} + +const NcaSparseInfo& NcaFsHeaderReader::GetSparseInfo() const { + ASSERT(this->IsInitialized()); + return m_data.sparse_info; +} + +bool NcaFsHeaderReader::ExistsCompressionLayer() const { + ASSERT(this->IsInitialized()); + return m_data.compression_info.bucket.offset != 0 && m_data.compression_info.bucket.size != 0; +} + +NcaCompressionInfo& NcaFsHeaderReader::GetCompressionInfo() { + ASSERT(this->IsInitialized()); + return m_data.compression_info; +} + +const NcaCompressionInfo& NcaFsHeaderReader::GetCompressionInfo() const { + ASSERT(this->IsInitialized()); + return m_data.compression_info; +} + +bool NcaFsHeaderReader::ExistsPatchMetaHashLayer() const { + ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_data_info.size != 0 && this->GetPatchInfo().HasIndirectTable(); +} + +NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetPatchMetaDataHashDataInfo() { + ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_data_info; +} + +const NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetPatchMetaDataHashDataInfo() const { + ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_data_info; +} + +NcaFsHeader::MetaDataHashType NcaFsHeaderReader::GetPatchMetaHashType() const { + ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_type; +} + +bool NcaFsHeaderReader::ExistsSparseMetaHashLayer() const { + ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_data_info.size != 0 && this->ExistsSparseLayer(); +} + +NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetSparseMetaDataHashDataInfo() { + ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_data_info; +} + +const NcaMetaDataHashDataInfo& NcaFsHeaderReader::GetSparseMetaDataHashDataInfo() const { + ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_data_info; +} + +NcaFsHeader::MetaDataHashType NcaFsHeaderReader::GetSparseMetaHashType() const { + ASSERT(this->IsInitialized()); + return m_data.meta_data_hash_type; +} + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_pooled_buffer.cpp b/src/core/file_sys/fssystem/fssystem_pooled_buffer.cpp new file mode 100644 index 000000000..bbfaab255 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_pooled_buffer.cpp @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/alignment.h" +#include "core/file_sys/fssystem/fssystem_pooled_buffer.h" + +namespace FileSys { + +namespace { + +constexpr size_t HeapBlockSize = BufferPoolAlignment; +static_assert(HeapBlockSize == 4_KiB); + +// A heap block is 4KiB. An order is a power of two. +// This gives blocks of the order 32KiB, 512KiB, 4MiB. +constexpr s32 HeapOrderMax = 7; +constexpr s32 HeapOrderMaxForLarge = HeapOrderMax + 3; + +constexpr size_t HeapAllocatableSizeMax = HeapBlockSize * (static_cast<size_t>(1) << HeapOrderMax); +constexpr size_t HeapAllocatableSizeMaxForLarge = + HeapBlockSize * (static_cast<size_t>(1) << HeapOrderMaxForLarge); + +} // namespace + +size_t PooledBuffer::GetAllocatableSizeMaxCore(bool large) { + return large ? HeapAllocatableSizeMaxForLarge : HeapAllocatableSizeMax; +} + +void PooledBuffer::AllocateCore(size_t ideal_size, size_t required_size, bool large) { + // Ensure preconditions. + ASSERT(m_buffer == nullptr); + + // Check that we can allocate this size. + ASSERT(required_size <= GetAllocatableSizeMaxCore(large)); + + const size_t target_size = + std::min(std::max(ideal_size, required_size), GetAllocatableSizeMaxCore(large)); + + // Dummy implementation for allocate. + if (target_size > 0) { + m_buffer = + reinterpret_cast<char*>(::operator new(target_size, std::align_val_t{HeapBlockSize})); + m_size = target_size; + + // Ensure postconditions. + ASSERT(m_buffer != nullptr); + } +} + +void PooledBuffer::Shrink(size_t ideal_size) { + ASSERT(ideal_size <= GetAllocatableSizeMaxCore(true)); + + // Shrinking to zero means that we have no buffer. + if (ideal_size == 0) { + ::operator delete(m_buffer, std::align_val_t{HeapBlockSize}); + m_buffer = nullptr; + m_size = ideal_size; + } +} + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_pooled_buffer.h b/src/core/file_sys/fssystem/fssystem_pooled_buffer.h new file mode 100644 index 000000000..9a6adbcb5 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_pooled_buffer.h @@ -0,0 +1,95 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/literals.h" +#include "core/hle/result.h" + +namespace FileSys { + +using namespace Common::Literals; + +constexpr inline size_t BufferPoolAlignment = 4_KiB; +constexpr inline size_t BufferPoolWorkSize = 320; + +class PooledBuffer { + YUZU_NON_COPYABLE(PooledBuffer); + +public: + // Constructor/Destructor. + constexpr PooledBuffer() : m_buffer(), m_size() {} + + PooledBuffer(size_t ideal_size, size_t required_size) : m_buffer(), m_size() { + this->Allocate(ideal_size, required_size); + } + + ~PooledBuffer() { + this->Deallocate(); + } + + // Move and assignment. + explicit PooledBuffer(PooledBuffer&& rhs) : m_buffer(rhs.m_buffer), m_size(rhs.m_size) { + rhs.m_buffer = nullptr; + rhs.m_size = 0; + } + + PooledBuffer& operator=(PooledBuffer&& rhs) { + PooledBuffer(std::move(rhs)).Swap(*this); + return *this; + } + + // Allocation API. + void Allocate(size_t ideal_size, size_t required_size) { + return this->AllocateCore(ideal_size, required_size, false); + } + + void AllocateParticularlyLarge(size_t ideal_size, size_t required_size) { + return this->AllocateCore(ideal_size, required_size, true); + } + + void Shrink(size_t ideal_size); + + void Deallocate() { + // Shrink the buffer to empty. + this->Shrink(0); + ASSERT(m_buffer == nullptr); + } + + char* GetBuffer() const { + ASSERT(m_buffer != nullptr); + return m_buffer; + } + + size_t GetSize() const { + ASSERT(m_buffer != nullptr); + return m_size; + } + +public: + static size_t GetAllocatableSizeMax() { + return GetAllocatableSizeMaxCore(false); + } + static size_t GetAllocatableParticularlyLargeSizeMax() { + return GetAllocatableSizeMaxCore(true); + } + +private: + static size_t GetAllocatableSizeMaxCore(bool large); + +private: + void Swap(PooledBuffer& rhs) { + std::swap(m_buffer, rhs.m_buffer); + std::swap(m_size, rhs.m_size); + } + + void AllocateCore(size_t ideal_size, size_t required_size, bool large); + +private: + char* m_buffer; + size_t m_size; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_sparse_storage.cpp b/src/core/file_sys/fssystem/fssystem_sparse_storage.cpp new file mode 100644 index 000000000..8574a11dd --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_sparse_storage.cpp @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/file_sys/fssystem/fssystem_sparse_storage.h" + +namespace FileSys { + +size_t SparseStorage::Read(u8* buffer, size_t size, size_t offset) const { + // Validate preconditions. + ASSERT(this->IsInitialized()); + ASSERT(buffer != nullptr); + + // Allow zero size. + if (size == 0) { + return size; + } + + SparseStorage* self = const_cast<SparseStorage*>(this); + + if (self->GetEntryTable().IsEmpty()) { + BucketTree::Offsets table_offsets; + ASSERT(R_SUCCEEDED(self->GetEntryTable().GetOffsets(std::addressof(table_offsets)))); + ASSERT(table_offsets.IsInclude(offset, size)); + + std::memset(buffer, 0, size); + } else { + self->OperatePerEntry<false, true>( + offset, size, + [=](VirtualFile storage, s64 data_offset, s64 cur_offset, s64 cur_size) -> Result { + storage->Read(reinterpret_cast<u8*>(buffer) + (cur_offset - offset), + static_cast<size_t>(cur_size), data_offset); + R_SUCCEED(); + }); + } + + return size; +} + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_sparse_storage.h b/src/core/file_sys/fssystem/fssystem_sparse_storage.h new file mode 100644 index 000000000..6c196ec61 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_sparse_storage.h @@ -0,0 +1,72 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/file_sys/fssystem/fssystem_indirect_storage.h" + +namespace FileSys { + +class SparseStorage : public IndirectStorage { + YUZU_NON_COPYABLE(SparseStorage); + YUZU_NON_MOVEABLE(SparseStorage); + +private: + class ZeroStorage : public IReadOnlyStorage { + public: + ZeroStorage() {} + virtual ~ZeroStorage() {} + + virtual size_t GetSize() const override { + return std::numeric_limits<size_t>::max(); + } + + virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { + ASSERT(buffer != nullptr || size == 0); + + if (size > 0) { + std::memset(buffer, 0, size); + } + + return size; + } + }; + +public: + SparseStorage() : IndirectStorage(), m_zero_storage(std::make_shared<ZeroStorage>()) {} + virtual ~SparseStorage() {} + + using IndirectStorage::Initialize; + + void Initialize(s64 end_offset) { + this->GetEntryTable().Initialize(NodeSize, end_offset); + this->SetZeroStorage(); + } + + void SetDataStorage(VirtualFile storage) { + ASSERT(this->IsInitialized()); + + this->SetStorage(0, storage); + this->SetZeroStorage(); + } + + template <typename T> + void SetDataStorage(T storage, s64 offset, s64 size) { + ASSERT(this->IsInitialized()); + + this->SetStorage(0, storage, offset, size); + this->SetZeroStorage(); + } + + virtual size_t Read(u8* buffer, size_t size, size_t offset) const override; + +private: + void SetZeroStorage() { + return this->SetStorage(1, m_zero_storage, 0, std::numeric_limits<s64>::max()); + } + +private: + VirtualFile m_zero_storage; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_switch_storage.h b/src/core/file_sys/fssystem/fssystem_switch_storage.h new file mode 100644 index 000000000..2b43927cb --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_switch_storage.h @@ -0,0 +1,80 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/file_sys/fssystem/fs_i_storage.h" + +namespace FileSys { + +class RegionSwitchStorage : public IReadOnlyStorage { + YUZU_NON_COPYABLE(RegionSwitchStorage); + YUZU_NON_MOVEABLE(RegionSwitchStorage); + +public: + struct Region { + s64 offset; + s64 size; + }; + +public: + RegionSwitchStorage(VirtualFile&& i, VirtualFile&& o, Region r) + : m_inside_region_storage(std::move(i)), m_outside_region_storage(std::move(o)), + m_region(r) {} + + virtual size_t Read(u8* buffer, size_t size, size_t offset) const override { + // Process until we're done. + size_t processed = 0; + while (processed < size) { + // Process on the appropriate storage. + s64 cur_size = 0; + if (this->CheckRegions(std::addressof(cur_size), offset + processed, + size - processed)) { + m_inside_region_storage->Read(buffer + processed, cur_size, offset + processed); + } else { + m_outside_region_storage->Read(buffer + processed, cur_size, offset + processed); + } + + // Advance. + processed += cur_size; + } + + return size; + } + + virtual size_t GetSize() const override { + return m_inside_region_storage->GetSize(); + } + +private: + bool CheckRegions(s64* out_current_size, s64 offset, s64 size) const { + // Check if our region contains the access. + if (m_region.offset <= offset) { + if (offset < m_region.offset + m_region.size) { + if (m_region.offset + m_region.size <= offset + size) { + *out_current_size = m_region.offset + m_region.size - offset; + } else { + *out_current_size = size; + } + return true; + } else { + *out_current_size = size; + return false; + } + } else { + if (m_region.offset <= offset + size) { + *out_current_size = m_region.offset - offset; + } else { + *out_current_size = size; + } + return false; + } + } + +private: + VirtualFile m_inside_region_storage; + VirtualFile m_outside_region_storage; + Region m_region; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_utility.cpp b/src/core/file_sys/fssystem/fssystem_utility.cpp new file mode 100644 index 000000000..ceabb8ff1 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_utility.cpp @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/file_sys/fssystem/fssystem_utility.h" + +namespace FileSys { + +void AddCounter(void* counter_, size_t counter_size, u64 value) { + u8* counter = static_cast<u8*>(counter_); + u64 remaining = value; + u8 carry = 0; + + for (size_t i = 0; i < counter_size; i++) { + auto sum = counter[counter_size - 1 - i] + (remaining & 0xFF) + carry; + carry = static_cast<u8>(sum >> (sizeof(u8) * 8)); + auto sum8 = static_cast<u8>(sum & 0xFF); + + counter[counter_size - 1 - i] = sum8; + + remaining >>= (sizeof(u8) * 8); + if (carry == 0 && remaining == 0) { + break; + } + } +} + +} // namespace FileSys diff --git a/src/core/file_sys/fssystem/fssystem_utility.h b/src/core/file_sys/fssystem/fssystem_utility.h new file mode 100644 index 000000000..284b8b811 --- /dev/null +++ b/src/core/file_sys/fssystem/fssystem_utility.h @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/common_funcs.h" + +namespace FileSys { + +void AddCounter(void* counter, size_t counter_size, u64 value); + +} diff --git a/src/core/file_sys/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp index efdf18cee..7be1322cc 100644 --- a/src/core/file_sys/ips_layer.cpp +++ b/src/core/file_sys/ips_layer.cpp @@ -165,7 +165,7 @@ static std::string EscapeStringSequences(std::string in) { void IPSwitchCompiler::ParseFlag(const std::string& line) { if (StartsWith(line, "@flag offset_shift ")) { // Offset Shift Flag - offset_shift = std::stoll(line.substr(19), nullptr, 0); + offset_shift = std::strtoll(line.substr(19).c_str(), nullptr, 0); } else if (StartsWith(line, "@little-endian")) { // Set values to read as little endian is_little_endian = true; @@ -263,7 +263,7 @@ void IPSwitchCompiler::Parse() { // 11 - 8 hex digit offset + space + minimum two digit overwrite val if (patch_line.length() < 11) break; - auto offset = std::stoul(patch_line.substr(0, 8), nullptr, 16); + auto offset = std::strtoul(patch_line.substr(0, 8).c_str(), nullptr, 16); offset += static_cast<unsigned long>(offset_shift); std::vector<u8> replace; diff --git a/src/core/file_sys/nca_metadata.cpp b/src/core/file_sys/nca_metadata.cpp index 52c78020c..f4a774675 100644 --- a/src/core/file_sys/nca_metadata.cpp +++ b/src/core/file_sys/nca_metadata.cpp @@ -45,6 +45,10 @@ CNMT::CNMT(CNMTHeader header_, OptionalHeader opt_header_, CNMT::~CNMT() = default; +const CNMTHeader& CNMT::GetHeader() const { + return header; +} + u64 CNMT::GetTitleID() const { return header.title_id; } diff --git a/src/core/file_sys/nca_metadata.h b/src/core/file_sys/nca_metadata.h index c59ece010..68e463b5f 100644 --- a/src/core/file_sys/nca_metadata.h +++ b/src/core/file_sys/nca_metadata.h @@ -89,6 +89,7 @@ public: std::vector<ContentRecord> content_records_, std::vector<MetaRecord> meta_records_); ~CNMT(); + const CNMTHeader& GetHeader() const; u64 GetTitleID() const; u32 GetTitleVersion() const; TitleType GetType() const; diff --git a/src/core/file_sys/nca_patch.cpp b/src/core/file_sys/nca_patch.cpp deleted file mode 100644 index 2735d053b..000000000 --- a/src/core/file_sys/nca_patch.cpp +++ /dev/null @@ -1,217 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include <algorithm> -#include <array> -#include <cstddef> -#include <cstring> - -#include "common/assert.h" -#include "core/crypto/aes_util.h" -#include "core/file_sys/nca_patch.h" - -namespace FileSys { -namespace { -template <bool Subsection, typename BlockType, typename BucketType> -std::pair<std::size_t, std::size_t> SearchBucketEntry(u64 offset, const BlockType& block, - const BucketType& buckets) { - if constexpr (Subsection) { - const auto& last_bucket = buckets[block.number_buckets - 1]; - if (offset >= last_bucket.entries[last_bucket.number_entries].address_patch) { - return {block.number_buckets - 1, last_bucket.number_entries}; - } - } else { - ASSERT_MSG(offset <= block.size, "Offset is out of bounds in BKTR relocation block."); - } - - std::size_t bucket_id = std::count_if( - block.base_offsets.begin() + 1, block.base_offsets.begin() + block.number_buckets, - [&offset](u64 base_offset) { return base_offset <= offset; }); - - const auto& bucket = buckets[bucket_id]; - - if (bucket.number_entries == 1) { - return {bucket_id, 0}; - } - - std::size_t low = 0; - std::size_t mid = 0; - std::size_t high = bucket.number_entries - 1; - while (low <= high) { - mid = (low + high) / 2; - if (bucket.entries[mid].address_patch > offset) { - high = mid - 1; - } else { - if (mid == bucket.number_entries - 1 || - bucket.entries[mid + 1].address_patch > offset) { - return {bucket_id, mid}; - } - - low = mid + 1; - } - } - ASSERT_MSG(false, "Offset could not be found in BKTR block."); - return {0, 0}; -} -} // Anonymous namespace - -BKTR::BKTR(VirtualFile base_romfs_, VirtualFile bktr_romfs_, RelocationBlock relocation_, - std::vector<RelocationBucket> relocation_buckets_, SubsectionBlock subsection_, - std::vector<SubsectionBucket> subsection_buckets_, bool is_encrypted_, - Core::Crypto::Key128 key_, u64 base_offset_, u64 ivfc_offset_, - std::array<u8, 8> section_ctr_) - : relocation(relocation_), relocation_buckets(std::move(relocation_buckets_)), - subsection(subsection_), subsection_buckets(std::move(subsection_buckets_)), - base_romfs(std::move(base_romfs_)), bktr_romfs(std::move(bktr_romfs_)), - encrypted(is_encrypted_), key(key_), base_offset(base_offset_), ivfc_offset(ivfc_offset_), - section_ctr(section_ctr_) { - for (std::size_t i = 0; i < relocation.number_buckets - 1; ++i) { - relocation_buckets[i].entries.push_back({relocation.base_offsets[i + 1], 0, 0}); - } - - for (std::size_t i = 0; i < subsection.number_buckets - 1; ++i) { - subsection_buckets[i].entries.push_back({subsection_buckets[i + 1].entries[0].address_patch, - {0}, - subsection_buckets[i + 1].entries[0].ctr}); - } - - relocation_buckets.back().entries.push_back({relocation.size, 0, 0}); -} - -BKTR::~BKTR() = default; - -std::size_t BKTR::Read(u8* data, std::size_t length, std::size_t offset) const { - // Read out of bounds. - if (offset >= relocation.size) { - return 0; - } - - const auto relocation_entry = GetRelocationEntry(offset); - const auto section_offset = - offset - relocation_entry.address_patch + relocation_entry.address_source; - const auto bktr_read = relocation_entry.from_patch; - - const auto next_relocation = GetNextRelocationEntry(offset); - - if (offset + length > next_relocation.address_patch) { - const u64 partition = next_relocation.address_patch - offset; - return Read(data, partition, offset) + - Read(data + partition, length - partition, offset + partition); - } - - if (!bktr_read) { - ASSERT_MSG(section_offset >= ivfc_offset, "Offset calculation negative."); - return base_romfs->Read(data, length, section_offset - ivfc_offset); - } - - if (!encrypted) { - return bktr_romfs->Read(data, length, section_offset); - } - - const auto subsection_entry = GetSubsectionEntry(section_offset); - Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(key, Core::Crypto::Mode::CTR); - - // Calculate AES IV - std::array<u8, 16> iv{}; - auto subsection_ctr = subsection_entry.ctr; - auto offset_iv = section_offset + base_offset; - for (std::size_t i = 0; i < section_ctr.size(); ++i) { - iv[i] = section_ctr[0x8 - i - 1]; - } - offset_iv >>= 4; - for (std::size_t i = 0; i < sizeof(u64); ++i) { - iv[0xF - i] = static_cast<u8>(offset_iv & 0xFF); - offset_iv >>= 8; - } - for (std::size_t i = 0; i < sizeof(u32); ++i) { - iv[0x7 - i] = static_cast<u8>(subsection_ctr & 0xFF); - subsection_ctr >>= 8; - } - cipher.SetIV(iv); - - const auto next_subsection = GetNextSubsectionEntry(section_offset); - - if (section_offset + length > next_subsection.address_patch) { - const u64 partition = next_subsection.address_patch - section_offset; - return Read(data, partition, offset) + - Read(data + partition, length - partition, offset + partition); - } - - const auto block_offset = section_offset & 0xF; - if (block_offset != 0) { - auto block = bktr_romfs->ReadBytes(0x10, section_offset & ~0xF); - cipher.Transcode(block.data(), block.size(), block.data(), Core::Crypto::Op::Decrypt); - if (length + block_offset < 0x10) { - std::memcpy(data, block.data() + block_offset, std::min(length, block.size())); - return std::min(length, block.size()); - } - - const auto read = 0x10 - block_offset; - std::memcpy(data, block.data() + block_offset, read); - return read + Read(data + read, length - read, offset + read); - } - - const auto raw_read = bktr_romfs->Read(data, length, section_offset); - cipher.Transcode(data, raw_read, data, Core::Crypto::Op::Decrypt); - return raw_read; -} - -RelocationEntry BKTR::GetRelocationEntry(u64 offset) const { - const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets); - return relocation_buckets[res.first].entries[res.second]; -} - -RelocationEntry BKTR::GetNextRelocationEntry(u64 offset) const { - const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets); - const auto bucket = relocation_buckets[res.first]; - if (res.second + 1 < bucket.entries.size()) - return bucket.entries[res.second + 1]; - return relocation_buckets[res.first + 1].entries[0]; -} - -SubsectionEntry BKTR::GetSubsectionEntry(u64 offset) const { - const auto res = SearchBucketEntry<true>(offset, subsection, subsection_buckets); - return subsection_buckets[res.first].entries[res.second]; -} - -SubsectionEntry BKTR::GetNextSubsectionEntry(u64 offset) const { - const auto res = SearchBucketEntry<true>(offset, subsection, subsection_buckets); - const auto bucket = subsection_buckets[res.first]; - if (res.second + 1 < bucket.entries.size()) - return bucket.entries[res.second + 1]; - return subsection_buckets[res.first + 1].entries[0]; -} - -std::string BKTR::GetName() const { - return base_romfs->GetName(); -} - -std::size_t BKTR::GetSize() const { - return relocation.size; -} - -bool BKTR::Resize(std::size_t new_size) { - return false; -} - -VirtualDir BKTR::GetContainingDirectory() const { - return base_romfs->GetContainingDirectory(); -} - -bool BKTR::IsWritable() const { - return false; -} - -bool BKTR::IsReadable() const { - return true; -} - -std::size_t BKTR::Write(const u8* data, std::size_t length, std::size_t offset) { - return 0; -} - -bool BKTR::Rename(std::string_view name) { - return base_romfs->Rename(name); -} - -} // namespace FileSys diff --git a/src/core/file_sys/nca_patch.h b/src/core/file_sys/nca_patch.h deleted file mode 100644 index 595e3ef09..000000000 --- a/src/core/file_sys/nca_patch.h +++ /dev/null @@ -1,145 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include <array> -#include <memory> -#include <vector> - -#include "common/common_funcs.h" -#include "common/common_types.h" -#include "common/swap.h" -#include "core/crypto/key_manager.h" - -namespace FileSys { - -#pragma pack(push, 1) -struct RelocationEntry { - u64_le address_patch; - u64_le address_source; - u32 from_patch; -}; -#pragma pack(pop) -static_assert(sizeof(RelocationEntry) == 0x14, "RelocationEntry has incorrect size."); - -struct RelocationBucketRaw { - INSERT_PADDING_BYTES(4); - u32_le number_entries; - u64_le end_offset; - std::array<RelocationEntry, 0x332> relocation_entries; - INSERT_PADDING_BYTES(8); -}; -static_assert(sizeof(RelocationBucketRaw) == 0x4000, "RelocationBucketRaw has incorrect size."); - -// Vector version of RelocationBucketRaw -struct RelocationBucket { - u32 number_entries; - u64 end_offset; - std::vector<RelocationEntry> entries; -}; - -struct RelocationBlock { - INSERT_PADDING_BYTES(4); - u32_le number_buckets; - u64_le size; - std::array<u64, 0x7FE> base_offsets; -}; -static_assert(sizeof(RelocationBlock) == 0x4000, "RelocationBlock has incorrect size."); - -struct SubsectionEntry { - u64_le address_patch; - INSERT_PADDING_BYTES(0x4); - u32_le ctr; -}; -static_assert(sizeof(SubsectionEntry) == 0x10, "SubsectionEntry has incorrect size."); - -struct SubsectionBucketRaw { - INSERT_PADDING_BYTES(4); - u32_le number_entries; - u64_le end_offset; - std::array<SubsectionEntry, 0x3FF> subsection_entries; -}; -static_assert(sizeof(SubsectionBucketRaw) == 0x4000, "SubsectionBucketRaw has incorrect size."); - -// Vector version of SubsectionBucketRaw -struct SubsectionBucket { - u32 number_entries; - u64 end_offset; - std::vector<SubsectionEntry> entries; -}; - -struct SubsectionBlock { - INSERT_PADDING_BYTES(4); - u32_le number_buckets; - u64_le size; - std::array<u64, 0x7FE> base_offsets; -}; -static_assert(sizeof(SubsectionBlock) == 0x4000, "SubsectionBlock has incorrect size."); - -inline RelocationBucket ConvertRelocationBucketRaw(RelocationBucketRaw raw) { - return {raw.number_entries, - raw.end_offset, - {raw.relocation_entries.begin(), raw.relocation_entries.begin() + raw.number_entries}}; -} - -inline SubsectionBucket ConvertSubsectionBucketRaw(SubsectionBucketRaw raw) { - return {raw.number_entries, - raw.end_offset, - {raw.subsection_entries.begin(), raw.subsection_entries.begin() + raw.number_entries}}; -} - -class BKTR : public VfsFile { -public: - BKTR(VirtualFile base_romfs, VirtualFile bktr_romfs, RelocationBlock relocation, - std::vector<RelocationBucket> relocation_buckets, SubsectionBlock subsection, - std::vector<SubsectionBucket> subsection_buckets, bool is_encrypted, - Core::Crypto::Key128 key, u64 base_offset, u64 ivfc_offset, std::array<u8, 8> section_ctr); - ~BKTR() override; - - std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override; - - std::string GetName() const override; - - std::size_t GetSize() const override; - - bool Resize(std::size_t new_size) override; - - VirtualDir GetContainingDirectory() const override; - - bool IsWritable() const override; - - bool IsReadable() const override; - - std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override; - - bool Rename(std::string_view name) override; - -private: - RelocationEntry GetRelocationEntry(u64 offset) const; - RelocationEntry GetNextRelocationEntry(u64 offset) const; - - SubsectionEntry GetSubsectionEntry(u64 offset) const; - SubsectionEntry GetNextSubsectionEntry(u64 offset) const; - - RelocationBlock relocation; - std::vector<RelocationBucket> relocation_buckets; - SubsectionBlock subsection; - std::vector<SubsectionBucket> subsection_buckets; - - // Should be the raw base romfs, decrypted. - VirtualFile base_romfs; - // Should be the raw BKTR romfs, (located at media_offset with size media_size). - VirtualFile bktr_romfs; - - bool encrypted; - Core::Crypto::Key128 key; - - // Base offset into NCA, used for IV calculation. - u64 base_offset; - // Distance between IVFC start and RomFS start, used for base reads - u64 ivfc_offset; - std::array<u8, 8> section_ctr; -}; - -} // namespace FileSys diff --git a/src/core/file_sys/partition_filesystem.cpp b/src/core/file_sys/partition_filesystem.cpp index 2527ae606..2422cb51b 100644 --- a/src/core/file_sys/partition_filesystem.cpp +++ b/src/core/file_sys/partition_filesystem.cpp @@ -47,6 +47,7 @@ PartitionFilesystem::PartitionFilesystem(VirtualFile file) { // Actually read in now... std::vector<u8> file_data = file->ReadBytes(metadata_size); const std::size_t total_size = file_data.size(); + file_data.push_back(0); if (total_size != metadata_size) { status = Loader::ResultStatus::ErrorIncorrectPFSFileSize; diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index d3286b352..8e475f25a 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -141,8 +141,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { const auto update_tid = GetUpdateTitleID(title_id); const auto update = content_provider.GetEntry(update_tid, ContentRecordType::Program); - if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr && - update->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) { + if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr) { LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully", FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0))); exefs = update->GetExeFS(); @@ -295,11 +294,11 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st return out; } -bool PatchManager::HasNSOPatch(const BuildID& build_id_) const { +bool PatchManager::HasNSOPatch(const BuildID& build_id_, std::string_view name) const { const auto build_id_raw = Common::HexToString(build_id_); const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1); - LOG_INFO(Loader, "Querying NSO patch existence for build_id={}", build_id); + LOG_INFO(Loader, "Querying NSO patch existence for build_id={}, name={}", build_id, name); const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); if (load_dir == nullptr) { @@ -353,16 +352,12 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t const Service::FileSystem::FileSystemController& fs_controller) { const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id); - if ((type != ContentRecordType::Program && type != ContentRecordType::Data) || + if ((type != ContentRecordType::Program && type != ContentRecordType::Data && + type != ContentRecordType::HtmlDocument) || (load_dir == nullptr && sdmc_load_dir == nullptr)) { return; } - auto extracted = ExtractRomFS(romfs); - if (extracted == nullptr) { - return; - } - const auto& disabled = Settings::values.disabled_addons[title_id]; std::vector<VirtualDir> patch_dirs = load_dir->GetSubdirectories(); if (std::find(disabled.cbegin(), disabled.cend(), "SDMC") == disabled.cend()) { @@ -387,6 +382,12 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t auto ext_dir = FindSubdirectoryCaseless(subdir, "romfs_ext"); if (ext_dir != nullptr) layers_ext.push_back(std::make_shared<CachedVfsDirectory>(ext_dir)); + + if (type == ContentRecordType::HtmlDocument) { + auto manual_dir = FindSubdirectoryCaseless(subdir, "manual_html"); + if (manual_dir != nullptr) + layers.push_back(std::make_shared<CachedVfsDirectory>(manual_dir)); + } } // When there are no layers to apply, return early as there is no need to rebuild the RomFS @@ -394,6 +395,11 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t return; } + auto extracted = ExtractRomFS(romfs); + if (extracted == nullptr) { + return; + } + layers.push_back(std::move(extracted)); auto layered = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers)); @@ -412,39 +418,43 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t romfs = std::move(packed); } -VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type, - VirtualFile update_raw, bool apply_layeredfs) const { +VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs, + ContentRecordType type, VirtualFile packed_update_raw, + bool apply_layeredfs) const { const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}", title_id, static_cast<u8>(type)); - if (type == ContentRecordType::Program || type == ContentRecordType::Data) { LOG_INFO(Loader, "{}", log_string); } else { LOG_DEBUG(Loader, "{}", log_string); } - if (romfs == nullptr) { - return romfs; + if (base_romfs == nullptr) { + return base_romfs; } + auto romfs = base_romfs; + // Game Updates const auto update_tid = GetUpdateTitleID(title_id); - const auto update = content_provider.GetEntryRaw(update_tid, type); + const auto update_raw = content_provider.GetEntryRaw(update_tid, type); const auto& disabled = Settings::values.disabled_addons[title_id]; const auto update_disabled = std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend(); - if (!update_disabled && update != nullptr) { - const auto new_nca = std::make_shared<NCA>(update, romfs, ivfc_offset); + if (!update_disabled && update_raw != nullptr && base_nca != nullptr) { + const auto new_nca = std::make_shared<NCA>(update_raw, base_nca); if (new_nca->GetStatus() == Loader::ResultStatus::Success && new_nca->GetRomFS() != nullptr) { LOG_INFO(Loader, " RomFS: Update ({}) applied successfully", FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0))); romfs = new_nca->GetRomFS(); + const auto version = + FormatTitleVersion(content_provider.GetEntryVersion(update_tid).value_or(0)); } - } else if (!update_disabled && update_raw != nullptr) { - const auto new_nca = std::make_shared<NCA>(update_raw, romfs, ivfc_offset); + } else if (!update_disabled && packed_update_raw != nullptr && base_nca != nullptr) { + const auto new_nca = std::make_shared<NCA>(packed_update_raw, base_nca); if (new_nca->GetStatus() == Loader::ResultStatus::Success && new_nca->GetRomFS() != nullptr) { LOG_INFO(Loader, " RomFS: Update (PACKED) applied successfully"); @@ -608,7 +618,7 @@ PatchManager::Metadata PatchManager::ParseControlNCA(const NCA& nca) const { return {}; } - const auto romfs = PatchRomFS(base_romfs, nca.GetBaseIVFCOffset(), ContentRecordType::Control); + const auto romfs = PatchRomFS(&nca, base_romfs, ContentRecordType::Control); if (romfs == nullptr) { return {}; } @@ -626,8 +636,8 @@ PatchManager::Metadata PatchManager::ParseControlNCA(const NCA& nca) const { auto nacp = nacp_file == nullptr ? nullptr : std::make_unique<NACP>(nacp_file); // Get language code from settings - const auto language_code = - Service::Set::GetLanguageCodeFromIndex(Settings::values.language_index.GetValue()); + const auto language_code = Service::Set::GetLanguageCodeFromIndex( + static_cast<u32>(Settings::values.language_index.GetValue())); // Convert to application language and get priority list const auto application_language = diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index 69d15e2f8..03e9c7301 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h @@ -52,7 +52,7 @@ public: // Checks to see if PatchNSO() will have any effect given the NSO's build ID. // Used to prevent expensive copies in NSO loader. - [[nodiscard]] bool HasNSOPatch(const BuildID& build_id) const; + [[nodiscard]] bool HasNSOPatch(const BuildID& build_id, std::string_view name) const; // Creates a CheatList object with all [[nodiscard]] std::vector<Core::Memory::CheatEntry> CreateCheatList( @@ -61,9 +61,9 @@ public: // Currently tracked RomFS patches: // - Game Updates // - LayeredFS - [[nodiscard]] VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset, + [[nodiscard]] VirtualFile PatchRomFS(const NCA* base_nca, VirtualFile base_romfs, ContentRecordType type = ContentRecordType::Program, - VirtualFile update_raw = nullptr, + VirtualFile packed_update_raw = nullptr, bool apply_layeredfs = true) const; // Returns a vector of pairs between patch names and patch versions. diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index a6960170c..04da93d5c 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -9,6 +9,7 @@ #include "common/fs/path_util.h" #include "common/hex_util.h" #include "common/logging/log.h" +#include "common/scope_exit.h" #include "core/crypto/key_manager.h" #include "core/file_sys/card_image.h" #include "core/file_sys/common_funcs.h" @@ -416,9 +417,9 @@ void RegisteredCache::ProcessFiles(const std::vector<NcaID>& ids) { if (file == nullptr) continue; - const auto nca = std::make_shared<NCA>(parser(file, id), nullptr, 0); + const auto nca = std::make_shared<NCA>(parser(file, id)); if (nca->GetStatus() != Loader::ResultStatus::Success || - nca->GetType() != NCAContentType::Meta) { + nca->GetType() != NCAContentType::Meta || nca->GetSubdirectories().empty()) { continue; } @@ -500,7 +501,7 @@ std::unique_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType t const auto raw = GetEntryRaw(title_id, type); if (raw == nullptr) return nullptr; - return std::make_unique<NCA>(raw, nullptr, 0); + return std::make_unique<NCA>(raw); } template <typename T> @@ -606,9 +607,9 @@ InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_ex const auto result = RemoveExistingEntry(title_id); // Install Metadata File - const auto res = RawInstallNCA(**meta_iter, copy, overwrite_if_exists, meta_id_data); - if (res != InstallResult::Success) { - return res; + const auto meta_result = RawInstallNCA(**meta_iter, copy, overwrite_if_exists, meta_id_data); + if (meta_result != InstallResult::Success) { + return meta_result; } // Install all the other NCAs @@ -621,9 +622,19 @@ InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_ex if (nca == nullptr) { return InstallResult::ErrorCopyFailed; } - const auto res2 = RawInstallNCA(*nca, copy, overwrite_if_exists, record.nca_id); - if (res2 != InstallResult::Success) { - return res2; + if (nca->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS && + nca->GetTitleId() != title_id) { + // Create fake cnmt for patch to multiprogram application + const auto sub_nca_result = + InstallEntry(*nca, cnmt.GetHeader(), record, overwrite_if_exists, copy); + if (sub_nca_result != InstallResult::Success) { + return sub_nca_result; + } + continue; + } + const auto nca_result = RawInstallNCA(*nca, copy, overwrite_if_exists, record.nca_id); + if (nca_result != InstallResult::Success) { + return nca_result; } } @@ -662,7 +673,34 @@ InstallResult RegisteredCache::InstallEntry(const NCA& nca, TitleType type, return RawInstallNCA(nca, copy, overwrite_if_exists, c_rec.nca_id); } +InstallResult RegisteredCache::InstallEntry(const NCA& nca, const CNMTHeader& base_header, + const ContentRecord& base_record, + bool overwrite_if_exists, const VfsCopyFunction& copy) { + const CNMTHeader header{ + .title_id = nca.GetTitleId(), + .title_version = base_header.title_version, + .type = base_header.type, + .reserved = {}, + .table_offset = 0x10, + .number_content_entries = 1, + .number_meta_entries = 0, + .attributes = 0, + .reserved2 = {}, + .is_committed = 0, + .required_download_system_version = 0, + .reserved3 = {}, + }; + const OptionalHeader opt_header{0, 0}; + const CNMT new_cnmt(header, opt_header, {base_record}, {}); + if (!RawInstallYuzuMeta(new_cnmt)) { + return InstallResult::ErrorMetaFailed; + } + return RawInstallNCA(nca, copy, overwrite_if_exists, base_record.nca_id); +} + bool RegisteredCache::RemoveExistingEntry(u64 title_id) const { + bool removed_data = false; + const auto delete_nca = [this](const NcaID& id) { const auto path = GetRelativePathFromNcaID(id, false, true, false); @@ -706,11 +744,20 @@ bool RegisteredCache::RemoveExistingEntry(u64 title_id) const { const auto deleted_html = delete_nca(html_id); const auto deleted_legal = delete_nca(legal_id); - return deleted_meta && (deleted_meta || deleted_program || deleted_data || - deleted_control || deleted_html || deleted_legal); + removed_data |= (deleted_meta || deleted_program || deleted_data || deleted_control || + deleted_html || deleted_legal); } - return false; + // If patch entries for any program exist in yuzu meta, remove them + for (u8 i = 0; i < 0x10; i++) { + const auto meta_dir = dir->CreateDirectoryRelative("yuzu_meta"); + const auto filename = GetCNMTName(TitleType::Update, title_id + i); + if (meta_dir->GetFile(filename)) { + removed_data |= meta_dir->DeleteFile(filename); + } + } + + return removed_data; } InstallResult RegisteredCache::RawInstallNCA(const NCA& nca, const VfsCopyFunction& copy, @@ -964,7 +1011,7 @@ std::unique_ptr<NCA> ManualContentProvider::GetEntry(u64 title_id, ContentRecord const auto res = GetEntryRaw(title_id, type); if (res == nullptr) return nullptr; - return std::make_unique<NCA>(res, nullptr, 0); + return std::make_unique<NCA>(res); } std::vector<ContentProviderEntry> ManualContentProvider::ListEntriesFilter( diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h index bd7f53eaf..64815a845 100644 --- a/src/core/file_sys/registered_cache.h +++ b/src/core/file_sys/registered_cache.h @@ -24,6 +24,7 @@ enum class NCAContentType : u8; enum class TitleType : u8; struct ContentRecord; +struct CNMTHeader; struct MetaRecord; class RegisteredCache; @@ -169,6 +170,10 @@ public: InstallResult InstallEntry(const NCA& nca, TitleType type, bool overwrite_if_exists = false, const VfsCopyFunction& copy = &VfsRawCopy); + InstallResult InstallEntry(const NCA& nca, const CNMTHeader& base_header, + const ContentRecord& base_record, bool overwrite_if_exists = false, + const VfsCopyFunction& copy = &VfsRawCopy); + // Removes an existing entry based on title id bool RemoveExistingEntry(u64 title_id) const; diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp index ae7a3511b..1bc07dae5 100644 --- a/src/core/file_sys/romfs_factory.cpp +++ b/src/core/file_sys/romfs_factory.cpp @@ -26,60 +26,52 @@ RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader, ContentProvider& provi } updatable = app_loader.IsRomFSUpdatable(); - ivfc_offset = app_loader.ReadRomFSIVFCOffset(); } RomFSFactory::~RomFSFactory() = default; void RomFSFactory::SetPackedUpdate(VirtualFile update_raw_file) { - update_raw = std::move(update_raw_file); + packed_update_raw = std::move(update_raw_file); } -ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess(u64 current_process_title_id) const { +VirtualFile RomFSFactory::OpenCurrentProcess(u64 current_process_title_id) const { if (!updatable) { return file; } + const auto type = ContentRecordType::Program; + const auto nca = content_provider.GetEntry(current_process_title_id, type); const PatchManager patch_manager{current_process_title_id, filesystem_controller, content_provider}; - return patch_manager.PatchRomFS(file, ivfc_offset, ContentRecordType::Program, update_raw); + return patch_manager.PatchRomFS(nca.get(), file, ContentRecordType::Program, packed_update_raw); } -ResultVal<VirtualFile> RomFSFactory::OpenPatchedRomFS(u64 title_id, ContentRecordType type) const { +VirtualFile RomFSFactory::OpenPatchedRomFS(u64 title_id, ContentRecordType type) const { auto nca = content_provider.GetEntry(title_id, type); if (nca == nullptr) { - // TODO: Find the right error code to use here - return ResultUnknown; + return nullptr; } const PatchManager patch_manager{title_id, filesystem_controller, content_provider}; - return patch_manager.PatchRomFS(nca->GetRomFS(), nca->GetBaseIVFCOffset(), type); + return patch_manager.PatchRomFS(nca.get(), nca->GetRomFS(), type); } -ResultVal<VirtualFile> RomFSFactory::OpenPatchedRomFSWithProgramIndex( - u64 title_id, u8 program_index, ContentRecordType type) const { +VirtualFile RomFSFactory::OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index, + ContentRecordType type) const { const auto res_title_id = GetBaseTitleIDWithProgramIndex(title_id, program_index); return OpenPatchedRomFS(res_title_id, type); } -ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, - ContentRecordType type) const { +VirtualFile RomFSFactory::Open(u64 title_id, StorageId storage, ContentRecordType type) const { const std::shared_ptr<NCA> res = GetEntry(title_id, storage, type); if (res == nullptr) { - // TODO(DarkLordZach): Find the right error code to use here - return ResultUnknown; - } - - const auto romfs = res->GetRomFS(); - if (romfs == nullptr) { - // TODO(DarkLordZach): Find the right error code to use here - return ResultUnknown; + return nullptr; } - return romfs; + return res->GetRomFS(); } std::shared_ptr<NCA> RomFSFactory::GetEntry(u64 title_id, StorageId storage, diff --git a/src/core/file_sys/romfs_factory.h b/src/core/file_sys/romfs_factory.h index 14936031f..e4809bc94 100644 --- a/src/core/file_sys/romfs_factory.h +++ b/src/core/file_sys/romfs_factory.h @@ -40,23 +40,22 @@ public: Service::FileSystem::FileSystemController& controller); ~RomFSFactory(); - void SetPackedUpdate(VirtualFile update_raw_file); - [[nodiscard]] ResultVal<VirtualFile> OpenCurrentProcess(u64 current_process_title_id) const; - [[nodiscard]] ResultVal<VirtualFile> OpenPatchedRomFS(u64 title_id, - ContentRecordType type) const; - [[nodiscard]] ResultVal<VirtualFile> OpenPatchedRomFSWithProgramIndex( - u64 title_id, u8 program_index, ContentRecordType type) const; - [[nodiscard]] ResultVal<VirtualFile> Open(u64 title_id, StorageId storage, - ContentRecordType type) const; - -private: + void SetPackedUpdate(VirtualFile packed_update_raw); + [[nodiscard]] VirtualFile OpenCurrentProcess(u64 current_process_title_id) const; + [[nodiscard]] VirtualFile OpenPatchedRomFS(u64 title_id, ContentRecordType type) const; + [[nodiscard]] VirtualFile OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index, + ContentRecordType type) const; + [[nodiscard]] VirtualFile Open(u64 title_id, StorageId storage, ContentRecordType type) const; [[nodiscard]] std::shared_ptr<NCA> GetEntry(u64 title_id, StorageId storage, ContentRecordType type) const; +private: VirtualFile file; - VirtualFile update_raw; + VirtualFile packed_update_raw; + + VirtualFile base; + bool updatable; - u64 ivfc_offset; ContentProvider& content_provider; Service::FileSystem::FileSystemController& filesystem_controller; diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp index 70b36f170..a4d060007 100644 --- a/src/core/file_sys/savedata_factory.cpp +++ b/src/core/file_sys/savedata_factory.cpp @@ -108,26 +108,16 @@ SaveDataFactory::SaveDataFactory(Core::System& system_, VirtualDir save_director SaveDataFactory::~SaveDataFactory() = default; -ResultVal<VirtualDir> SaveDataFactory::Create(SaveDataSpaceId space, - const SaveDataAttribute& meta) const { +VirtualDir SaveDataFactory::Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const { PrintSaveDataAttributeWarnings(meta); const auto save_directory = GetFullPath(system, dir, space, meta.type, meta.title_id, meta.user_id, meta.save_id); - auto out = dir->CreateDirectoryRelative(save_directory); - - // Return an error if the save data doesn't actually exist. - if (out == nullptr) { - // TODO(DarkLordZach): Find out correct error code. - return ResultUnknown; - } - - return out; + return dir->CreateDirectoryRelative(save_directory); } -ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, - const SaveDataAttribute& meta) const { +VirtualDir SaveDataFactory::Open(SaveDataSpaceId space, const SaveDataAttribute& meta) const { const auto save_directory = GetFullPath(system, dir, space, meta.type, meta.title_id, meta.user_id, meta.save_id); @@ -138,12 +128,6 @@ ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, return Create(space, meta); } - // Return an error if the save data doesn't actually exist. - if (out == nullptr) { - // TODO(Subv): Find out correct error code. - return ResultUnknown; - } - return out; } diff --git a/src/core/file_sys/savedata_factory.h b/src/core/file_sys/savedata_factory.h index d3633ef03..45c7c81fb 100644 --- a/src/core/file_sys/savedata_factory.h +++ b/src/core/file_sys/savedata_factory.h @@ -89,8 +89,8 @@ public: explicit SaveDataFactory(Core::System& system_, VirtualDir save_directory_); ~SaveDataFactory(); - ResultVal<VirtualDir> Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const; - ResultVal<VirtualDir> Open(SaveDataSpaceId space, const SaveDataAttribute& meta) const; + VirtualDir Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const; + VirtualDir Open(SaveDataSpaceId space, const SaveDataAttribute& meta) const; VirtualDir GetSaveDataSpaceDirectory(SaveDataSpaceId space) const; diff --git a/src/core/file_sys/sdmc_factory.cpp b/src/core/file_sys/sdmc_factory.cpp index 1df022c9e..d5158cd64 100644 --- a/src/core/file_sys/sdmc_factory.cpp +++ b/src/core/file_sys/sdmc_factory.cpp @@ -23,7 +23,7 @@ SDMCFactory::SDMCFactory(VirtualDir sd_dir_, VirtualDir sd_mod_dir_) SDMCFactory::~SDMCFactory() = default; -ResultVal<VirtualDir> SDMCFactory::Open() const { +VirtualDir SDMCFactory::Open() const { return sd_dir; } diff --git a/src/core/file_sys/sdmc_factory.h b/src/core/file_sys/sdmc_factory.h index 3aebfb25e..a445fdb16 100644 --- a/src/core/file_sys/sdmc_factory.h +++ b/src/core/file_sys/sdmc_factory.h @@ -18,7 +18,7 @@ public: explicit SDMCFactory(VirtualDir sd_dir_, VirtualDir sd_mod_dir_); ~SDMCFactory(); - ResultVal<VirtualDir> Open() const; + VirtualDir Open() const; VirtualDir GetSDMCModificationLoadRoot(u64 title_id) const; VirtualDir GetSDMCContentDirectory() const; diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp index c90e6e372..68e8ec22f 100644 --- a/src/core/file_sys/submission_package.cpp +++ b/src/core/file_sys/submission_package.cpp @@ -164,24 +164,6 @@ VirtualFile NSP::GetNCAFile(u64 title_id, ContentRecordType type, TitleType titl return nullptr; } -std::vector<Core::Crypto::Key128> NSP::GetTitlekey() const { - if (extracted) - LOG_WARNING(Service_FS, "called on an NSP that is of type extracted."); - std::vector<Core::Crypto::Key128> out; - for (const auto& ticket_file : ticket_files) { - if (ticket_file == nullptr || - ticket_file->GetSize() < - Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) { - continue; - } - - out.emplace_back(); - ticket_file->Read(out.back().data(), out.back().size(), - Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET); - } - return out; -} - std::vector<VirtualFile> NSP::GetFiles() const { return pfs->GetFiles(); } @@ -208,22 +190,11 @@ void NSP::SetTicketKeys(const std::vector<VirtualFile>& files) { continue; } - if (ticket_file->GetSize() < - Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) { + auto ticket = Core::Crypto::Ticket::Read(ticket_file); + if (!keys.AddTicket(ticket)) { + LOG_WARNING(Common_Filesystem, "Could not load NSP ticket {}", ticket_file->GetName()); continue; } - - Core::Crypto::Key128 key{}; - ticket_file->Read(key.data(), key.size(), Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET); - - // We get the name without the extension in order to create the rights ID. - std::string name_only(ticket_file->GetName()); - name_only.erase(name_only.size() - 4); - - const auto rights_id_raw = Common::HexStringToArray<16>(name_only); - u128 rights_id; - std::memcpy(rights_id.data(), rights_id_raw.data(), sizeof(u128)); - keys.SetKey(Core::Crypto::S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); } } @@ -249,7 +220,7 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) { } const auto nca = std::make_shared<NCA>(outer_file); - if (nca->GetStatus() != Loader::ResultStatus::Success) { + if (nca->GetStatus() != Loader::ResultStatus::Success || nca->GetSubdirectories().empty()) { program_status[nca->GetTitleId()] = nca->GetStatus(); continue; } @@ -280,7 +251,7 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) { continue; } - auto next_nca = std::make_shared<NCA>(std::move(next_file), nullptr, 0); + auto next_nca = std::make_shared<NCA>(std::move(next_file)); if (next_nca->GetType() == NCAContentType::Program) { program_status[next_nca->GetTitleId()] = next_nca->GetStatus(); diff --git a/src/core/file_sys/submission_package.h b/src/core/file_sys/submission_package.h index 27f97c725..915bffca9 100644 --- a/src/core/file_sys/submission_package.h +++ b/src/core/file_sys/submission_package.h @@ -53,7 +53,6 @@ public: TitleType title_type = TitleType::Application) const; VirtualFile GetNCAFile(u64 title_id, ContentRecordType type, TitleType title_type = TitleType::Application) const; - std::vector<Core::Crypto::Key128> GetTitlekey() const; std::vector<VirtualFile> GetFiles() const override; diff --git a/src/core/frontend/applets/controller.cpp b/src/core/frontend/applets/controller.cpp index 3300d4f79..27755cb58 100644 --- a/src/core/frontend/applets/controller.cpp +++ b/src/core/frontend/applets/controller.cpp @@ -3,6 +3,8 @@ #include "common/assert.h" #include "common/logging/log.h" +#include "common/settings.h" +#include "common/settings_enums.h" #include "core/frontend/applets/controller.h" #include "core/hid/emulated_controller.h" #include "core/hid/hid_core.h" @@ -62,7 +64,7 @@ void DefaultControllerApplet::ReconfigureControllers(ReconfigureCallback callbac controller->Connect(true); } } else if (index == 0 && parameters.enable_single_mode && parameters.allow_handheld && - !Settings::values.use_docked_mode.GetValue()) { + !Settings::IsDockedMode()) { // We should *never* reach here under any normal circumstances. controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld); controller->Connect(true); diff --git a/src/core/frontend/framebuffer_layout.cpp b/src/core/frontend/framebuffer_layout.cpp index b4081fc39..2590b20da 100644 --- a/src/core/frontend/framebuffer_layout.cpp +++ b/src/core/frontend/framebuffer_layout.cpp @@ -5,6 +5,7 @@ #include "common/assert.h" #include "common/settings.h" +#include "common/settings_enums.h" #include "core/frontend/framebuffer_layout.h" namespace Layout { @@ -49,7 +50,7 @@ FramebufferLayout DefaultFrameLayout(u32 width, u32 height) { } FramebufferLayout FrameLayoutFromResolutionScale(f32 res_scale) { - const bool is_docked = Settings::values.use_docked_mode.GetValue(); + const bool is_docked = Settings::IsDockedMode(); const u32 screen_width = is_docked ? ScreenDocked::Width : ScreenUndocked::Width; const u32 screen_height = is_docked ? ScreenDocked::Height : ScreenUndocked::Height; diff --git a/src/core/hid/hid_core.cpp b/src/core/hid/hid_core.cpp index 7d6373414..cf53c04d9 100644 --- a/src/core/hid/hid_core.cpp +++ b/src/core/hid/hid_core.cpp @@ -154,6 +154,14 @@ NpadIdType HIDCore::GetFirstDisconnectedNpadId() const { return NpadIdType::Player1; } +void HIDCore::SetLastActiveController(NpadIdType npad_id) { + last_active_controller = npad_id; +} + +NpadIdType HIDCore::GetLastActiveController() const { + return last_active_controller; +} + void HIDCore::EnableAllControllerConfiguration() { player_1->EnableConfiguration(); player_2->EnableConfiguration(); diff --git a/src/core/hid/hid_core.h b/src/core/hid/hid_core.h index 5fe36551e..80abab18b 100644 --- a/src/core/hid/hid_core.h +++ b/src/core/hid/hid_core.h @@ -48,6 +48,12 @@ public: /// Returns the first disconnected npad id NpadIdType GetFirstDisconnectedNpadId() const; + /// Sets the npad id of the last active controller + void SetLastActiveController(NpadIdType npad_id); + + /// Returns the npad id of the last controller that pushed a button + NpadIdType GetLastActiveController() const; + /// Sets all emulated controllers into configuring mode. void EnableAllControllerConfiguration(); @@ -77,6 +83,7 @@ private: std::unique_ptr<EmulatedConsole> console; std::unique_ptr<EmulatedDevices> devices; NpadStyleTag supported_style_tag{NpadStyleSet::All}; + NpadIdType last_active_controller{NpadIdType::Handheld}; }; } // namespace Core::HID diff --git a/src/core/hid/hid_types.h b/src/core/hid/hid_types.h index 6b35f448c..00beb40dd 100644 --- a/src/core/hid/hid_types.h +++ b/src/core/hid/hid_types.h @@ -289,6 +289,19 @@ enum class GyroscopeZeroDriftMode : u32 { Tight = 2, }; +// This is nn::settings::system::TouchScreenMode +enum class TouchScreenMode : u32 { + Stylus = 0, + Standard = 1, +}; + +// This is nn::hid::TouchScreenModeForNx +enum class TouchScreenModeForNx : u8 { + UseSystemSetting, + Finger, + Heat2, +}; + // This is nn::hid::NpadStyleTag struct NpadStyleTag { union { @@ -334,6 +347,14 @@ struct TouchState { }; static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size"); +// This is nn::hid::TouchScreenConfigurationForNx +struct TouchScreenConfigurationForNx { + TouchScreenModeForNx mode{TouchScreenModeForNx::UseSystemSetting}; + INSERT_PADDING_BYTES(0xF); +}; +static_assert(sizeof(TouchScreenConfigurationForNx) == 0x10, + "TouchScreenConfigurationForNx is an invalid size"); + struct NpadColor { u8 r{}; u8 g{}; @@ -662,6 +683,11 @@ struct MouseState { }; static_assert(sizeof(MouseState) == 0x28, "MouseState is an invalid size"); +struct UniquePadId { + u64 id; +}; +static_assert(sizeof(UniquePadId) == 0x8, "UniquePadId is an invalid size"); + /// Converts a NpadIdType to an array index. constexpr size_t NpadIdTypeToIndex(NpadIdType npad_id_type) { switch (npad_id_type) { diff --git a/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp b/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp index 49bdc671e..4cfdf4558 100644 --- a/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp +++ b/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp @@ -35,13 +35,27 @@ namespace { using namespace Common::Literals; u32 GetMemorySizeForInit() { - return Settings::values.use_unsafe_extended_memory_layout ? Smc::MemorySize_8GB - : Smc::MemorySize_4GB; + switch (Settings::values.memory_layout_mode.GetValue()) { + case Settings::MemoryLayout::Memory_4Gb: + return Smc::MemorySize_4GB; + case Settings::MemoryLayout::Memory_6Gb: + return Smc::MemorySize_6GB; + case Settings::MemoryLayout::Memory_8Gb: + return Smc::MemorySize_8GB; + } + return Smc::MemorySize_4GB; } Smc::MemoryArrangement GetMemoryArrangeForInit() { - return Settings::values.use_unsafe_extended_memory_layout ? Smc::MemoryArrangement_8GB - : Smc::MemoryArrangement_4GB; + switch (Settings::values.memory_layout_mode.GetValue()) { + case Settings::MemoryLayout::Memory_4Gb: + return Smc::MemoryArrangement_4GB; + case Settings::MemoryLayout::Memory_6Gb: + return Smc::MemoryArrangement_6GB; + case Settings::MemoryLayout::Memory_8Gb: + return Smc::MemoryArrangement_8GB; + } + return Smc::MemoryArrangement_4GB; } } // namespace diff --git a/src/core/hle/kernel/k_capabilities.cpp b/src/core/hle/kernel/k_capabilities.cpp index 90e4e8fb0..e7da7a21d 100644 --- a/src/core/hle/kernel/k_capabilities.cpp +++ b/src/core/hle/kernel/k_capabilities.cpp @@ -156,7 +156,6 @@ Result KCapabilities::MapIoPage_(const u32 cap, KPageTable* page_table) { const u64 phys_addr = MapIoPage{cap}.address.Value() * PageSize; const size_t num_pages = 1; const size_t size = num_pages * PageSize; - R_UNLESS(num_pages != 0, ResultInvalidSize); R_UNLESS(phys_addr < phys_addr + size, ResultInvalidAddress); R_UNLESS(((phys_addr + size - 1) & ~PhysicalMapAllowedMask) == 0, ResultInvalidAddress); diff --git a/src/core/hle/kernel/k_hardware_timer.h b/src/core/hle/kernel/k_hardware_timer.h index 00bef6ea1..27f43cd19 100644 --- a/src/core/hle/kernel/k_hardware_timer.h +++ b/src/core/hle/kernel/k_hardware_timer.h @@ -19,13 +19,7 @@ public: void Initialize(); void Finalize(); - s64 GetCount() const { - return GetTick(); - } - - void RegisterTask(KTimerTask* task, s64 time_from_now) { - this->RegisterAbsoluteTask(task, GetTick() + time_from_now); - } + s64 GetTick() const; void RegisterAbsoluteTask(KTimerTask* task, s64 task_time) { KScopedDisableDispatch dd{m_kernel}; @@ -42,7 +36,6 @@ private: void EnableInterrupt(s64 wakeup_time); void DisableInterrupt(); bool GetInterruptEnabled(); - s64 GetTick() const; void DoTask(); private: diff --git a/src/core/hle/kernel/k_page_table.cpp b/src/core/hle/kernel/k_page_table.cpp index 02b5cada4..9bfc85b34 100644 --- a/src/core/hle/kernel/k_page_table.cpp +++ b/src/core/hle/kernel/k_page_table.cpp @@ -768,7 +768,7 @@ Result KPageTable::UnmapProcessMemory(KProcessAddress dst_addr, size_t size, m_memory_block_slab_manager, num_allocator_blocks); R_TRY(allocator_result); - CASCADE_CODE(Operate(dst_addr, num_pages, KMemoryPermission::None, OperationType::Unmap)); + R_TRY(Operate(dst_addr, num_pages, KMemoryPermission::None, OperationType::Unmap)); // Apply the memory block update. m_memory_block_manager.Update(std::addressof(allocator), dst_addr, num_pages, diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp index 44c7cb22f..4a099286b 100644 --- a/src/core/hle/kernel/k_process.cpp +++ b/src/core/hle/kernel/k_process.cpp @@ -38,7 +38,7 @@ namespace { */ void SetupMainThread(Core::System& system, KProcess& owner_process, u32 priority, KProcessAddress stack_top) { - const KProcessAddress entry_point = owner_process.GetPageTable().GetCodeRegionStart(); + const KProcessAddress entry_point = owner_process.GetEntryPoint(); ASSERT(owner_process.GetResourceLimit()->Reserve(LimitableResource::ThreadCountMax, 1)); KThread* thread = KThread::Create(system.Kernel()); @@ -81,7 +81,8 @@ Result KProcess::Initialize(KProcess* process, Core::System& system, std::string process->m_capabilities.InitializeForMetadatalessProcess(); process->m_is_initialized = true; - std::mt19937 rng(Settings::values.rng_seed.GetValue().value_or(std::time(nullptr))); + std::mt19937 rng(Settings::values.rng_seed_enabled ? Settings::values.rng_seed.GetValue() + : static_cast<u32>(std::time(nullptr))); std::uniform_int_distribution<u64> distribution; std::generate(process->m_random_entropy.begin(), process->m_random_entropy.end(), [&] { return distribution(rng); }); @@ -95,6 +96,7 @@ Result KProcess::Initialize(KProcess* process, Core::System& system, std::string process->m_is_suspended = false; process->m_schedule_count = 0; process->m_is_handle_table_initialized = false; + process->m_is_hbl = false; // Open a reference to the resource limit. process->m_resource_limit->Open(); @@ -350,12 +352,29 @@ Result KProcess::SetActivity(ProcessActivity activity) { R_SUCCEED(); } -Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size) { +Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size, + bool is_hbl) { m_program_id = metadata.GetTitleID(); m_ideal_core = metadata.GetMainThreadCore(); m_is_64bit_process = metadata.Is64BitProgram(); m_system_resource_size = metadata.GetSystemResourceSize(); m_image_size = code_size; + m_is_hbl = is_hbl; + + if (metadata.GetAddressSpaceType() == FileSys::ProgramAddressSpaceType::Is39Bit) { + // For 39-bit processes, the ASLR region starts at 0x800'0000 and is ~512GiB large. + // However, some (buggy) programs/libraries like skyline incorrectly depend on the + // existence of ASLR pages before the entry point, so we will adjust the load address + // to point to about 2GiB into the ASLR region. + m_code_address = 0x8000'0000; + } else { + // All other processes can be mapped at the beginning of the code region. + if (metadata.GetAddressSpaceType() == FileSys::ProgramAddressSpaceType::Is36Bit) { + m_code_address = 0x800'0000; + } else { + m_code_address = 0x20'0000; + } + } KScopedResourceReservation memory_reservation( m_resource_limit, LimitableResource::PhysicalMemoryMax, code_size + m_system_resource_size); @@ -367,15 +386,15 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std: // Initialize process address space if (const Result result{m_page_table.InitializeForProcess( metadata.GetAddressSpaceType(), false, false, false, KMemoryManager::Pool::Application, - 0x8000000, code_size, std::addressof(m_kernel.GetAppSystemResource()), m_resource_limit, - m_kernel.System().ApplicationMemory())}; + this->GetEntryPoint(), code_size, std::addressof(m_kernel.GetAppSystemResource()), + m_resource_limit, m_kernel.System().ApplicationMemory())}; result.IsError()) { R_RETURN(result); } // Map process code region - if (const Result result{m_page_table.MapProcessCode(m_page_table.GetCodeRegionStart(), - code_size / PageSize, KMemoryState::Code, + if (const Result result{m_page_table.MapProcessCode(this->GetEntryPoint(), code_size / PageSize, + KMemoryState::Code, KMemoryPermission::None)}; result.IsError()) { R_RETURN(result); diff --git a/src/core/hle/kernel/k_process.h b/src/core/hle/kernel/k_process.h index c9b37e138..146e07a57 100644 --- a/src/core/hle/kernel/k_process.h +++ b/src/core/hle/kernel/k_process.h @@ -177,6 +177,10 @@ public: return m_program_id; } + KProcessAddress GetEntryPoint() const { + return m_code_address; + } + /// Gets the resource limit descriptor for this process KResourceLimit* GetResourceLimit() const; @@ -334,7 +338,8 @@ public: * @returns ResultSuccess if all relevant metadata was able to be * loaded and parsed. Otherwise, an error code is returned. */ - Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size); + Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size, + bool is_hbl); /** * Starts the main application thread for this process. @@ -364,6 +369,10 @@ public: return GetProcessId(); } + bool IsHbl() const { + return m_is_hbl; + } + bool IsSignaled() const override; void DoWorkerTaskImpl(); @@ -485,6 +494,9 @@ private: /// Address indicating the location of the process' dedicated TLS region. KProcessAddress m_plr_address = 0; + /// Address indicating the location of the process's entry point. + KProcessAddress m_code_address = 0; + /// Random values for svcGetInfo RandomEntropy std::array<u64, RANDOM_ENTROPY_SIZE> m_random_entropy{}; @@ -518,6 +530,7 @@ private: bool m_is_immortal{}; bool m_is_handle_table_initialized{}; bool m_is_initialized{}; + bool m_is_hbl{}; std::atomic<u16> m_num_running_threads{}; diff --git a/src/core/hle/kernel/k_resource_limit.cpp b/src/core/hle/kernel/k_resource_limit.cpp index fcee26a29..d8a63aaf8 100644 --- a/src/core/hle/kernel/k_resource_limit.cpp +++ b/src/core/hle/kernel/k_resource_limit.cpp @@ -5,6 +5,7 @@ #include "common/overflow.h" #include "core/core.h" #include "core/core_timing.h" +#include "core/hle/kernel/k_hardware_timer.h" #include "core/hle/kernel/k_resource_limit.h" #include "core/hle/kernel/svc_results.h" @@ -15,9 +16,7 @@ KResourceLimit::KResourceLimit(KernelCore& kernel) : KAutoObjectWithSlabHeapAndContainer{kernel}, m_lock{m_kernel}, m_cond_var{m_kernel} {} KResourceLimit::~KResourceLimit() = default; -void KResourceLimit::Initialize(const Core::Timing::CoreTiming* core_timing) { - m_core_timing = core_timing; -} +void KResourceLimit::Initialize() {} void KResourceLimit::Finalize() {} @@ -86,7 +85,7 @@ Result KResourceLimit::SetLimitValue(LimitableResource which, s64 value) { } bool KResourceLimit::Reserve(LimitableResource which, s64 value) { - return Reserve(which, value, m_core_timing->GetGlobalTimeNs().count() + DefaultTimeout); + return Reserve(which, value, m_kernel.HardwareTimer().GetTick() + DefaultTimeout); } bool KResourceLimit::Reserve(LimitableResource which, s64 value, s64 timeout) { @@ -117,7 +116,7 @@ bool KResourceLimit::Reserve(LimitableResource which, s64 value, s64 timeout) { } if (m_current_hints[index] + value <= m_limit_values[index] && - (timeout < 0 || m_core_timing->GetGlobalTimeNs().count() < timeout)) { + (timeout < 0 || m_kernel.HardwareTimer().GetTick() < timeout)) { m_waiter_count++; m_cond_var.Wait(std::addressof(m_lock), timeout, false); m_waiter_count--; @@ -154,7 +153,7 @@ void KResourceLimit::Release(LimitableResource which, s64 value, s64 hint) { KResourceLimit* CreateResourceLimitForProcess(Core::System& system, s64 physical_memory_size) { auto* resource_limit = KResourceLimit::Create(system.Kernel()); - resource_limit->Initialize(std::addressof(system.CoreTiming())); + resource_limit->Initialize(); // Initialize default resource limit values. // TODO(bunnei): These values are the system defaults, the limits for service processes are diff --git a/src/core/hle/kernel/k_resource_limit.h b/src/core/hle/kernel/k_resource_limit.h index 15e69af56..b733ec8f8 100644 --- a/src/core/hle/kernel/k_resource_limit.h +++ b/src/core/hle/kernel/k_resource_limit.h @@ -31,7 +31,7 @@ public: explicit KResourceLimit(KernelCore& kernel); ~KResourceLimit() override; - void Initialize(const Core::Timing::CoreTiming* core_timing); + void Initialize(); void Finalize() override; s64 GetLimitValue(LimitableResource which) const; @@ -57,7 +57,6 @@ private: mutable KLightLock m_lock; s32 m_waiter_count{}; KLightConditionVariable m_cond_var; - const Core::Timing::CoreTiming* m_core_timing{}; }; KResourceLimit* CreateResourceLimitForProcess(Core::System& system, s64 physical_memory_size); diff --git a/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h b/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h index c485022f5..b62415da7 100644 --- a/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h +++ b/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h @@ -28,7 +28,7 @@ public: ~KScopedSchedulerLockAndSleep() { // Register the sleep. if (m_timeout_tick > 0) { - m_timer->RegisterTask(m_thread, m_timeout_tick); + m_timer->RegisterAbsoluteTask(m_thread, m_timeout_tick); } // Unlock the scheduler. diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index ebe7582c6..a1134b7e2 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -231,7 +231,7 @@ struct KernelCore::Impl { void InitializeSystemResourceLimit(KernelCore& kernel, const Core::Timing::CoreTiming& core_timing) { system_resource_limit = KResourceLimit::Create(system.Kernel()); - system_resource_limit->Initialize(&core_timing); + system_resource_limit->Initialize(); KResourceLimit::Register(kernel, system_resource_limit); const auto sizes{memory_layout->GetTotalAndKernelMemorySizes()}; diff --git a/src/core/hle/kernel/svc/svc_address_arbiter.cpp b/src/core/hle/kernel/svc/svc_address_arbiter.cpp index 04cc5ea64..90ee43521 100644 --- a/src/core/hle/kernel/svc/svc_address_arbiter.cpp +++ b/src/core/hle/kernel/svc/svc_address_arbiter.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "core/core.h" +#include "core/hle/kernel/k_hardware_timer.h" #include "core/hle/kernel/k_memory_layout.h" #include "core/hle/kernel/k_process.h" #include "core/hle/kernel/kernel.h" @@ -52,7 +53,7 @@ Result WaitForAddress(Core::System& system, u64 address, ArbitrationType arb_typ if (timeout_ns > 0) { const s64 offset_tick(timeout_ns); if (offset_tick > 0) { - timeout = offset_tick + 2; + timeout = system.Kernel().HardwareTimer().GetTick() + offset_tick + 2; if (timeout <= 0) { timeout = std::numeric_limits<s64>::max(); } diff --git a/src/core/hle/kernel/svc/svc_condition_variable.cpp b/src/core/hle/kernel/svc/svc_condition_variable.cpp index ca120d67e..bb678e6c5 100644 --- a/src/core/hle/kernel/svc/svc_condition_variable.cpp +++ b/src/core/hle/kernel/svc/svc_condition_variable.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "core/core.h" +#include "core/hle/kernel/k_hardware_timer.h" #include "core/hle/kernel/k_memory_layout.h" #include "core/hle/kernel/k_process.h" #include "core/hle/kernel/kernel.h" @@ -25,7 +26,7 @@ Result WaitProcessWideKeyAtomic(Core::System& system, u64 address, u64 cv_key, u if (timeout_ns > 0) { const s64 offset_tick(timeout_ns); if (offset_tick > 0) { - timeout = offset_tick + 2; + timeout = system.Kernel().HardwareTimer().GetTick() + offset_tick + 2; if (timeout <= 0) { timeout = std::numeric_limits<s64>::max(); } diff --git a/src/core/hle/kernel/svc/svc_debug_string.cpp b/src/core/hle/kernel/svc/svc_debug_string.cpp index 4c14ce668..00b65429b 100644 --- a/src/core/hle/kernel/svc/svc_debug_string.cpp +++ b/src/core/hle/kernel/svc/svc_debug_string.cpp @@ -14,7 +14,7 @@ Result OutputDebugString(Core::System& system, u64 address, u64 len) { std::string str(len, '\0'); GetCurrentMemory(system.Kernel()).ReadBlock(address, str.data(), str.size()); - LOG_DEBUG(Debug_Emulated, "{}", str); + LOG_INFO(Debug_Emulated, "{}", str); R_SUCCEED(); } diff --git a/src/core/hle/kernel/svc/svc_exception.cpp b/src/core/hle/kernel/svc/svc_exception.cpp index 580cf2f75..c581c086b 100644 --- a/src/core/hle/kernel/svc/svc_exception.cpp +++ b/src/core/hle/kernel/svc/svc_exception.cpp @@ -3,6 +3,7 @@ #include "core/core.h" #include "core/debugger/debugger.h" +#include "core/hle/kernel/k_process.h" #include "core/hle/kernel/k_thread.h" #include "core/hle/kernel/svc.h" #include "core/hle/kernel/svc_types.h" @@ -107,7 +108,10 @@ void Break(Core::System& system, BreakReason reason, u64 info1, u64 info2) { system.ArmInterface(static_cast<std::size_t>(thread_processor_id)).LogBacktrace(); } - if (system.DebuggerEnabled()) { + const bool is_hbl = GetCurrentProcess(system.Kernel()).IsHbl(); + const bool should_break = is_hbl || !notification_only; + + if (system.DebuggerEnabled() && should_break) { auto* thread = system.Kernel().GetCurrentEmuThread(); system.GetDebugger().NotifyThreadStopped(thread); thread->RequestSuspend(Kernel::SuspendType::Debug); diff --git a/src/core/hle/kernel/svc/svc_ipc.cpp b/src/core/hle/kernel/svc/svc_ipc.cpp index 373ae7c8d..6b5e1cb8d 100644 --- a/src/core/hle/kernel/svc/svc_ipc.cpp +++ b/src/core/hle/kernel/svc/svc_ipc.cpp @@ -5,6 +5,7 @@ #include "common/scratch_buffer.h" #include "core/core.h" #include "core/hle/kernel/k_client_session.h" +#include "core/hle/kernel/k_hardware_timer.h" #include "core/hle/kernel/k_process.h" #include "core/hle/kernel/k_server_session.h" #include "core/hle/kernel/svc.h" @@ -82,12 +83,29 @@ Result ReplyAndReceive(Core::System& system, s32* out_index, uint64_t handles_ad R_TRY(session->SendReply()); } + // Convert the timeout from nanoseconds to ticks. + // NOTE: Nintendo does not use this conversion logic in WaitSynchronization... + s64 timeout; + if (timeout_ns > 0) { + const s64 offset_tick(timeout_ns); + if (offset_tick > 0) { + timeout = kernel.HardwareTimer().GetTick() + offset_tick + 2; + if (timeout <= 0) { + timeout = std::numeric_limits<s64>::max(); + } + } else { + timeout = std::numeric_limits<s64>::max(); + } + } else { + timeout = timeout_ns; + } + // Wait for a message. while (true) { // Wait for an object. s32 index; Result result = KSynchronizationObject::Wait(kernel, std::addressof(index), objs.data(), - num_handles, timeout_ns); + num_handles, timeout); if (result == ResultTimedOut) { R_RETURN(result); } diff --git a/src/core/hle/kernel/svc/svc_resource_limit.cpp b/src/core/hle/kernel/svc/svc_resource_limit.cpp index 732bc017e..c8e820b6a 100644 --- a/src/core/hle/kernel/svc/svc_resource_limit.cpp +++ b/src/core/hle/kernel/svc/svc_resource_limit.cpp @@ -21,7 +21,7 @@ Result CreateResourceLimit(Core::System& system, Handle* out_handle) { SCOPE_EXIT({ resource_limit->Close(); }); // Initialize the resource limit. - resource_limit->Initialize(std::addressof(system.CoreTiming())); + resource_limit->Initialize(); // Register the limit. KResourceLimit::Register(kernel, resource_limit); diff --git a/src/core/hle/kernel/svc/svc_synchronization.cpp b/src/core/hle/kernel/svc/svc_synchronization.cpp index 366e8ed4a..8ebc1bd1c 100644 --- a/src/core/hle/kernel/svc/svc_synchronization.cpp +++ b/src/core/hle/kernel/svc/svc_synchronization.cpp @@ -4,6 +4,7 @@ #include "common/scope_exit.h" #include "common/scratch_buffer.h" #include "core/core.h" +#include "core/hle/kernel/k_hardware_timer.h" #include "core/hle/kernel/k_process.h" #include "core/hle/kernel/k_readable_event.h" #include "core/hle/kernel/svc.h" @@ -83,9 +84,20 @@ Result WaitSynchronization(Core::System& system, int32_t* out_index, u64 user_ha } }); + // Convert the timeout from nanoseconds to ticks. + s64 timeout; + if (timeout_ns > 0) { + u64 ticks = kernel.HardwareTimer().GetTick(); + ticks += timeout_ns; + ticks += 2; + + timeout = ticks; + } else { + timeout = timeout_ns; + } + // Wait on the objects. - Result res = - KSynchronizationObject::Wait(kernel, out_index, objs.data(), num_handles, timeout_ns); + Result res = KSynchronizationObject::Wait(kernel, out_index, objs.data(), num_handles, timeout); R_SUCCEED_IF(res == ResultSessionClosed); R_RETURN(res); diff --git a/src/core/hle/kernel/svc/svc_thread.cpp b/src/core/hle/kernel/svc/svc_thread.cpp index 92bcea72b..933b82e30 100644 --- a/src/core/hle/kernel/svc/svc_thread.cpp +++ b/src/core/hle/kernel/svc/svc_thread.cpp @@ -4,6 +4,7 @@ #include "common/scope_exit.h" #include "core/core.h" #include "core/core_timing.h" +#include "core/hle/kernel/k_hardware_timer.h" #include "core/hle/kernel/k_process.h" #include "core/hle/kernel/k_scoped_resource_reservation.h" #include "core/hle/kernel/k_thread.h" @@ -42,9 +43,9 @@ Result CreateThread(Core::System& system, Handle* out_handle, u64 entry_point, u R_UNLESS(process.CheckThreadPriority(priority), ResultInvalidPriority); // Reserve a new thread from the process resource limit (waiting up to 100ms). - KScopedResourceReservation thread_reservation( - std::addressof(process), LimitableResource::ThreadCountMax, 1, - system.CoreTiming().GetGlobalTimeNs().count() + 100000000); + KScopedResourceReservation thread_reservation(std::addressof(process), + LimitableResource::ThreadCountMax, 1, + kernel.HardwareTimer().GetTick() + 100000000); R_UNLESS(thread_reservation.Succeeded(), ResultLimitReached); // Create the thread. @@ -102,20 +103,31 @@ void ExitThread(Core::System& system) { } /// Sleep the current thread -void SleepThread(Core::System& system, s64 nanoseconds) { +void SleepThread(Core::System& system, s64 ns) { auto& kernel = system.Kernel(); - const auto yield_type = static_cast<Svc::YieldType>(nanoseconds); + const auto yield_type = static_cast<Svc::YieldType>(ns); - LOG_TRACE(Kernel_SVC, "called nanoseconds={}", nanoseconds); + LOG_TRACE(Kernel_SVC, "called nanoseconds={}", ns); // When the input tick is positive, sleep. - if (nanoseconds > 0) { + if (ns > 0) { // Convert the timeout from nanoseconds to ticks. // NOTE: Nintendo does not use this conversion logic in WaitSynchronization... + s64 timeout; + + const s64 offset_tick(ns); + if (offset_tick > 0) { + timeout = kernel.HardwareTimer().GetTick() + offset_tick + 2; + if (timeout <= 0) { + timeout = std::numeric_limits<s64>::max(); + } + } else { + timeout = std::numeric_limits<s64>::max(); + } // Sleep. // NOTE: Nintendo does not check the result of this sleep. - static_cast<void>(GetCurrentThread(kernel).Sleep(nanoseconds)); + static_cast<void>(GetCurrentThread(kernel).Sleep(timeout)); } else if (yield_type == Svc::YieldType::WithoutCoreMigration) { KScheduler::YieldWithoutCoreMigration(kernel); } else if (yield_type == Svc::YieldType::WithCoreMigration) { @@ -124,7 +136,6 @@ void SleepThread(Core::System& system, s64 nanoseconds) { KScheduler::YieldToAnyThread(kernel); } else { // Nintendo does nothing at all if an otherwise invalid value is passed. - ASSERT_MSG(false, "Unimplemented sleep yield type '{:016X}'!", nanoseconds); } } diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 240f06689..dd0b27f47 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -62,7 +62,7 @@ enum class ErrorModule : u32 { XCD = 108, TMP451 = 109, NIFM = 110, - Hwopus = 111, + HwOpus = 111, LSM6DS3 = 112, Bluetooth = 113, VI = 114, @@ -283,159 +283,6 @@ private: u32 description_end; }; -/** - * This is an optional value type. It holds a `Result` and, if that code is ResultSuccess, it - * also holds a result of type `T`. If the code is an error code (not ResultSuccess), then trying - * to access the inner value with operator* is undefined behavior and will assert with Unwrap(). - * Users of this class must be cognizant to check the status of the ResultVal with operator bool(), - * Code(), Succeeded() or Failed() prior to accessing the inner value. - * - * An example of how it could be used: - * \code - * ResultVal<int> Frobnicate(float strength) { - * if (strength < 0.f || strength > 1.0f) { - * // Can't frobnicate too weakly or too strongly - * return Result{ErrorModule::Common, 1}; - * } else { - * // Frobnicated! Give caller a cookie - * return 42; - * } - * } - * \endcode - * - * \code - * auto frob_result = Frobnicate(0.75f); - * if (frob_result) { - * // Frobbed ok - * printf("My cookie is %d\n", *frob_result); - * } else { - * printf("Guess I overdid it. :( Error code: %ux\n", frob_result.Code().raw); - * } - * \endcode - */ -template <typename T> -class ResultVal { -public: - constexpr ResultVal() : expected{} {} - - constexpr ResultVal(Result code) : expected{Common::Unexpected(code)} {} - - constexpr ResultVal(ResultRange range) : expected{Common::Unexpected(range)} {} - - template <typename U> - constexpr ResultVal(U&& val) : expected{std::forward<U>(val)} {} - - template <typename... Args> - constexpr ResultVal(Args&&... args) : expected{std::in_place, std::forward<Args>(args)...} {} - - ~ResultVal() = default; - - constexpr ResultVal(const ResultVal&) = default; - constexpr ResultVal(ResultVal&&) = default; - - ResultVal& operator=(const ResultVal&) = default; - ResultVal& operator=(ResultVal&&) = default; - - [[nodiscard]] constexpr explicit operator bool() const noexcept { - return expected.has_value(); - } - - [[nodiscard]] constexpr Result Code() const { - return expected.has_value() ? ResultSuccess : expected.error(); - } - - [[nodiscard]] constexpr bool Succeeded() const { - return expected.has_value(); - } - - [[nodiscard]] constexpr bool Failed() const { - return !expected.has_value(); - } - - [[nodiscard]] constexpr T* operator->() { - return std::addressof(expected.value()); - } - - [[nodiscard]] constexpr const T* operator->() const { - return std::addressof(expected.value()); - } - - [[nodiscard]] constexpr T& operator*() & { - return *expected; - } - - [[nodiscard]] constexpr const T& operator*() const& { - return *expected; - } - - [[nodiscard]] constexpr T&& operator*() && { - return *expected; - } - - [[nodiscard]] constexpr const T&& operator*() const&& { - return *expected; - } - - [[nodiscard]] constexpr T& Unwrap() & { - ASSERT_MSG(Succeeded(), "Tried to Unwrap empty ResultVal"); - return expected.value(); - } - - [[nodiscard]] constexpr const T& Unwrap() const& { - ASSERT_MSG(Succeeded(), "Tried to Unwrap empty ResultVal"); - return expected.value(); - } - - [[nodiscard]] constexpr T&& Unwrap() && { - ASSERT_MSG(Succeeded(), "Tried to Unwrap empty ResultVal"); - return std::move(expected.value()); - } - - [[nodiscard]] constexpr const T&& Unwrap() const&& { - ASSERT_MSG(Succeeded(), "Tried to Unwrap empty ResultVal"); - return std::move(expected.value()); - } - - template <typename U> - [[nodiscard]] constexpr T ValueOr(U&& v) const& { - return expected.value_or(v); - } - - template <typename U> - [[nodiscard]] constexpr T ValueOr(U&& v) && { - return expected.value_or(v); - } - -private: - // TODO (Morph): Replace this with C++23 std::expected. - Common::Expected<T, Result> expected; -}; - -/** - * Check for the success of `source` (which must evaluate to a ResultVal). If it succeeds, unwraps - * the contained value and assigns it to `target`, which can be either an l-value expression or a - * variable declaration. If it fails the return code is returned from the current function. Thus it - * can be used to cascade errors out, achieving something akin to exception handling. - */ -#define CASCADE_RESULT(target, source) \ - auto CONCAT2(check_result_L, __LINE__) = source; \ - if (CONCAT2(check_result_L, __LINE__).Failed()) { \ - return CONCAT2(check_result_L, __LINE__).Code(); \ - } \ - target = std::move(*CONCAT2(check_result_L, __LINE__)) - -/** - * Analogous to CASCADE_RESULT, but for a bare Result. The code will be propagated if - * non-success, or discarded otherwise. - */ -#define CASCADE_CODE(source) \ - do { \ - auto CONCAT2(check_result_L, __LINE__) = source; \ - if (CONCAT2(check_result_L, __LINE__).IsError()) { \ - return CONCAT2(check_result_L, __LINE__); \ - } \ - } while (false) - #define R_SUCCEEDED(res) (static_cast<Result>(res).IsSuccess()) #define R_FAILED(res) (static_cast<Result>(res).IsFailure()) diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp index 2632cd3ef..b971401e6 100644 --- a/src/core/hle/service/acc/acc.cpp +++ b/src/core/hle/service/acc/acc.cpp @@ -765,15 +765,16 @@ Result Module::Interface::InitializeApplicationInfoBase() { // TODO(ogniK): This should be changed to reflect the target process for when we have multiple // processes emulated. As we don't actually have pid support we should assume we're just using // our own process - const auto launch_property = - system.GetARPManager().GetLaunchProperty(system.GetApplicationProcessProgramID()); + Glue::ApplicationLaunchProperty launch_property{}; + const auto result = system.GetARPManager().GetLaunchProperty( + &launch_property, system.GetApplicationProcessProgramID()); - if (launch_property.Failed()) { + if (result != ResultSuccess) { LOG_ERROR(Service_ACC, "Failed to get launch property"); return Account::ResultInvalidApplication; } - switch (launch_property->base_game_storage_id) { + switch (launch_property.base_game_storage_id) { case FileSys::StorageId::GameCard: application_info.application_type = ApplicationType::GameCard; break; @@ -785,7 +786,7 @@ Result Module::Interface::InitializeApplicationInfoBase() { break; default: LOG_ERROR(Service_ACC, "Invalid game storage ID! storage_id={}", - launch_property->base_game_storage_id); + launch_property.base_game_storage_id); return Account::ResultInvalidApplication; } diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 4f400d341..8ffdd19e7 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -6,6 +6,7 @@ #include <cinttypes> #include <cstring> #include "common/settings.h" +#include "common/settings_enums.h" #include "core/core.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" @@ -45,7 +46,7 @@ constexpr Result ResultNoMessages{ErrorModule::AM, 3}; constexpr Result ResultInvalidOffset{ErrorModule::AM, 503}; enum class LaunchParameterKind : u32 { - ApplicationSpecific = 1, + UserChannel = 1, AccountPreselectedUser = 2, }; @@ -340,7 +341,7 @@ void ISelfController::Exit(HLERequestContext& ctx) { void ISelfController::LockExit(HLERequestContext& ctx) { LOG_DEBUG(Service_AM, "called"); - system.SetExitLock(true); + system.SetExitLocked(true); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -349,10 +350,14 @@ void ISelfController::LockExit(HLERequestContext& ctx) { void ISelfController::UnlockExit(HLERequestContext& ctx) { LOG_DEBUG(Service_AM, "called"); - system.SetExitLock(false); + system.SetExitLocked(false); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); + + if (system.GetExitRequested()) { + system.Exit(); + } } void ISelfController::EnterFatalSection(HLERequestContext& ctx) { @@ -833,7 +838,7 @@ void ICommonStateGetter::GetDefaultDisplayResolution(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); - if (Settings::values.use_docked_mode.GetValue()) { + if (Settings::IsDockedMode()) { rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth)); rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight)); } else { @@ -921,7 +926,7 @@ void IStorage::Open(HLERequestContext& ctx) { } void ICommonStateGetter::GetOperationMode(HLERequestContext& ctx) { - const bool use_docked_mode{Settings::values.use_docked_mode.GetValue()}; + const bool use_docked_mode{Settings::IsDockedMode()}; LOG_DEBUG(Service_AM, "called, use_docked_mode={}", use_docked_mode); IPC::ResponseBuilder rb{ctx, 3}; @@ -1317,6 +1322,50 @@ void ILibraryAppletCreator::CreateHandleStorage(HLERequestContext& ctx) { rb.PushIpcInterface<IStorage>(system, std::move(memory)); } +ILibraryAppletSelfAccessor::ILibraryAppletSelfAccessor(Core::System& system_) + : ServiceFramework{system_, "ILibraryAppletSelfAccessor"} { + static const FunctionInfo functions[] = { + {0, nullptr, "PopInData"}, + {1, nullptr, "PushOutData"}, + {2, nullptr, "PopInteractiveInData"}, + {3, nullptr, "PushInteractiveOutData"}, + {5, nullptr, "GetPopInDataEvent"}, + {6, nullptr, "GetPopInteractiveInDataEvent"}, + {10, nullptr, "ExitProcessAndReturn"}, + {11, nullptr, "GetLibraryAppletInfo"}, + {12, nullptr, "GetMainAppletIdentityInfo"}, + {13, nullptr, "CanUseApplicationCore"}, + {14, nullptr, "GetCallerAppletIdentityInfo"}, + {15, nullptr, "GetMainAppletApplicationControlProperty"}, + {16, nullptr, "GetMainAppletStorageId"}, + {17, nullptr, "GetCallerAppletIdentityInfoStack"}, + {18, nullptr, "GetNextReturnDestinationAppletIdentityInfo"}, + {19, nullptr, "GetDesirableKeyboardLayout"}, + {20, nullptr, "PopExtraStorage"}, + {25, nullptr, "GetPopExtraStorageEvent"}, + {30, nullptr, "UnpopInData"}, + {31, nullptr, "UnpopExtraStorage"}, + {40, nullptr, "GetIndirectLayerProducerHandle"}, + {50, nullptr, "ReportVisibleError"}, + {51, nullptr, "ReportVisibleErrorWithErrorContext"}, + {60, nullptr, "GetMainAppletApplicationDesiredLanguage"}, + {70, nullptr, "GetCurrentApplicationId"}, + {80, nullptr, "RequestExitToSelf"}, + {90, nullptr, "CreateApplicationAndPushAndRequestToLaunch"}, + {100, nullptr, "CreateGameMovieTrimmer"}, + {101, nullptr, "ReserveResourceForMovieOperation"}, + {102, nullptr, "UnreserveResourceForMovieOperation"}, + {110, nullptr, "GetMainAppletAvailableUsers"}, + {120, nullptr, "GetLaunchStorageInfoForDebug"}, + {130, nullptr, "GetGpuErrorDetectedSystemEvent"}, + {140, nullptr, "SetApplicationMemoryReservation"}, + {150, nullptr, "ShouldSetGpuTimeSliceManually"}, + }; + RegisterHandlers(functions); +} + +ILibraryAppletSelfAccessor::~ILibraryAppletSelfAccessor() = default; + IApplicationFunctions::IApplicationFunctions(Core::System& system_) : ServiceFramework{system_, "IApplicationFunctions"}, service_context{system, "IApplicationFunctions"} { @@ -1337,7 +1386,7 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_) {25, &IApplicationFunctions::ExtendSaveData, "ExtendSaveData"}, {26, &IApplicationFunctions::GetSaveDataSize, "GetSaveDataSize"}, {27, &IApplicationFunctions::CreateCacheStorage, "CreateCacheStorage"}, - {28, nullptr, "GetSaveDataSizeMax"}, + {28, &IApplicationFunctions::GetSaveDataSizeMax, "GetSaveDataSizeMax"}, {29, nullptr, "GetCacheStorageMax"}, {30, &IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed, "BeginBlockingHomeButtonShortAndLongPressed"}, {31, &IApplicationFunctions::EndBlockingHomeButtonShortAndLongPressed, "EndBlockingHomeButtonShortAndLongPressed"}, @@ -1469,27 +1518,26 @@ void IApplicationFunctions::PopLaunchParameter(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto kind = rp.PopEnum<LaunchParameterKind>(); - LOG_DEBUG(Service_AM, "called, kind={:08X}", kind); - - if (kind == LaunchParameterKind::ApplicationSpecific && !launch_popped_application_specific) { - const auto backend = BCAT::CreateBackendFromSettings(system, [this](u64 tid) { - return system.GetFileSystemController().GetBCATDirectory(tid); - }); - const auto build_id_full = system.GetApplicationProcessBuildID(); - u64 build_id{}; - std::memcpy(&build_id, build_id_full.data(), sizeof(u64)); - - auto data = - backend->GetLaunchParameter({system.GetApplicationProcessProgramID(), build_id}); - if (data.has_value()) { - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(ResultSuccess); - rb.PushIpcInterface<IStorage>(system, std::move(*data)); - launch_popped_application_specific = true; + LOG_INFO(Service_AM, "called, kind={:08X}", kind); + + if (kind == LaunchParameterKind::UserChannel) { + auto channel = system.GetUserChannel(); + if (channel.empty()) { + LOG_ERROR(Service_AM, "Attempted to load launch parameter but none was found!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(AM::ResultNoDataInChannel); return; } + + auto data = channel.back(); + channel.pop_back(); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface<IStorage>(system, std::move(data)); } else if (kind == LaunchParameterKind::AccountPreselectedUser && !launch_popped_account_preselect) { + // TODO: Verify this is hw-accurate LaunchParameterAccountPreselectedUser params{}; params.magic = LAUNCH_PARAMETER_ACCOUNT_PRESELECTED_USER_MAGIC; @@ -1501,7 +1549,6 @@ void IApplicationFunctions::PopLaunchParameter(HLERequestContext& ctx) { params.current_user = *uuid; IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(ResultSuccess); std::vector<u8> buffer(sizeof(LaunchParameterAccountPreselectedUser)); @@ -1509,12 +1556,11 @@ void IApplicationFunctions::PopLaunchParameter(HLERequestContext& ctx) { rb.PushIpcInterface<IStorage>(system, std::move(buffer)); launch_popped_account_preselect = true; - return; + } else { + LOG_ERROR(Service_AM, "Unknown launch parameter kind."); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(AM::ResultNoDataInChannel); } - - LOG_ERROR(Service_AM, "Attempted to load launch parameter but none was found!"); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(AM::ResultNoDataInChannel); } void IApplicationFunctions::CreateApplicationAndRequestToStartForQuest(HLERequestContext& ctx) { @@ -1534,11 +1580,13 @@ void IApplicationFunctions::EnsureSaveData(HLERequestContext& ctx) { attribute.title_id = system.GetApplicationProcessProgramID(); attribute.user_id = user_id; attribute.type = FileSys::SaveDataType::SaveData; + + FileSys::VirtualDir save_data{}; const auto res = system.GetFileSystemController().CreateSaveData( - FileSys::SaveDataSpaceId::NandUser, attribute); + &save_data, FileSys::SaveDataSpaceId::NandUser, attribute); IPC::ResponseBuilder rb{ctx, 4}; - rb.Push(res.Code()); + rb.Push(res); rb.Push<u64>(0); } @@ -1623,26 +1671,30 @@ void IApplicationFunctions::GetDesiredLanguage(HLERequestContext& ctx) { auto app_man = ns_am2->GetApplicationManagerInterface(); // Get desired application language - const auto res_lang = app_man->GetApplicationDesiredLanguage(supported_languages); - if (res_lang.Failed()) { + u8 desired_language{}; + const auto res_lang = + app_man->GetApplicationDesiredLanguage(&desired_language, supported_languages); + if (res_lang != ResultSuccess) { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(res_lang.Code()); + rb.Push(res_lang); return; } // Convert to settings language code. - const auto res_code = app_man->ConvertApplicationLanguageToLanguageCode(*res_lang); - if (res_code.Failed()) { + u64 language_code{}; + const auto res_code = + app_man->ConvertApplicationLanguageToLanguageCode(&language_code, desired_language); + if (res_code != ResultSuccess) { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(res_code.Code()); + rb.Push(res_code); return; } - LOG_DEBUG(Service_AM, "got desired_language={:016X}", *res_code); + LOG_DEBUG(Service_AM, "got desired_language={:016X}", language_code); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); - rb.Push(*res_code); + rb.Push(language_code); } void IApplicationFunctions::IsGamePlayRecordingSupported(HLERequestContext& ctx) { @@ -1769,6 +1821,18 @@ void IApplicationFunctions::CreateCacheStorage(HLERequestContext& ctx) { rb.PushRaw(resp); } +void IApplicationFunctions::GetSaveDataSizeMax(HLERequestContext& ctx) { + LOG_WARNING(Service_AM, "(STUBBED) called"); + + constexpr u64 size_max_normal = 0xFFFFFFF; + constexpr u64 size_max_journal = 0xFFFFFFF; + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(ResultSuccess); + rb.Push(size_max_normal); + rb.Push(size_max_journal); +} + void IApplicationFunctions::QueryApplicationPlayStatistics(HLERequestContext& ctx) { LOG_WARNING(Service_AM, "(STUBBED) called"); @@ -1800,14 +1864,22 @@ void IApplicationFunctions::ExecuteProgram(HLERequestContext& ctx) { } void IApplicationFunctions::ClearUserChannel(HLERequestContext& ctx) { - LOG_WARNING(Service_AM, "(STUBBED) called"); + LOG_DEBUG(Service_AM, "called"); + + system.GetUserChannel().clear(); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } void IApplicationFunctions::UnpopToUserChannel(HLERequestContext& ctx) { - LOG_WARNING(Service_AM, "(STUBBED) called"); + LOG_DEBUG(Service_AM, "called"); + + IPC::RequestParser rp{ctx}; + const auto storage = rp.PopIpcInterface<IStorage>().lock(); + if (storage) { + system.GetUserChannel().push_back(storage->GetData()); + } IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index d4fd163da..f86841c60 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -22,30 +22,6 @@ class Nvnflinger; namespace Service::AM { -// This is nn::settings::Language -enum SystemLanguage { - Japanese = 0, - English = 1, // en-US - French = 2, - German = 3, - Italian = 4, - Spanish = 5, - Chinese = 6, - Korean = 7, - Dutch = 8, - Portuguese = 9, - Russian = 10, - Taiwanese = 11, - BritishEnglish = 12, // en-GB - CanadianFrench = 13, - LatinAmericanSpanish = 14, // es-419 - // 4.0.0+ - SimplifiedChinese = 15, - TraditionalChinese = 16, - // 10.1.0+ - BrazilianPortuguese = 17, -}; - class AppletMessageQueue { public: // This is nn::am::AppletMessage @@ -314,6 +290,12 @@ private: void CreateHandleStorage(HLERequestContext& ctx); }; +class ILibraryAppletSelfAccessor final : public ServiceFramework<ILibraryAppletSelfAccessor> { +public: + explicit ILibraryAppletSelfAccessor(Core::System& system_); + ~ILibraryAppletSelfAccessor() override; +}; + class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> { public: explicit IApplicationFunctions(Core::System& system_); @@ -334,6 +316,7 @@ private: void ExtendSaveData(HLERequestContext& ctx); void GetSaveDataSize(HLERequestContext& ctx); void CreateCacheStorage(HLERequestContext& ctx); + void GetSaveDataSizeMax(HLERequestContext& ctx); void BeginBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx); void EndBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx); void BeginBlockingHomeButton(HLERequestContext& ctx); @@ -357,7 +340,6 @@ private: KernelHelpers::ServiceContext service_context; - bool launch_popped_application_specific = false; bool launch_popped_account_preselect = false; s32 previous_program_index{-1}; Kernel::KEvent* gpu_error_detected_event; diff --git a/src/core/hle/service/am/applet_ae.cpp b/src/core/hle/service/am/applet_ae.cpp index 2764f7ceb..ee9d99a54 100644 --- a/src/core/hle/service/am/applet_ae.cpp +++ b/src/core/hle/service/am/applet_ae.cpp @@ -26,8 +26,10 @@ public: {4, &ILibraryAppletProxy::GetDisplayController, "GetDisplayController"}, {10, &ILibraryAppletProxy::GetProcessWindingController, "GetProcessWindingController"}, {11, &ILibraryAppletProxy::GetLibraryAppletCreator, "GetLibraryAppletCreator"}, - {20, &ILibraryAppletProxy::GetApplicationFunctions, "GetApplicationFunctions"}, + {20, &ILibraryAppletProxy::OpenLibraryAppletSelfAccessor, "OpenLibraryAppletSelfAccessor"}, {21, nullptr, "GetAppletCommonFunctions"}, + {22, nullptr, "GetHomeMenuFunctions"}, + {23, nullptr, "GetGlobalStateController"}, {1000, &ILibraryAppletProxy::GetDebugFunctions, "GetDebugFunctions"}, }; // clang-format on @@ -100,12 +102,12 @@ private: rb.PushIpcInterface<ILibraryAppletCreator>(system); } - void GetApplicationFunctions(HLERequestContext& ctx) { + void OpenLibraryAppletSelfAccessor(HLERequestContext& ctx) { LOG_DEBUG(Service_AM, "called"); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); - rb.PushIpcInterface<IApplicationFunctions>(system); + rb.PushIpcInterface<ILibraryAppletSelfAccessor>(system); } Nvnflinger::Nvnflinger& nvnflinger; diff --git a/src/core/hle/service/am/applets/applet_mii_edit.cpp b/src/core/hle/service/am/applets/applet_mii_edit.cpp index d1f652c09..ff77830d2 100644 --- a/src/core/hle/service/am/applets/applet_mii_edit.cpp +++ b/src/core/hle/service/am/applets/applet_mii_edit.cpp @@ -7,7 +7,9 @@ #include "core/frontend/applets/mii_edit.h" #include "core/hle/service/am/am.h" #include "core/hle/service/am/applets/applet_mii_edit.h" +#include "core/hle/service/mii/mii.h" #include "core/hle/service/mii/mii_manager.h" +#include "core/hle/service/sm/sm.h" namespace Service::AM::Applets { @@ -56,6 +58,12 @@ void MiiEdit::Initialize() { sizeof(MiiEditAppletInputV4)); break; } + + manager = system.ServiceManager().GetService<Mii::MiiDBModule>("mii:e")->GetMiiManager(); + if (manager == nullptr) { + manager = std::make_shared<Mii::MiiManager>(); + } + manager->Initialize(metadata); } bool MiiEdit::TransactionComplete() const { @@ -78,22 +86,49 @@ void MiiEdit::Execute() { // This is a default stub for each of the MiiEdit applet modes. switch (applet_input_common.applet_mode) { case MiiEditAppletMode::ShowMiiEdit: - case MiiEditAppletMode::AppendMii: case MiiEditAppletMode::AppendMiiImage: case MiiEditAppletMode::UpdateMiiImage: MiiEditOutput(MiiEditResult::Success, 0); break; - case MiiEditAppletMode::CreateMii: - case MiiEditAppletMode::EditMii: { - Service::Mii::MiiManager mii_manager; + case MiiEditAppletMode::AppendMii: { + Mii::StoreData store_data{}; + store_data.BuildRandom(Mii::Age::All, Mii::Gender::All, Mii::Race::All); + store_data.SetNickname({u'y', u'u', u'z', u'u'}); + store_data.SetChecksum(); + const auto result = manager->AddOrReplace(metadata, store_data); + + if (result.IsError()) { + MiiEditOutput(MiiEditResult::Cancel, 0); + break; + } + + s32 index = manager->FindIndex(store_data.GetCreateId(), false); + + if (index == -1) { + MiiEditOutput(MiiEditResult::Cancel, 0); + break; + } + + MiiEditOutput(MiiEditResult::Success, index); + break; + } + case MiiEditAppletMode::CreateMii: { + Mii::CharInfo char_info{}; + manager->BuildRandom(char_info, Mii::Age::All, Mii::Gender::All, Mii::Race::All); - const MiiEditCharInfo char_info{ - .mii_info{applet_input_common.applet_mode == MiiEditAppletMode::EditMii - ? applet_input_v4.char_info.mii_info - : mii_manager.BuildDefault(0)}, + const MiiEditCharInfo edit_char_info{ + .mii_info{char_info}, + }; + + MiiEditOutputForCharInfoEditing(MiiEditResult::Success, edit_char_info); + break; + } + case MiiEditAppletMode::EditMii: { + const MiiEditCharInfo edit_char_info{ + .mii_info{applet_input_v4.char_info.mii_info}, }; - MiiEditOutputForCharInfoEditing(MiiEditResult::Success, char_info); + MiiEditOutputForCharInfoEditing(MiiEditResult::Success, edit_char_info); break; } default: diff --git a/src/core/hle/service/am/applets/applet_mii_edit.h b/src/core/hle/service/am/applets/applet_mii_edit.h index 3f46fae1b..7ff34af49 100644 --- a/src/core/hle/service/am/applets/applet_mii_edit.h +++ b/src/core/hle/service/am/applets/applet_mii_edit.h @@ -11,6 +11,11 @@ namespace Core { class System; } // namespace Core +namespace Service::Mii { +struct DatabaseSessionMetadata; +class MiiManager; +} // namespace Service::Mii + namespace Service::AM::Applets { class MiiEdit final : public Applet { @@ -40,6 +45,8 @@ private: MiiEditAppletInputV4 applet_input_v4{}; bool is_complete{false}; + std::shared_ptr<Mii::MiiManager> manager = nullptr; + Mii::DatabaseSessionMetadata metadata{}; }; } // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/applet_mii_edit_types.h b/src/core/hle/service/am/applets/applet_mii_edit_types.h index 4705d019f..f3d764073 100644 --- a/src/core/hle/service/am/applets/applet_mii_edit_types.h +++ b/src/core/hle/service/am/applets/applet_mii_edit_types.h @@ -7,7 +7,8 @@ #include "common/common_funcs.h" #include "common/common_types.h" -#include "core/hle/service/mii/types.h" +#include "common/uuid.h" +#include "core/hle/service/mii/types/char_info.h" namespace Service::AM::Applets { diff --git a/src/core/hle/service/am/applets/applet_web_browser.cpp b/src/core/hle/service/am/applets/applet_web_browser.cpp index 2accf7898..1c9a1dc29 100644 --- a/src/core/hle/service/am/applets/applet_web_browser.cpp +++ b/src/core/hle/service/am/applets/applet_web_browser.cpp @@ -139,7 +139,7 @@ FileSys::VirtualFile GetOfflineRomFS(Core::System& system, u64 title_id, const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), system.GetContentProvider()}; - return pm.PatchRomFS(nca->GetRomFS(), nca->GetBaseIVFCOffset(), nca_type); + return pm.PatchRomFS(nca.get(), nca->GetRomFS(), nca_type); } } diff --git a/src/core/hle/service/aoc/aoc_u.cpp b/src/core/hle/service/aoc/aoc_u.cpp index 38c2138e8..7075ab800 100644 --- a/src/core/hle/service/aoc/aoc_u.cpp +++ b/src/core/hle/service/aoc/aoc_u.cpp @@ -22,6 +22,8 @@ namespace Service::AOC { +constexpr Result ResultNoPurchasedProductInfoAvailable{ErrorModule::NIMShop, 400}; + static bool CheckAOCTitleIDMatchesBase(u64 title_id, u64 base) { return FileSys::GetBaseTitleID(title_id) == base; } @@ -54,8 +56,8 @@ public: {0, &IPurchaseEventManager::SetDefaultDeliveryTarget, "SetDefaultDeliveryTarget"}, {1, &IPurchaseEventManager::SetDeliveryTarget, "SetDeliveryTarget"}, {2, &IPurchaseEventManager::GetPurchasedEventReadableHandle, "GetPurchasedEventReadableHandle"}, - {3, nullptr, "PopPurchasedProductInfo"}, - {4, nullptr, "PopPurchasedProductInfoWithUid"}, + {3, &IPurchaseEventManager::PopPurchasedProductInfo, "PopPurchasedProductInfo"}, + {4, &IPurchaseEventManager::PopPurchasedProductInfoWithUid, "PopPurchasedProductInfoWithUid"}, }; // clang-format on @@ -101,6 +103,20 @@ private: rb.PushCopyObjects(purchased_event->GetReadableEvent()); } + void PopPurchasedProductInfo(HLERequestContext& ctx) { + LOG_DEBUG(Service_AOC, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultNoPurchasedProductInfoAvailable); + } + + void PopPurchasedProductInfoWithUid(HLERequestContext& ctx) { + LOG_DEBUG(Service_AOC, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultNoPurchasedProductInfoAvailable); + } + KernelHelpers::ServiceContext service_context; Kernel::KEvent* purchased_event; diff --git a/src/core/hle/service/apm/apm_controller.cpp b/src/core/hle/service/apm/apm_controller.cpp index 227fdd0cf..4f1aa5cc2 100644 --- a/src/core/hle/service/apm/apm_controller.cpp +++ b/src/core/hle/service/apm/apm_controller.cpp @@ -7,6 +7,7 @@ #include "common/logging/log.h" #include "common/settings.h" +#include "common/settings_enums.h" #include "core/core_timing.h" #include "core/hle/service/apm/apm_controller.h" @@ -67,8 +68,7 @@ void Controller::SetFromCpuBoostMode(CpuBoostMode mode) { } PerformanceMode Controller::GetCurrentPerformanceMode() const { - return Settings::values.use_docked_mode.GetValue() ? PerformanceMode::Boost - : PerformanceMode::Normal; + return Settings::IsDockedMode() ? PerformanceMode::Boost : PerformanceMode::Normal; } PerformanceConfiguration Controller::GetCurrentPerformanceConfiguration(PerformanceMode mode) { diff --git a/src/core/hle/service/audio/audctl.cpp b/src/core/hle/service/audio/audctl.cpp index 7ad93be6b..66dd64fd1 100644 --- a/src/core/hle/service/audio/audctl.cpp +++ b/src/core/hle/service/audio/audctl.cpp @@ -22,13 +22,13 @@ AudCtl::AudCtl(Core::System& system_) : ServiceFramework{system_, "audctl"} { {9, nullptr, "GetAudioOutputMode"}, {10, nullptr, "SetAudioOutputMode"}, {11, nullptr, "SetForceMutePolicy"}, - {12, nullptr, "GetForceMutePolicy"}, - {13, nullptr, "GetOutputModeSetting"}, + {12, &AudCtl::GetForceMutePolicy, "GetForceMutePolicy"}, + {13, &AudCtl::GetOutputModeSetting, "GetOutputModeSetting"}, {14, nullptr, "SetOutputModeSetting"}, {15, nullptr, "SetOutputTarget"}, {16, nullptr, "SetInputTargetForceEnabled"}, {17, nullptr, "SetHeadphoneOutputLevelMode"}, - {18, nullptr, "GetHeadphoneOutputLevelMode"}, + {18, &AudCtl::GetHeadphoneOutputLevelMode, "GetHeadphoneOutputLevelMode"}, {19, nullptr, "AcquireAudioVolumeUpdateEventForPlayReport"}, {20, nullptr, "AcquireAudioOutputDeviceUpdateEventForPlayReport"}, {21, nullptr, "GetAudioOutputTargetForPlayReport"}, @@ -41,7 +41,7 @@ AudCtl::AudCtl(Core::System& system_) : ServiceFramework{system_, "audctl"} { {28, nullptr, "GetAudioOutputChannelCountForPlayReport"}, {29, nullptr, "BindAudioOutputChannelCountUpdateEventForPlayReport"}, {30, nullptr, "SetSpeakerAutoMuteEnabled"}, - {31, nullptr, "IsSpeakerAutoMuteEnabled"}, + {31, &AudCtl::IsSpeakerAutoMuteEnabled, "IsSpeakerAutoMuteEnabled"}, {32, nullptr, "GetActiveOutputTarget"}, {33, nullptr, "GetTargetDeviceInfo"}, {34, nullptr, "AcquireTargetNotification"}, @@ -96,4 +96,42 @@ void AudCtl::GetTargetVolumeMax(HLERequestContext& ctx) { rb.Push(target_max_volume); } +void AudCtl::GetForceMutePolicy(HLERequestContext& ctx) { + LOG_WARNING(Audio, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushEnum(ForceMutePolicy::Disable); +} + +void AudCtl::GetOutputModeSetting(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto value = rp.Pop<u32>(); + + LOG_WARNING(Audio, "(STUBBED) called, value={}", value); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushEnum(AudioOutputMode::PcmAuto); +} + +void AudCtl::GetHeadphoneOutputLevelMode(HLERequestContext& ctx) { + LOG_WARNING(Audio, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushEnum(HeadphoneOutputLevelMode::Normal); +} + +void AudCtl::IsSpeakerAutoMuteEnabled(HLERequestContext& ctx) { + const bool is_speaker_auto_mute_enabled = false; + + LOG_WARNING(Audio, "(STUBBED) called, is_speaker_auto_mute_enabled={}", + is_speaker_auto_mute_enabled); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push<u8>(is_speaker_auto_mute_enabled); +} + } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audctl.h b/src/core/hle/service/audio/audctl.h index 8e31ac237..d57abb383 100644 --- a/src/core/hle/service/audio/audctl.h +++ b/src/core/hle/service/audio/audctl.h @@ -17,8 +17,30 @@ public: ~AudCtl() override; private: + enum class AudioOutputMode { + Invalid, + Pcm1ch, + Pcm2ch, + Pcm6ch, + PcmAuto, + }; + + enum class ForceMutePolicy { + Disable, + SpeakerMuteOnHeadphoneUnplugged, + }; + + enum class HeadphoneOutputLevelMode { + Normal, + HighPower, + }; + void GetTargetVolumeMin(HLERequestContext& ctx); void GetTargetVolumeMax(HLERequestContext& ctx); + void GetForceMutePolicy(HLERequestContext& ctx); + void GetOutputModeSetting(HLERequestContext& ctx); + void GetHeadphoneOutputLevelMode(HLERequestContext& ctx); + void IsSpeakerAutoMuteEnabled(HLERequestContext& ctx); }; } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audin_u.cpp b/src/core/hle/service/audio/audin_u.cpp index 526a39130..56fee4591 100644 --- a/src/core/hle/service/audio/audin_u.cpp +++ b/src/core/hle/service/audio/audin_u.cpp @@ -220,7 +220,7 @@ AudInU::AudInU(Core::System& system_) AudInU::~AudInU() = default; void AudInU::ListAudioIns(HLERequestContext& ctx) { - using namespace AudioCore::AudioRenderer; + using namespace AudioCore::Renderer; LOG_DEBUG(Service_Audio, "called"); @@ -240,7 +240,7 @@ void AudInU::ListAudioIns(HLERequestContext& ctx) { } void AudInU::ListAudioInsAutoFiltered(HLERequestContext& ctx) { - using namespace AudioCore::AudioRenderer; + using namespace AudioCore::Renderer; LOG_DEBUG(Service_Audio, "called"); diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp index 23f84a29f..ca683d72c 100644 --- a/src/core/hle/service/audio/audout_u.cpp +++ b/src/core/hle/service/audio/audout_u.cpp @@ -228,7 +228,7 @@ AudOutU::AudOutU(Core::System& system_) AudOutU::~AudOutU() = default; void AudOutU::ListAudioOuts(HLERequestContext& ctx) { - using namespace AudioCore::AudioRenderer; + using namespace AudioCore::Renderer; std::scoped_lock l{impl->mutex}; diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp index 003870176..2f09cade5 100644 --- a/src/core/hle/service/audio/audren_u.cpp +++ b/src/core/hle/service/audio/audren_u.cpp @@ -26,7 +26,7 @@ #include "core/hle/service/ipc_helpers.h" #include "core/memory.h" -using namespace AudioCore::AudioRenderer; +using namespace AudioCore::Renderer; namespace Service::Audio { @@ -441,10 +441,11 @@ void AudRenU::OpenAudioRenderer(HLERequestContext& ctx) { AudioCore::AudioRendererParameterInternal params; rp.PopRaw<AudioCore::AudioRendererParameterInternal>(params); - auto transfer_memory_handle = ctx.GetCopyHandle(0); - auto process_handle = ctx.GetCopyHandle(1); + rp.Skip(1, false); auto transfer_memory_size = rp.Pop<u64>(); auto applet_resource_user_id = rp.Pop<u64>(); + auto transfer_memory_handle = ctx.GetCopyHandle(0); + auto process_handle = ctx.GetCopyHandle(1); if (impl->GetSessionCount() + 1 > AudioCore::MaxRendererSessions) { LOG_ERROR(Service_Audio, "Too many AudioRenderer sessions open!"); diff --git a/src/core/hle/service/audio/audren_u.h b/src/core/hle/service/audio/audren_u.h index d8e9c8719..3d7993a16 100644 --- a/src/core/hle/service/audio/audren_u.h +++ b/src/core/hle/service/audio/audren_u.h @@ -28,7 +28,7 @@ private: void GetAudioDeviceServiceWithRevisionInfo(HLERequestContext& ctx); KernelHelpers::ServiceContext service_context; - std::unique_ptr<AudioCore::AudioRenderer::Manager> impl; + std::unique_ptr<AudioCore::Renderer::Manager> impl; u32 num_audio_devices{0}; }; diff --git a/src/core/hle/service/audio/errors.h b/src/core/hle/service/audio/errors.h index 3d3d3d97a..c41345f7e 100644 --- a/src/core/hle/service/audio/errors.h +++ b/src/core/hle/service/audio/errors.h @@ -20,4 +20,16 @@ constexpr Result ResultNotSupported{ErrorModule::Audio, 513}; constexpr Result ResultInvalidHandle{ErrorModule::Audio, 1536}; constexpr Result ResultInvalidRevision{ErrorModule::Audio, 1537}; +constexpr Result ResultLibOpusAllocFail{ErrorModule::HwOpus, 7}; +constexpr Result ResultInputDataTooSmall{ErrorModule::HwOpus, 8}; +constexpr Result ResultLibOpusInvalidState{ErrorModule::HwOpus, 6}; +constexpr Result ResultLibOpusUnimplemented{ErrorModule::HwOpus, 5}; +constexpr Result ResultLibOpusInvalidPacket{ErrorModule::HwOpus, 17}; +constexpr Result ResultLibOpusInternalError{ErrorModule::HwOpus, 4}; +constexpr Result ResultBufferTooSmall{ErrorModule::HwOpus, 3}; +constexpr Result ResultLibOpusBadArg{ErrorModule::HwOpus, 2}; +constexpr Result ResultInvalidOpusDSPReturnCode{ErrorModule::HwOpus, 259}; +constexpr Result ResultInvalidOpusSampleRate{ErrorModule::HwOpus, 1001}; +constexpr Result ResultInvalidOpusChannelCount{ErrorModule::HwOpus, 1002}; + } // namespace Service::Audio diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp index fa77007f3..6a7bf9416 100644 --- a/src/core/hle/service/audio/hwopus.cpp +++ b/src/core/hle/service/audio/hwopus.cpp @@ -1,371 +1,506 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include <chrono> -#include <cstring> #include <memory> #include <vector> -#include <opus.h> -#include <opus_multistream.h> - +#include "audio_core/opus/decoder.h" +#include "audio_core/opus/parameters.h" #include "common/assert.h" #include "common/logging/log.h" #include "common/scratch_buffer.h" +#include "core/core.h" #include "core/hle/service/audio/hwopus.h" #include "core/hle/service/ipc_helpers.h" namespace Service::Audio { -namespace { -struct OpusDeleter { - void operator()(OpusMSDecoder* ptr) const { - opus_multistream_decoder_destroy(ptr); +using namespace AudioCore::OpusDecoder; + +class IHardwareOpusDecoder final : public ServiceFramework<IHardwareOpusDecoder> { +public: + explicit IHardwareOpusDecoder(Core::System& system_, HardwareOpus& hardware_opus) + : ServiceFramework{system_, "IHardwareOpusDecoder"}, + impl{std::make_unique<AudioCore::OpusDecoder::OpusDecoder>(system_, hardware_opus)} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &IHardwareOpusDecoder::DecodeInterleavedOld, "DecodeInterleavedOld"}, + {1, &IHardwareOpusDecoder::SetContext, "SetContext"}, + {2, &IHardwareOpusDecoder::DecodeInterleavedForMultiStreamOld, "DecodeInterleavedForMultiStreamOld"}, + {3, &IHardwareOpusDecoder::SetContextForMultiStream, "SetContextForMultiStream"}, + {4, &IHardwareOpusDecoder::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"}, + {5, &IHardwareOpusDecoder::DecodeInterleavedForMultiStreamWithPerfOld, "DecodeInterleavedForMultiStreamWithPerfOld"}, + {6, &IHardwareOpusDecoder::DecodeInterleavedWithPerfAndResetOld, "DecodeInterleavedWithPerfAndResetOld"}, + {7, &IHardwareOpusDecoder::DecodeInterleavedForMultiStreamWithPerfAndResetOld, "DecodeInterleavedForMultiStreamWithPerfAndResetOld"}, + {8, &IHardwareOpusDecoder::DecodeInterleaved, "DecodeInterleaved"}, + {9, &IHardwareOpusDecoder::DecodeInterleavedForMultiStream, "DecodeInterleavedForMultiStream"}, + }; + // clang-format on + + RegisterHandlers(functions); } -}; -using OpusDecoderPtr = std::unique_ptr<OpusMSDecoder, OpusDeleter>; + Result Initialize(OpusParametersEx& params, Kernel::KTransferMemory* transfer_memory, + u64 transfer_memory_size) { + return impl->Initialize(params, transfer_memory, transfer_memory_size); + } -struct OpusPacketHeader { - // Packet size in bytes. - u32_be size; - // Indicates the final range of the codec's entropy coder. - u32_be final_range; -}; -static_assert(sizeof(OpusPacketHeader) == 0x8, "OpusHeader is an invalid size"); + Result Initialize(OpusMultiStreamParametersEx& params, Kernel::KTransferMemory* transfer_memory, + u64 transfer_memory_size) { + return impl->Initialize(params, transfer_memory, transfer_memory_size); + } -class OpusDecoderState { -public: - /// Describes extra behavior that may be asked of the decoding context. - enum class ExtraBehavior { - /// No extra behavior. - None, +private: + void DecodeInterleavedOld(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; - /// Resets the decoder context back to a freshly initialized state. - ResetContext, - }; + auto input_data{ctx.ReadBuffer(0)}; + output_data.resize_destructive(ctx.GetWriteBufferSize()); - enum class PerfTime { - Disabled, - Enabled, - }; + u32 size{}; + u32 sample_count{}; + auto result = + impl->DecodeInterleaved(&size, nullptr, &sample_count, input_data, output_data, false); + + LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {}", size, sample_count); + + ctx.WriteBuffer(output_data); - explicit OpusDecoderState(OpusDecoderPtr decoder_, u32 sample_rate_, u32 channel_count_) - : decoder{std::move(decoder_)}, sample_rate{sample_rate_}, channel_count{channel_count_} {} - - // Decodes interleaved Opus packets. Optionally allows reporting time taken to - // perform the decoding, as well as any relevant extra behavior. - void DecodeInterleaved(HLERequestContext& ctx, PerfTime perf_time, - ExtraBehavior extra_behavior) { - if (perf_time == PerfTime::Disabled) { - DecodeInterleavedHelper(ctx, nullptr, extra_behavior); - } else { - u64 performance = 0; - DecodeInterleavedHelper(ctx, &performance, extra_behavior); - } + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result); + rb.Push(size); + rb.Push(sample_count); } -private: - void DecodeInterleavedHelper(HLERequestContext& ctx, u64* performance, - ExtraBehavior extra_behavior) { - u32 consumed = 0; - u32 sample_count = 0; - samples.resize_destructive(ctx.GetWriteBufferNumElements<opus_int16>()); - - if (extra_behavior == ExtraBehavior::ResetContext) { - ResetDecoderContext(); - } - - if (!DecodeOpusData(consumed, sample_count, ctx.ReadBuffer(), samples, performance)) { - LOG_ERROR(Audio, "Failed to decode opus data"); - IPC::ResponseBuilder rb{ctx, 2}; - // TODO(ogniK): Use correct error code - rb.Push(ResultUnknown); - return; - } - - const u32 param_size = performance != nullptr ? 6 : 4; - IPC::ResponseBuilder rb{ctx, param_size}; - rb.Push(ResultSuccess); - rb.Push<u32>(consumed); - rb.Push<u32>(sample_count); - if (performance) { - rb.Push<u64>(*performance); - } - ctx.WriteBuffer(samples); + void SetContext(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + LOG_DEBUG(Service_Audio, "called"); + + auto input_data{ctx.ReadBuffer(0)}; + auto result = impl->SetContext(input_data); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); } - bool DecodeOpusData(u32& consumed, u32& sample_count, std::span<const u8> input, - std::span<opus_int16> output, u64* out_performance_time) const { - const auto start_time = std::chrono::steady_clock::now(); - const std::size_t raw_output_sz = output.size() * sizeof(opus_int16); - if (sizeof(OpusPacketHeader) > input.size()) { - LOG_ERROR(Audio, "Input is smaller than the header size, header_sz={}, input_sz={}", - sizeof(OpusPacketHeader), input.size()); - return false; - } - - OpusPacketHeader hdr{}; - std::memcpy(&hdr, input.data(), sizeof(OpusPacketHeader)); - if (sizeof(OpusPacketHeader) + static_cast<u32>(hdr.size) > input.size()) { - LOG_ERROR(Audio, "Input does not fit in the opus header size. data_sz={}, input_sz={}", - sizeof(OpusPacketHeader) + static_cast<u32>(hdr.size), input.size()); - return false; - } - - const auto frame = input.data() + sizeof(OpusPacketHeader); - const auto decoded_sample_count = opus_packet_get_nb_samples( - frame, static_cast<opus_int32>(input.size() - sizeof(OpusPacketHeader)), - static_cast<opus_int32>(sample_rate)); - if (decoded_sample_count * channel_count * sizeof(u16) > raw_output_sz) { - LOG_ERROR( - Audio, - "Decoded data does not fit into the output data, decoded_sz={}, raw_output_sz={}", - decoded_sample_count * channel_count * sizeof(u16), raw_output_sz); - return false; - } - - const int frame_size = (static_cast<int>(raw_output_sz / sizeof(s16) / channel_count)); - const auto out_sample_count = - opus_multistream_decode(decoder.get(), frame, hdr.size, output.data(), frame_size, 0); - if (out_sample_count < 0) { - LOG_ERROR(Audio, - "Incorrect sample count received from opus_decode, " - "output_sample_count={}, frame_size={}, data_sz_from_hdr={}", - out_sample_count, frame_size, static_cast<u32>(hdr.size)); - return false; - } - - const auto end_time = std::chrono::steady_clock::now() - start_time; - sample_count = out_sample_count; - consumed = static_cast<u32>(sizeof(OpusPacketHeader) + hdr.size); - if (out_performance_time != nullptr) { - *out_performance_time = - std::chrono::duration_cast<std::chrono::milliseconds>(end_time).count(); - } - - return true; + void DecodeInterleavedForMultiStreamOld(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto input_data{ctx.ReadBuffer(0)}; + output_data.resize_destructive(ctx.GetWriteBufferSize()); + + u32 size{}; + u32 sample_count{}; + auto result = impl->DecodeInterleavedForMultiStream(&size, nullptr, &sample_count, + input_data, output_data, false); + + LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {}", size, sample_count); + + ctx.WriteBuffer(output_data); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result); + rb.Push(size); + rb.Push(sample_count); } - void ResetDecoderContext() { - ASSERT(decoder != nullptr); + void SetContextForMultiStream(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + LOG_DEBUG(Service_Audio, "called"); + + auto input_data{ctx.ReadBuffer(0)}; + auto result = impl->SetContext(input_data); - opus_multistream_decoder_ctl(decoder.get(), OPUS_RESET_STATE); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); } - OpusDecoderPtr decoder; - u32 sample_rate; - u32 channel_count; - Common::ScratchBuffer<opus_int16> samples; -}; + void DecodeInterleavedWithPerfOld(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; -class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> { -public: - explicit IHardwareOpusDecoderManager(Core::System& system_, OpusDecoderState decoder_state_) - : ServiceFramework{system_, "IHardwareOpusDecoderManager"}, decoder_state{ - std::move(decoder_state_)} { - // clang-format off - static const FunctionInfo functions[] = { - {0, &IHardwareOpusDecoderManager::DecodeInterleavedOld, "DecodeInterleavedOld"}, - {1, nullptr, "SetContext"}, - {2, nullptr, "DecodeInterleavedForMultiStreamOld"}, - {3, nullptr, "SetContextForMultiStream"}, - {4, &IHardwareOpusDecoderManager::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"}, - {5, nullptr, "DecodeInterleavedForMultiStreamWithPerfOld"}, - {6, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleavedWithPerfAndResetOld"}, - {7, nullptr, "DecodeInterleavedForMultiStreamWithPerfAndResetOld"}, - {8, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleaved"}, - {9, nullptr, "DecodeInterleavedForMultiStream"}, - }; - // clang-format on + auto input_data{ctx.ReadBuffer(0)}; + output_data.resize_destructive(ctx.GetWriteBufferSize()); - RegisterHandlers(functions); + u32 size{}; + u32 sample_count{}; + u64 time_taken{}; + auto result = impl->DecodeInterleaved(&size, &time_taken, &sample_count, input_data, + output_data, false); + + LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {} time taken {}", size, + sample_count, time_taken); + + ctx.WriteBuffer(output_data); + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(result); + rb.Push(size); + rb.Push(sample_count); + rb.Push(time_taken); } -private: - void DecodeInterleavedOld(HLERequestContext& ctx) { - LOG_DEBUG(Audio, "called"); + void DecodeInterleavedForMultiStreamWithPerfOld(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto input_data{ctx.ReadBuffer(0)}; + output_data.resize_destructive(ctx.GetWriteBufferSize()); + + u32 size{}; + u32 sample_count{}; + u64 time_taken{}; + auto result = impl->DecodeInterleavedForMultiStream(&size, &time_taken, &sample_count, + input_data, output_data, false); - decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Disabled, - OpusDecoderState::ExtraBehavior::None); + LOG_DEBUG(Service_Audio, "bytes read 0x{:X} samples generated {} time taken {}", size, + sample_count, time_taken); + + ctx.WriteBuffer(output_data); + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(result); + rb.Push(size); + rb.Push(sample_count); + rb.Push(time_taken); } - void DecodeInterleavedWithPerfOld(HLERequestContext& ctx) { - LOG_DEBUG(Audio, "called"); + void DecodeInterleavedWithPerfAndResetOld(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto reset{rp.Pop<bool>()}; + + auto input_data{ctx.ReadBuffer(0)}; + output_data.resize_destructive(ctx.GetWriteBufferSize()); + + u32 size{}; + u32 sample_count{}; + u64 time_taken{}; + auto result = impl->DecodeInterleaved(&size, &time_taken, &sample_count, input_data, + output_data, reset); + + LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}", + reset, size, sample_count, time_taken); + + ctx.WriteBuffer(output_data); + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(result); + rb.Push(size); + rb.Push(sample_count); + rb.Push(time_taken); + } + + void DecodeInterleavedForMultiStreamWithPerfAndResetOld(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto reset{rp.Pop<bool>()}; + + auto input_data{ctx.ReadBuffer(0)}; + output_data.resize_destructive(ctx.GetWriteBufferSize()); + + u32 size{}; + u32 sample_count{}; + u64 time_taken{}; + auto result = impl->DecodeInterleavedForMultiStream(&size, &time_taken, &sample_count, + input_data, output_data, reset); + + LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}", + reset, size, sample_count, time_taken); - decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, - OpusDecoderState::ExtraBehavior::None); + ctx.WriteBuffer(output_data); + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(result); + rb.Push(size); + rb.Push(sample_count); + rb.Push(time_taken); } void DecodeInterleaved(HLERequestContext& ctx) { - LOG_DEBUG(Audio, "called"); + IPC::RequestParser rp{ctx}; + + auto reset{rp.Pop<bool>()}; + + auto input_data{ctx.ReadBuffer(0)}; + output_data.resize_destructive(ctx.GetWriteBufferSize()); + + u32 size{}; + u32 sample_count{}; + u64 time_taken{}; + auto result = impl->DecodeInterleaved(&size, &time_taken, &sample_count, input_data, + output_data, reset); + + LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}", + reset, size, sample_count, time_taken); + + ctx.WriteBuffer(output_data); + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(result); + rb.Push(size); + rb.Push(sample_count); + rb.Push(time_taken); + } + void DecodeInterleavedForMultiStream(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto extra_behavior = rp.Pop<bool>() ? OpusDecoderState::ExtraBehavior::ResetContext - : OpusDecoderState::ExtraBehavior::None; - decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, extra_behavior); + auto reset{rp.Pop<bool>()}; + + auto input_data{ctx.ReadBuffer(0)}; + output_data.resize_destructive(ctx.GetWriteBufferSize()); + + u32 size{}; + u32 sample_count{}; + u64 time_taken{}; + auto result = impl->DecodeInterleavedForMultiStream(&size, &time_taken, &sample_count, + input_data, output_data, reset); + + LOG_DEBUG(Service_Audio, "reset {} bytes read 0x{:X} samples generated {} time taken {}", + reset, size, sample_count, time_taken); + + ctx.WriteBuffer(output_data); + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(result); + rb.Push(size); + rb.Push(sample_count); + rb.Push(time_taken); } - OpusDecoderState decoder_state; + std::unique_ptr<AudioCore::OpusDecoder::OpusDecoder> impl; + Common::ScratchBuffer<u8> output_data; }; -std::size_t WorkerBufferSize(u32 channel_count) { - ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); - constexpr int num_streams = 1; - const int num_stereo_streams = channel_count == 2 ? 1 : 0; - return opus_multistream_decoder_get_size(num_streams, num_stereo_streams); -} +void HwOpus::OpenHardwareOpusDecoder(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; -// Creates the mapping table that maps the input channels to the particular -// output channels. In the stereo case, we map the left and right input channels -// to the left and right output channels respectively. -// -// However, in the monophonic case, we only map the one available channel -// to the sole output channel. We specify 255 for the would-be right channel -// as this is a special value defined by Opus to indicate to the decoder to -// ignore that channel. -std::array<u8, 2> CreateMappingTable(u32 channel_count) { - if (channel_count == 2) { - return {{0, 1}}; - } + auto params = rp.PopRaw<OpusParameters>(); + auto transfer_memory_size{rp.Pop<u32>()}; + auto transfer_memory_handle{ctx.GetCopyHandle(0)}; + auto transfer_memory{ + system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>( + transfer_memory_handle)}; - return {{0, 255}}; + LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} transfer_memory_size 0x{:X}", + params.sample_rate, params.channel_count, transfer_memory_size); + + auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())}; + + OpusParametersEx ex{ + .sample_rate = params.sample_rate, + .channel_count = params.channel_count, + .use_large_frame_size = false, + }; + auto result = decoder->Initialize(ex, transfer_memory.GetPointerUnsafe(), transfer_memory_size); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(result); + rb.PushIpcInterface(decoder); } -} // Anonymous namespace void HwOpus::GetWorkBufferSize(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto sample_rate = rp.Pop<u32>(); - const auto channel_count = rp.Pop<u32>(); + auto params = rp.PopRaw<OpusParameters>(); - LOG_DEBUG(Audio, "called with sample_rate={}, channel_count={}", sample_rate, channel_count); + u64 size{}; + auto result = impl.GetWorkBufferSize(params, size); - ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || - sample_rate == 12000 || sample_rate == 8000, - "Invalid sample rate"); - ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); + LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} -- returned size 0x{:X}", + params.sample_rate, params.channel_count, size); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result); + rb.Push(size); +} + +void HwOpus::OpenHardwareOpusDecoderForMultiStream(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; - const u32 worker_buffer_sz = static_cast<u32>(WorkerBufferSize(channel_count)); - LOG_DEBUG(Audio, "worker_buffer_sz={}", worker_buffer_sz); + auto input{ctx.ReadBuffer()}; + OpusMultiStreamParameters params; + std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParameters)); + + auto transfer_memory_size{rp.Pop<u32>()}; + auto transfer_memory_handle{ctx.GetCopyHandle(0)}; + auto transfer_memory{ + system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>( + transfer_memory_handle)}; + + LOG_DEBUG(Service_Audio, + "sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} " + "transfer_memory_size 0x{:X}", + params.sample_rate, params.channel_count, params.total_stream_count, + params.stereo_stream_count, transfer_memory_size); + + auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())}; + + OpusMultiStreamParametersEx ex{ + .sample_rate = params.sample_rate, + .channel_count = params.channel_count, + .total_stream_count = params.total_stream_count, + .stereo_stream_count = params.stereo_stream_count, + .use_large_frame_size = false, + .mappings{}, + }; + std::memcpy(ex.mappings.data(), params.mappings.data(), sizeof(params.mappings)); + auto result = decoder->Initialize(ex, transfer_memory.GetPointerUnsafe(), transfer_memory_size); - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.Push<u32>(worker_buffer_sz); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(result); + rb.PushIpcInterface(decoder); } -void HwOpus::GetWorkBufferSizeEx(HLERequestContext& ctx) { - GetWorkBufferSize(ctx); +void HwOpus::GetWorkBufferSizeForMultiStream(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto input{ctx.ReadBuffer()}; + OpusMultiStreamParameters params; + std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParameters)); + + u64 size{}; + auto result = impl.GetWorkBufferSizeForMultiStream(params, size); + + LOG_DEBUG(Service_Audio, "size 0x{:X}", size); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result); + rb.Push(size); } -void HwOpus::GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx) { - OpusMultiStreamParametersEx param; - std::memcpy(¶m, ctx.ReadBuffer().data(), ctx.GetReadBufferSize()); +void HwOpus::OpenHardwareOpusDecoderEx(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; - const auto sample_rate = param.sample_rate; - const auto channel_count = param.channel_count; - const auto number_streams = param.number_streams; - const auto number_stereo_streams = param.number_stereo_streams; + auto params = rp.PopRaw<OpusParametersEx>(); + auto transfer_memory_size{rp.Pop<u32>()}; + auto transfer_memory_handle{ctx.GetCopyHandle(0)}; + auto transfer_memory{ + system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>( + transfer_memory_handle)}; - LOG_DEBUG( - Audio, - "called with sample_rate={}, channel_count={}, number_streams={}, number_stereo_streams={}", - sample_rate, channel_count, number_streams, number_stereo_streams); + LOG_DEBUG(Service_Audio, "sample_rate {} channel_count {} transfer_memory_size 0x{:X}", + params.sample_rate, params.channel_count, transfer_memory_size); - ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || - sample_rate == 12000 || sample_rate == 8000, - "Invalid sample rate"); + auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())}; - const u32 worker_buffer_sz = - static_cast<u32>(opus_multistream_decoder_get_size(number_streams, number_stereo_streams)); + auto result = + decoder->Initialize(params, transfer_memory.GetPointerUnsafe(), transfer_memory_size); - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.Push<u32>(worker_buffer_sz); + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(result); + rb.PushIpcInterface(decoder); } -void HwOpus::OpenHardwareOpusDecoder(HLERequestContext& ctx) { +void HwOpus::GetWorkBufferSizeEx(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto sample_rate = rp.Pop<u32>(); - const auto channel_count = rp.Pop<u32>(); - const auto buffer_sz = rp.Pop<u32>(); - - LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}, buffer_size={}", sample_rate, - channel_count, buffer_sz); - - ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || - sample_rate == 12000 || sample_rate == 8000, - "Invalid sample rate"); - ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); - - const std::size_t worker_sz = WorkerBufferSize(channel_count); - ASSERT_MSG(buffer_sz >= worker_sz, "Worker buffer too large"); - - const int num_stereo_streams = channel_count == 2 ? 1 : 0; - const auto mapping_table = CreateMappingTable(channel_count); - - int error = 0; - OpusDecoderPtr decoder{ - opus_multistream_decoder_create(sample_rate, static_cast<int>(channel_count), 1, - num_stereo_streams, mapping_table.data(), &error)}; - if (error != OPUS_OK || decoder == nullptr) { - LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error); - IPC::ResponseBuilder rb{ctx, 2}; - // TODO(ogniK): Use correct error code - rb.Push(ResultUnknown); - return; - } + auto params = rp.PopRaw<OpusParametersEx>(); - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(ResultSuccess); - rb.PushIpcInterface<IHardwareOpusDecoderManager>( - system, OpusDecoderState{std::move(decoder), sample_rate, channel_count}); + u64 size{}; + auto result = impl.GetWorkBufferSizeEx(params, size); + + LOG_DEBUG(Service_Audio, "size 0x{:X}", size); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result); + rb.Push(size); } -void HwOpus::OpenHardwareOpusDecoderEx(HLERequestContext& ctx) { +void HwOpus::OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto sample_rate = rp.Pop<u32>(); - const auto channel_count = rp.Pop<u32>(); - LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}", sample_rate, channel_count); + auto input{ctx.ReadBuffer()}; + OpusMultiStreamParametersEx params; + std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParametersEx)); - ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 || - sample_rate == 12000 || sample_rate == 8000, - "Invalid sample rate"); - ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); + auto transfer_memory_size{rp.Pop<u32>()}; + auto transfer_memory_handle{ctx.GetCopyHandle(0)}; + auto transfer_memory{ + system.ApplicationProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>( + transfer_memory_handle)}; - const int num_stereo_streams = channel_count == 2 ? 1 : 0; - const auto mapping_table = CreateMappingTable(channel_count); + LOG_DEBUG(Service_Audio, + "sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} " + "use_large_frame_size {}" + "transfer_memory_size 0x{:X}", + params.sample_rate, params.channel_count, params.total_stream_count, + params.stereo_stream_count, params.use_large_frame_size, transfer_memory_size); - int error = 0; - OpusDecoderPtr decoder{ - opus_multistream_decoder_create(sample_rate, static_cast<int>(channel_count), 1, - num_stereo_streams, mapping_table.data(), &error)}; - if (error != OPUS_OK || decoder == nullptr) { - LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error); - IPC::ResponseBuilder rb{ctx, 2}; - // TODO(ogniK): Use correct error code - rb.Push(ResultUnknown); - return; - } + auto decoder{std::make_shared<IHardwareOpusDecoder>(system, impl.GetHardwareOpus())}; + + auto result = + decoder->Initialize(params, transfer_memory.GetPointerUnsafe(), transfer_memory_size); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(ResultSuccess); - rb.PushIpcInterface<IHardwareOpusDecoderManager>( - system, OpusDecoderState{std::move(decoder), sample_rate, channel_count}); + rb.Push(result); + rb.PushIpcInterface(decoder); +} + +void HwOpus::GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto input{ctx.ReadBuffer()}; + OpusMultiStreamParametersEx params; + std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParametersEx)); + + u64 size{}; + auto result = impl.GetWorkBufferSizeForMultiStreamEx(params, size); + + LOG_DEBUG(Service_Audio, + "sample_rate {} channel_count {} total_stream_count {} stereo_stream_count {} " + "use_large_frame_size {} -- returned size 0x{:X}", + params.sample_rate, params.channel_count, params.total_stream_count, + params.stereo_stream_count, params.use_large_frame_size, size); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result); + rb.Push(size); +} + +void HwOpus::GetWorkBufferSizeExEx(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + auto params = rp.PopRaw<OpusParametersEx>(); + + u64 size{}; + auto result = impl.GetWorkBufferSizeExEx(params, size); + + LOG_DEBUG(Service_Audio, "size 0x{:X}", size); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result); + rb.Push(size); +} + +void HwOpus::GetWorkBufferSizeForMultiStreamExEx(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + auto input{ctx.ReadBuffer()}; + OpusMultiStreamParametersEx params; + std::memcpy(¶ms, input.data(), sizeof(OpusMultiStreamParametersEx)); + + u64 size{}; + auto result = impl.GetWorkBufferSizeForMultiStreamExEx(params, size); + + LOG_DEBUG(Service_Audio, "size 0x{:X}", size); + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(result); + rb.Push(size); } -HwOpus::HwOpus(Core::System& system_) : ServiceFramework{system_, "hwopus"} { +HwOpus::HwOpus(Core::System& system_) + : ServiceFramework{system_, "hwopus"}, system{system_}, impl{system} { static const FunctionInfo functions[] = { {0, &HwOpus::OpenHardwareOpusDecoder, "OpenHardwareOpusDecoder"}, {1, &HwOpus::GetWorkBufferSize, "GetWorkBufferSize"}, - {2, nullptr, "OpenOpusDecoderForMultiStream"}, - {3, nullptr, "GetWorkBufferSizeForMultiStream"}, + {2, &HwOpus::OpenHardwareOpusDecoderForMultiStream, "OpenOpusDecoderForMultiStream"}, + {3, &HwOpus::GetWorkBufferSizeForMultiStream, "GetWorkBufferSizeForMultiStream"}, {4, &HwOpus::OpenHardwareOpusDecoderEx, "OpenHardwareOpusDecoderEx"}, {5, &HwOpus::GetWorkBufferSizeEx, "GetWorkBufferSizeEx"}, - {6, nullptr, "OpenHardwareOpusDecoderForMultiStreamEx"}, + {6, &HwOpus::OpenHardwareOpusDecoderForMultiStreamEx, + "OpenHardwareOpusDecoderForMultiStreamEx"}, {7, &HwOpus::GetWorkBufferSizeForMultiStreamEx, "GetWorkBufferSizeForMultiStreamEx"}, - {8, nullptr, "GetWorkBufferSizeExEx"}, - {9, nullptr, "GetWorkBufferSizeForMultiStreamExEx"}, + {8, &HwOpus::GetWorkBufferSizeExEx, "GetWorkBufferSizeExEx"}, + {9, &HwOpus::GetWorkBufferSizeForMultiStreamExEx, "GetWorkBufferSizeForMultiStreamExEx"}, }; RegisterHandlers(functions); } diff --git a/src/core/hle/service/audio/hwopus.h b/src/core/hle/service/audio/hwopus.h index ece65c02c..d3960065e 100644 --- a/src/core/hle/service/audio/hwopus.h +++ b/src/core/hle/service/audio/hwopus.h @@ -3,6 +3,7 @@ #pragma once +#include "audio_core/opus/decoder_manager.h" #include "core/hle/service/service.h" namespace Core { @@ -11,16 +12,6 @@ class System; namespace Service::Audio { -struct OpusMultiStreamParametersEx { - u32 sample_rate; - u32 channel_count; - u32 number_streams; - u32 number_stereo_streams; - u32 use_large_frame_size; - u32 padding; - std::array<u32, 64> channel_mappings; -}; - class HwOpus final : public ServiceFramework<HwOpus> { public: explicit HwOpus(Core::System& system_); @@ -28,10 +19,18 @@ public: private: void OpenHardwareOpusDecoder(HLERequestContext& ctx); - void OpenHardwareOpusDecoderEx(HLERequestContext& ctx); void GetWorkBufferSize(HLERequestContext& ctx); + void OpenHardwareOpusDecoderForMultiStream(HLERequestContext& ctx); + void GetWorkBufferSizeForMultiStream(HLERequestContext& ctx); + void OpenHardwareOpusDecoderEx(HLERequestContext& ctx); void GetWorkBufferSizeEx(HLERequestContext& ctx); + void OpenHardwareOpusDecoderForMultiStreamEx(HLERequestContext& ctx); void GetWorkBufferSizeForMultiStreamEx(HLERequestContext& ctx); + void GetWorkBufferSizeExEx(HLERequestContext& ctx); + void GetWorkBufferSizeForMultiStreamExEx(HLERequestContext& ctx); + + Core::System& system; + AudioCore::OpusDecoder::OpusDecoderManager impl; }; } // namespace Service::Audio diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp index 446f46b3c..9eaae4c4b 100644 --- a/src/core/hle/service/es/es.cpp +++ b/src/core/hle/service/es/es.cpp @@ -122,20 +122,18 @@ private: } void ImportTicket(HLERequestContext& ctx) { - const auto ticket = ctx.ReadBuffer(); + const auto raw_ticket = ctx.ReadBuffer(); [[maybe_unused]] const auto cert = ctx.ReadBuffer(1); - if (ticket.size() < sizeof(Core::Crypto::Ticket)) { + if (raw_ticket.size() < sizeof(Core::Crypto::Ticket)) { LOG_ERROR(Service_ETicket, "The input buffer is not large enough!"); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ERROR_INVALID_ARGUMENT); return; } - Core::Crypto::Ticket raw{}; - std::memcpy(&raw, ticket.data(), sizeof(Core::Crypto::Ticket)); - - if (!keys.AddTicketPersonalized(raw)) { + Core::Crypto::Ticket ticket = Core::Crypto::Ticket::Read(raw_ticket); + if (!keys.AddTicket(ticket)) { LOG_ERROR(Service_ETicket, "The ticket could not be imported!"); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ERROR_INVALID_ARGUMENT); diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index dfcdd3ada..508db7360 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -4,6 +4,7 @@ #include <utility> #include "common/assert.h" +#include "common/fs/fs.h" #include "common/fs/path_util.h" #include "common/settings.h" #include "core/core.h" @@ -57,8 +58,8 @@ Result VfsDirectoryServiceWrapper::CreateFile(const std::string& path_, u64 size return FileSys::ERROR_PATH_NOT_FOUND; } - const auto entry_type = GetEntryType(path); - if (entry_type.Code() == ResultSuccess) { + FileSys::EntryType entry_type{}; + if (GetEntryType(&entry_type, path) == ResultSuccess) { return FileSys::ERROR_PATH_ALREADY_EXISTS; } @@ -154,10 +155,18 @@ Result VfsDirectoryServiceWrapper::RenameFile(const std::string& src_path_, std::string src_path(Common::FS::SanitizePath(src_path_)); std::string dest_path(Common::FS::SanitizePath(dest_path_)); auto src = backing->GetFileRelative(src_path); + auto dst = backing->GetFileRelative(dest_path); if (Common::FS::GetParentPath(src_path) == Common::FS::GetParentPath(dest_path)) { // Use more-optimized vfs implementation rename. - if (src == nullptr) + if (src == nullptr) { return FileSys::ERROR_PATH_NOT_FOUND; + } + + if (dst && Common::FS::Exists(dst->GetFullPath())) { + LOG_ERROR(Service_FS, "File at new_path={} already exists", dst->GetFullPath()); + return FileSys::ERROR_PATH_ALREADY_EXISTS; + } + if (!src->Rename(Common::FS::GetFilename(dest_path))) { // TODO(DarkLordZach): Find a better error code for this return ResultUnknown; @@ -210,8 +219,8 @@ Result VfsDirectoryServiceWrapper::RenameDirectory(const std::string& src_path_, return ResultUnknown; } -ResultVal<FileSys::VirtualFile> VfsDirectoryServiceWrapper::OpenFile(const std::string& path_, - FileSys::Mode mode) const { +Result VfsDirectoryServiceWrapper::OpenFile(FileSys::VirtualFile* out_file, + const std::string& path_, FileSys::Mode mode) const { const std::string path(Common::FS::SanitizePath(path_)); std::string_view npath = path; while (!npath.empty() && (npath[0] == '/' || npath[0] == '\\')) { @@ -224,50 +233,68 @@ ResultVal<FileSys::VirtualFile> VfsDirectoryServiceWrapper::OpenFile(const std:: } if (mode == FileSys::Mode::Append) { - return std::make_shared<FileSys::OffsetVfsFile>(file, 0, file->GetSize()); + *out_file = std::make_shared<FileSys::OffsetVfsFile>(file, 0, file->GetSize()); + } else { + *out_file = file; } - return file; + return ResultSuccess; } -ResultVal<FileSys::VirtualDir> VfsDirectoryServiceWrapper::OpenDirectory(const std::string& path_) { +Result VfsDirectoryServiceWrapper::OpenDirectory(FileSys::VirtualDir* out_directory, + const std::string& path_) { std::string path(Common::FS::SanitizePath(path_)); auto dir = GetDirectoryRelativeWrapped(backing, path); if (dir == nullptr) { // TODO(DarkLordZach): Find a better error code for this return FileSys::ERROR_PATH_NOT_FOUND; } - return dir; + *out_directory = dir; + return ResultSuccess; } -ResultVal<FileSys::EntryType> VfsDirectoryServiceWrapper::GetEntryType( - const std::string& path_) const { +Result VfsDirectoryServiceWrapper::GetEntryType(FileSys::EntryType* out_entry_type, + const std::string& path_) const { std::string path(Common::FS::SanitizePath(path_)); auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path)); - if (dir == nullptr) + if (dir == nullptr) { return FileSys::ERROR_PATH_NOT_FOUND; + } + auto filename = Common::FS::GetFilename(path); // TODO(Subv): Some games use the '/' path, find out what this means. - if (filename.empty()) - return FileSys::EntryType::Directory; + if (filename.empty()) { + *out_entry_type = FileSys::EntryType::Directory; + return ResultSuccess; + } + + if (dir->GetFile(filename) != nullptr) { + *out_entry_type = FileSys::EntryType::File; + return ResultSuccess; + } + + if (dir->GetSubdirectory(filename) != nullptr) { + *out_entry_type = FileSys::EntryType::Directory; + return ResultSuccess; + } - if (dir->GetFile(filename) != nullptr) - return FileSys::EntryType::File; - if (dir->GetSubdirectory(filename) != nullptr) - return FileSys::EntryType::Directory; return FileSys::ERROR_PATH_NOT_FOUND; } -ResultVal<FileSys::FileTimeStampRaw> VfsDirectoryServiceWrapper::GetFileTimeStampRaw( - const std::string& path) const { +Result VfsDirectoryServiceWrapper::GetFileTimeStampRaw( + FileSys::FileTimeStampRaw* out_file_time_stamp_raw, const std::string& path) const { auto dir = GetDirectoryRelativeWrapped(backing, Common::FS::GetParentPath(path)); if (dir == nullptr) { return FileSys::ERROR_PATH_NOT_FOUND; } - if (GetEntryType(path).Failed()) { + + FileSys::EntryType entry_type; + if (GetEntryType(&entry_type, path) != ResultSuccess) { return FileSys::ERROR_PATH_NOT_FOUND; } - return dir->GetFileTimeStamp(Common::FS::GetFilename(path)); + + *out_file_time_stamp_raw = dir->GetFileTimeStamp(Common::FS::GetFilename(path)); + return ResultSuccess; } FileSystemController::FileSystemController(Core::System& system_) : system{system_} {} @@ -310,57 +337,59 @@ void FileSystemController::SetPackedUpdate(FileSys::VirtualFile update_raw) { romfs_factory->SetPackedUpdate(std::move(update_raw)); } -ResultVal<FileSys::VirtualFile> FileSystemController::OpenRomFSCurrentProcess() const { +FileSys::VirtualFile FileSystemController::OpenRomFSCurrentProcess() const { LOG_TRACE(Service_FS, "Opening RomFS for current process"); if (romfs_factory == nullptr) { - // TODO(bunnei): Find a better error code for this - return ResultUnknown; + return nullptr; } return romfs_factory->OpenCurrentProcess(system.GetApplicationProcessProgramID()); } -ResultVal<FileSys::VirtualFile> FileSystemController::OpenPatchedRomFS( - u64 title_id, FileSys::ContentRecordType type) const { +FileSys::VirtualFile FileSystemController::OpenPatchedRomFS(u64 title_id, + FileSys::ContentRecordType type) const { LOG_TRACE(Service_FS, "Opening patched RomFS for title_id={:016X}", title_id); if (romfs_factory == nullptr) { - // TODO: Find a better error code for this - return ResultUnknown; + return nullptr; } return romfs_factory->OpenPatchedRomFS(title_id, type); } -ResultVal<FileSys::VirtualFile> FileSystemController::OpenPatchedRomFSWithProgramIndex( +FileSys::VirtualFile FileSystemController::OpenPatchedRomFSWithProgramIndex( u64 title_id, u8 program_index, FileSys::ContentRecordType type) const { LOG_TRACE(Service_FS, "Opening patched RomFS for title_id={:016X}, program_index={}", title_id, program_index); if (romfs_factory == nullptr) { - // TODO: Find a better error code for this - return ResultUnknown; + return nullptr; } return romfs_factory->OpenPatchedRomFSWithProgramIndex(title_id, program_index, type); } -ResultVal<FileSys::VirtualFile> FileSystemController::OpenRomFS( - u64 title_id, FileSys::StorageId storage_id, FileSys::ContentRecordType type) const { +FileSys::VirtualFile FileSystemController::OpenRomFS(u64 title_id, FileSys::StorageId storage_id, + FileSys::ContentRecordType type) const { LOG_TRACE(Service_FS, "Opening RomFS for title_id={:016X}, storage_id={:02X}, type={:02X}", title_id, storage_id, type); if (romfs_factory == nullptr) { - // TODO(bunnei): Find a better error code for this - return ResultUnknown; + return nullptr; } return romfs_factory->Open(title_id, storage_id, type); } -ResultVal<FileSys::VirtualDir> FileSystemController::CreateSaveData( - FileSys::SaveDataSpaceId space, const FileSys::SaveDataAttribute& save_struct) const { +std::shared_ptr<FileSys::NCA> FileSystemController::OpenBaseNca( + u64 title_id, FileSys::StorageId storage_id, FileSys::ContentRecordType type) const { + return romfs_factory->GetEntry(title_id, storage_id, type); +} + +Result FileSystemController::CreateSaveData(FileSys::VirtualDir* out_save_data, + FileSys::SaveDataSpaceId space, + const FileSys::SaveDataAttribute& save_struct) const { LOG_TRACE(Service_FS, "Creating Save Data for space_id={:01X}, save_struct={}", space, save_struct.DebugInfo()); @@ -368,11 +397,18 @@ ResultVal<FileSys::VirtualDir> FileSystemController::CreateSaveData( return FileSys::ERROR_ENTITY_NOT_FOUND; } - return save_data_factory->Create(space, save_struct); + auto save_data = save_data_factory->Create(space, save_struct); + if (save_data == nullptr) { + return FileSys::ERROR_ENTITY_NOT_FOUND; + } + + *out_save_data = save_data; + return ResultSuccess; } -ResultVal<FileSys::VirtualDir> FileSystemController::OpenSaveData( - FileSys::SaveDataSpaceId space, const FileSys::SaveDataAttribute& attribute) const { +Result FileSystemController::OpenSaveData(FileSys::VirtualDir* out_save_data, + FileSys::SaveDataSpaceId space, + const FileSys::SaveDataAttribute& attribute) const { LOG_TRACE(Service_FS, "Opening Save Data for space_id={:01X}, save_struct={}", space, attribute.DebugInfo()); @@ -380,32 +416,50 @@ ResultVal<FileSys::VirtualDir> FileSystemController::OpenSaveData( return FileSys::ERROR_ENTITY_NOT_FOUND; } - return save_data_factory->Open(space, attribute); + auto save_data = save_data_factory->Open(space, attribute); + if (save_data == nullptr) { + return FileSys::ERROR_ENTITY_NOT_FOUND; + } + + *out_save_data = save_data; + return ResultSuccess; } -ResultVal<FileSys::VirtualDir> FileSystemController::OpenSaveDataSpace( - FileSys::SaveDataSpaceId space) const { +Result FileSystemController::OpenSaveDataSpace(FileSys::VirtualDir* out_save_data_space, + FileSys::SaveDataSpaceId space) const { LOG_TRACE(Service_FS, "Opening Save Data Space for space_id={:01X}", space); if (save_data_factory == nullptr) { return FileSys::ERROR_ENTITY_NOT_FOUND; } - return save_data_factory->GetSaveDataSpaceDirectory(space); + auto save_data_space = save_data_factory->GetSaveDataSpaceDirectory(space); + if (save_data_space == nullptr) { + return FileSys::ERROR_ENTITY_NOT_FOUND; + } + + *out_save_data_space = save_data_space; + return ResultSuccess; } -ResultVal<FileSys::VirtualDir> FileSystemController::OpenSDMC() const { +Result FileSystemController::OpenSDMC(FileSys::VirtualDir* out_sdmc) const { LOG_TRACE(Service_FS, "Opening SDMC"); if (sdmc_factory == nullptr) { return FileSys::ERROR_SD_CARD_NOT_FOUND; } - return sdmc_factory->Open(); + auto sdmc = sdmc_factory->Open(); + if (sdmc == nullptr) { + return FileSys::ERROR_SD_CARD_NOT_FOUND; + } + + *out_sdmc = sdmc; + return ResultSuccess; } -ResultVal<FileSys::VirtualDir> FileSystemController::OpenBISPartition( - FileSys::BisPartitionId id) const { +Result FileSystemController::OpenBISPartition(FileSys::VirtualDir* out_bis_partition, + FileSys::BisPartitionId id) const { LOG_TRACE(Service_FS, "Opening BIS Partition with id={:08X}", id); if (bis_factory == nullptr) { @@ -417,11 +471,12 @@ ResultVal<FileSys::VirtualDir> FileSystemController::OpenBISPartition( return FileSys::ERROR_INVALID_ARGUMENT; } - return part; + *out_bis_partition = part; + return ResultSuccess; } -ResultVal<FileSys::VirtualFile> FileSystemController::OpenBISPartitionStorage( - FileSys::BisPartitionId id) const { +Result FileSystemController::OpenBISPartitionStorage( + FileSys::VirtualFile* out_bis_partition_storage, FileSys::BisPartitionId id) const { LOG_TRACE(Service_FS, "Opening BIS Partition Storage with id={:08X}", id); if (bis_factory == nullptr) { @@ -433,7 +488,8 @@ ResultVal<FileSys::VirtualFile> FileSystemController::OpenBISPartitionStorage( return FileSys::ERROR_INVALID_ARGUMENT; } - return part; + *out_bis_partition_storage = part; + return ResultSuccess; } u64 FileSystemController::GetFreeSpaceSize(FileSys::StorageId id) const { diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index a5c1c9d3e..e7e7c4c28 100644 --- a/src/core/hle/service/filesystem/filesystem.h +++ b/src/core/hle/service/filesystem/filesystem.h @@ -15,6 +15,7 @@ class System; namespace FileSys { class BISFactory; +class NCA; class RegisteredCache; class RegisteredCacheUnion; class PlaceholderCache; @@ -64,21 +65,26 @@ public: Result RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory); void SetPackedUpdate(FileSys::VirtualFile update_raw); - ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess() const; - ResultVal<FileSys::VirtualFile> OpenPatchedRomFS(u64 title_id, - FileSys::ContentRecordType type) const; - ResultVal<FileSys::VirtualFile> OpenPatchedRomFSWithProgramIndex( - u64 title_id, u8 program_index, FileSys::ContentRecordType type) const; - ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId storage_id, + FileSys::VirtualFile OpenRomFSCurrentProcess() const; + FileSys::VirtualFile OpenPatchedRomFS(u64 title_id, FileSys::ContentRecordType type) const; + FileSys::VirtualFile OpenPatchedRomFSWithProgramIndex(u64 title_id, u8 program_index, + FileSys::ContentRecordType type) const; + FileSys::VirtualFile OpenRomFS(u64 title_id, FileSys::StorageId storage_id, + FileSys::ContentRecordType type) const; + std::shared_ptr<FileSys::NCA> OpenBaseNca(u64 title_id, FileSys::StorageId storage_id, FileSys::ContentRecordType type) const; - ResultVal<FileSys::VirtualDir> CreateSaveData( - FileSys::SaveDataSpaceId space, const FileSys::SaveDataAttribute& save_struct) const; - ResultVal<FileSys::VirtualDir> OpenSaveData( - FileSys::SaveDataSpaceId space, const FileSys::SaveDataAttribute& save_struct) const; - ResultVal<FileSys::VirtualDir> OpenSaveDataSpace(FileSys::SaveDataSpaceId space) const; - ResultVal<FileSys::VirtualDir> OpenSDMC() const; - ResultVal<FileSys::VirtualDir> OpenBISPartition(FileSys::BisPartitionId id) const; - ResultVal<FileSys::VirtualFile> OpenBISPartitionStorage(FileSys::BisPartitionId id) const; + + Result CreateSaveData(FileSys::VirtualDir* out_save_data, FileSys::SaveDataSpaceId space, + const FileSys::SaveDataAttribute& save_struct) const; + Result OpenSaveData(FileSys::VirtualDir* out_save_data, FileSys::SaveDataSpaceId space, + const FileSys::SaveDataAttribute& save_struct) const; + Result OpenSaveDataSpace(FileSys::VirtualDir* out_save_data_space, + FileSys::SaveDataSpaceId space) const; + Result OpenSDMC(FileSys::VirtualDir* out_sdmc) const; + Result OpenBISPartition(FileSys::VirtualDir* out_bis_partition, + FileSys::BisPartitionId id) const; + Result OpenBISPartitionStorage(FileSys::VirtualFile* out_bis_partition_storage, + FileSys::BisPartitionId id) const; u64 GetFreeSpaceSize(FileSys::StorageId id) const; u64 GetTotalSpaceSize(FileSys::StorageId id) const; @@ -224,26 +230,28 @@ public: * @param mode Mode to open the file with * @return Opened file, or error code */ - ResultVal<FileSys::VirtualFile> OpenFile(const std::string& path, FileSys::Mode mode) const; + Result OpenFile(FileSys::VirtualFile* out_file, const std::string& path, + FileSys::Mode mode) const; /** * Open a directory specified by its path * @param path Path relative to the archive * @return Opened directory, or error code */ - ResultVal<FileSys::VirtualDir> OpenDirectory(const std::string& path); + Result OpenDirectory(FileSys::VirtualDir* out_directory, const std::string& path); /** * Get the type of the specified path * @return The type of the specified path or error code */ - ResultVal<FileSys::EntryType> GetEntryType(const std::string& path) const; + Result GetEntryType(FileSys::EntryType* out_entry_type, const std::string& path) const; /** * Get the timestamp of the specified path * @return The timestamp of the specified path or error code */ - ResultVal<FileSys::FileTimeStampRaw> GetFileTimeStampRaw(const std::string& path) const; + Result GetFileTimeStampRaw(FileSys::FileTimeStampRaw* out_time_stamp_raw, + const std::string& path) const; private: FileSys::VirtualDir backing; diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp index 427dbc8b3..6e4d26b1e 100644 --- a/src/core/hle/service/filesystem/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp_srv.cpp @@ -419,14 +419,15 @@ public: LOG_DEBUG(Service_FS, "called. file={}, mode={}", name, mode); - auto result = backend.OpenFile(name, mode); - if (result.Failed()) { + FileSys::VirtualFile vfs_file{}; + auto result = backend.OpenFile(&vfs_file, name, mode); + if (result != ResultSuccess) { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result.Code()); + rb.Push(result); return; } - auto file = std::make_shared<IFile>(system, result.Unwrap()); + auto file = std::make_shared<IFile>(system, vfs_file); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); @@ -444,14 +445,15 @@ public: LOG_DEBUG(Service_FS, "called. directory={}, filter={}", name, filter_flags); - auto result = backend.OpenDirectory(name); - if (result.Failed()) { + FileSys::VirtualDir vfs_dir{}; + auto result = backend.OpenDirectory(&vfs_dir, name); + if (result != ResultSuccess) { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result.Code()); + rb.Push(result); return; } - auto directory = std::make_shared<IDirectory>(system, result.Unwrap()); + auto directory = std::make_shared<IDirectory>(system, vfs_dir); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); @@ -464,16 +466,17 @@ public: LOG_DEBUG(Service_FS, "called. file={}", name); - auto result = backend.GetEntryType(name); - if (result.Failed()) { + FileSys::EntryType vfs_entry_type{}; + auto result = backend.GetEntryType(&vfs_entry_type, name); + if (result != ResultSuccess) { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result.Code()); + rb.Push(result); return; } IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push<u32>(static_cast<u32>(*result)); + rb.Push<u32>(static_cast<u32>(vfs_entry_type)); } void Commit(HLERequestContext& ctx) { @@ -505,16 +508,17 @@ public: LOG_WARNING(Service_FS, "(Partial Implementation) called. file={}", name); - auto result = backend.GetFileTimeStampRaw(name); - if (result.Failed()) { + FileSys::FileTimeStampRaw vfs_timestamp{}; + auto result = backend.GetFileTimeStampRaw(&vfs_timestamp, name); + if (result != ResultSuccess) { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result.Code()); + rb.Push(result); return; } IPC::ResponseBuilder rb{ctx, 10}; rb.Push(ResultSuccess); - rb.PushRaw(*result); + rb.PushRaw(vfs_timestamp); } private: @@ -572,14 +576,15 @@ private: } void FindAllSaves(FileSys::SaveDataSpaceId space) { - const auto save_root = fsc.OpenSaveDataSpace(space); + FileSys::VirtualDir save_root{}; + const auto result = fsc.OpenSaveDataSpace(&save_root, space); - if (save_root.Failed() || *save_root == nullptr) { + if (result != ResultSuccess || save_root == nullptr) { LOG_ERROR(Service_FS, "The save root for the space_id={:02X} was invalid!", space); return; } - for (const auto& type : (*save_root)->GetSubdirectories()) { + for (const auto& type : save_root->GetSubdirectories()) { if (type->GetName() == "save") { for (const auto& save_id : type->GetSubdirectories()) { for (const auto& user_id : save_id->GetSubdirectories()) { @@ -837,9 +842,11 @@ void FSP_SRV::OpenFileSystemWithPatch(HLERequestContext& ctx) { void FSP_SRV::OpenSdCardFileSystem(HLERequestContext& ctx) { LOG_DEBUG(Service_FS, "called"); - auto filesystem = - std::make_shared<IFileSystem>(system, fsc.OpenSDMC().Unwrap(), - SizeGetter::FromStorageId(fsc, FileSys::StorageId::SdCard)); + FileSys::VirtualDir sdmc_dir{}; + fsc.OpenSDMC(&sdmc_dir); + + auto filesystem = std::make_shared<IFileSystem>( + system, sdmc_dir, SizeGetter::FromStorageId(fsc, FileSys::StorageId::SdCard)); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); @@ -856,7 +863,8 @@ void FSP_SRV::CreateSaveDataFileSystem(HLERequestContext& ctx) { LOG_DEBUG(Service_FS, "called save_struct = {}, uid = {:016X}{:016X}", save_struct.DebugInfo(), uid[1], uid[0]); - fsc.CreateSaveData(FileSys::SaveDataSpaceId::NandUser, save_struct); + FileSys::VirtualDir save_data_dir{}; + fsc.CreateSaveData(&save_data_dir, FileSys::SaveDataSpaceId::NandUser, save_struct); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -874,8 +882,9 @@ void FSP_SRV::OpenSaveDataFileSystem(HLERequestContext& ctx) { LOG_INFO(Service_FS, "called."); - auto dir = fsc.OpenSaveData(parameters.space_id, parameters.attribute); - if (dir.Failed()) { + FileSys::VirtualDir dir{}; + auto result = fsc.OpenSaveData(&dir, parameters.space_id, parameters.attribute); + if (result != ResultSuccess) { IPC::ResponseBuilder rb{ctx, 2, 0, 0}; rb.Push(FileSys::ERROR_ENTITY_NOT_FOUND); return; @@ -899,8 +908,8 @@ void FSP_SRV::OpenSaveDataFileSystem(HLERequestContext& ctx) { ASSERT(false); } - auto filesystem = std::make_shared<IFileSystem>(system, std::move(dir.Unwrap()), - SizeGetter::FromStorageId(fsc, id)); + auto filesystem = + std::make_shared<IFileSystem>(system, std::move(dir), SizeGetter::FromStorageId(fsc, id)); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); @@ -970,7 +979,7 @@ void FSP_SRV::OpenDataStorageByCurrentProcess(HLERequestContext& ctx) { if (!romfs) { auto current_romfs = fsc.OpenRomFSCurrentProcess(); - if (current_romfs.Failed()) { + if (!current_romfs) { // TODO (bunnei): Find the right error code to use here LOG_CRITICAL(Service_FS, "no file system interface available!"); IPC::ResponseBuilder rb{ctx, 2}; @@ -978,7 +987,7 @@ void FSP_SRV::OpenDataStorageByCurrentProcess(HLERequestContext& ctx) { return; } - romfs = current_romfs.Unwrap(); + romfs = current_romfs; } auto storage = std::make_shared<IStorage>(system, romfs); @@ -999,7 +1008,7 @@ void FSP_SRV::OpenDataStorageByDataId(HLERequestContext& ctx) { auto data = fsc.OpenRomFS(title_id, storage_id, FileSys::ContentRecordType::Data); - if (data.Failed()) { + if (!data) { const auto archive = FileSys::SystemArchive::SynthesizeSystemArchive(title_id); if (archive != nullptr) { @@ -1020,8 +1029,9 @@ void FSP_SRV::OpenDataStorageByDataId(HLERequestContext& ctx) { const FileSys::PatchManager pm{title_id, fsc, content_provider}; + auto base = fsc.OpenBaseNca(title_id, storage_id, FileSys::ContentRecordType::Data); auto storage = std::make_shared<IStorage>( - system, pm.PatchRomFS(std::move(data.Unwrap()), 0, FileSys::ContentRecordType::Data)); + system, pm.PatchRomFS(base.get(), std::move(data), FileSys::ContentRecordType::Data)); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); @@ -1051,7 +1061,7 @@ void FSP_SRV::OpenDataStorageWithProgramIndex(HLERequestContext& ctx) { fsc.OpenPatchedRomFSWithProgramIndex(system.GetApplicationProcessProgramID(), program_index, FileSys::ContentRecordType::Program); - if (patched_romfs.Failed()) { + if (!patched_romfs) { // TODO: Find the right error code to use here LOG_ERROR(Service_FS, "could not open storage with program_index={}", program_index); @@ -1060,7 +1070,7 @@ void FSP_SRV::OpenDataStorageWithProgramIndex(HLERequestContext& ctx) { return; } - auto storage = std::make_shared<IStorage>(system, std::move(patched_romfs.Unwrap())); + auto storage = std::make_shared<IStorage>(system, std::move(patched_romfs)); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(ResultSuccess); diff --git a/src/core/hle/service/glue/arp.cpp b/src/core/hle/service/glue/arp.cpp index ed6fcb5f6..6f1151b03 100644 --- a/src/core/hle/service/glue/arp.cpp +++ b/src/core/hle/service/glue/arp.cpp @@ -65,18 +65,19 @@ void ARP_R::GetApplicationLaunchProperty(HLERequestContext& ctx) { return; } - const auto res = manager.GetLaunchProperty(*title_id); + ApplicationLaunchProperty launch_property{}; + const auto res = manager.GetLaunchProperty(&launch_property, *title_id); - if (res.Failed()) { + if (res != ResultSuccess) { LOG_ERROR(Service_ARP, "Failed to get launch property!"); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(res.Code()); + rb.Push(res); return; } IPC::ResponseBuilder rb{ctx, 6}; rb.Push(ResultSuccess); - rb.PushRaw(*res); + rb.PushRaw(launch_property); } void ARP_R::GetApplicationLaunchPropertyWithApplicationId(HLERequestContext& ctx) { @@ -85,18 +86,19 @@ void ARP_R::GetApplicationLaunchPropertyWithApplicationId(HLERequestContext& ctx LOG_DEBUG(Service_ARP, "called, title_id={:016X}", title_id); - const auto res = manager.GetLaunchProperty(title_id); + ApplicationLaunchProperty launch_property{}; + const auto res = manager.GetLaunchProperty(&launch_property, title_id); - if (res.Failed()) { + if (res != ResultSuccess) { LOG_ERROR(Service_ARP, "Failed to get launch property!"); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(res.Code()); + rb.Push(res); return; } IPC::ResponseBuilder rb{ctx, 6}; rb.Push(ResultSuccess); - rb.PushRaw(*res); + rb.PushRaw(launch_property); } void ARP_R::GetApplicationControlProperty(HLERequestContext& ctx) { @@ -113,16 +115,17 @@ void ARP_R::GetApplicationControlProperty(HLERequestContext& ctx) { return; } - const auto res = manager.GetControlProperty(*title_id); + std::vector<u8> nacp_data; + const auto res = manager.GetControlProperty(&nacp_data, *title_id); - if (res.Failed()) { + if (res != ResultSuccess) { LOG_ERROR(Service_ARP, "Failed to get control property!"); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(res.Code()); + rb.Push(res); return; } - ctx.WriteBuffer(*res); + ctx.WriteBuffer(nacp_data); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -134,16 +137,17 @@ void ARP_R::GetApplicationControlPropertyWithApplicationId(HLERequestContext& ct LOG_DEBUG(Service_ARP, "called, title_id={:016X}", title_id); - const auto res = manager.GetControlProperty(title_id); + std::vector<u8> nacp_data; + const auto res = manager.GetControlProperty(&nacp_data, title_id); - if (res.Failed()) { + if (res != ResultSuccess) { LOG_ERROR(Service_ARP, "Failed to get control property!"); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(res.Code()); + rb.Push(res); return; } - ctx.WriteBuffer(*res); + ctx.WriteBuffer(nacp_data); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); diff --git a/src/core/hle/service/glue/glue_manager.cpp b/src/core/hle/service/glue/glue_manager.cpp index 4bf67921b..22f001704 100644 --- a/src/core/hle/service/glue/glue_manager.cpp +++ b/src/core/hle/service/glue/glue_manager.cpp @@ -15,7 +15,8 @@ ARPManager::ARPManager() = default; ARPManager::~ARPManager() = default; -ResultVal<ApplicationLaunchProperty> ARPManager::GetLaunchProperty(u64 title_id) const { +Result ARPManager::GetLaunchProperty(ApplicationLaunchProperty* out_launch_property, + u64 title_id) const { if (title_id == 0) { return Glue::ResultInvalidProcessId; } @@ -25,10 +26,11 @@ ResultVal<ApplicationLaunchProperty> ARPManager::GetLaunchProperty(u64 title_id) return Glue::ResultProcessIdNotRegistered; } - return iter->second.launch; + *out_launch_property = iter->second.launch; + return ResultSuccess; } -ResultVal<std::vector<u8>> ARPManager::GetControlProperty(u64 title_id) const { +Result ARPManager::GetControlProperty(std::vector<u8>* out_control_property, u64 title_id) const { if (title_id == 0) { return Glue::ResultInvalidProcessId; } @@ -38,7 +40,8 @@ ResultVal<std::vector<u8>> ARPManager::GetControlProperty(u64 title_id) const { return Glue::ResultProcessIdNotRegistered; } - return iter->second.control; + *out_control_property = iter->second.control; + return ResultSuccess; } Result ARPManager::Register(u64 title_id, ApplicationLaunchProperty launch, diff --git a/src/core/hle/service/glue/glue_manager.h b/src/core/hle/service/glue/glue_manager.h index 1cf53d9d9..216aa34c1 100644 --- a/src/core/hle/service/glue/glue_manager.h +++ b/src/core/hle/service/glue/glue_manager.h @@ -32,12 +32,12 @@ public: // Returns the ApplicationLaunchProperty corresponding to the provided title ID if it was // previously registered, otherwise ResultProcessIdNotRegistered if it was never registered or // ResultInvalidProcessId if the title ID is 0. - ResultVal<ApplicationLaunchProperty> GetLaunchProperty(u64 title_id) const; + Result GetLaunchProperty(ApplicationLaunchProperty* out_launch_property, u64 title_id) const; // Returns a vector of the raw bytes of NACP data (necessarily 0x4000 in size) corresponding to // the provided title ID if it was previously registered, otherwise ResultProcessIdNotRegistered // if it was never registered or ResultInvalidProcessId if the title ID is 0. - ResultVal<std::vector<u8>> GetControlProperty(u64 title_id) const; + Result GetControlProperty(std::vector<u8>* out_control_property, u64 title_id) const; // Adds a new entry to the internal database with the provided parameters, returning // ResultProcessIdNotRegistered if attempting to re-register a title ID without an intermediate diff --git a/src/core/hle/service/hid/controllers/gesture.cpp b/src/core/hle/service/hid/controllers/gesture.cpp index 03432f7cb..63eecd42b 100644 --- a/src/core/hle/service/hid/controllers/gesture.cpp +++ b/src/core/hle/service/hid/controllers/gesture.cpp @@ -331,7 +331,7 @@ Controller_Gesture::GestureProperties Controller_Gesture::GetGestureProperties() }; // Hack: There is no touch in docked but games still allow it - if (Settings::values.use_docked_mode.GetValue()) { + if (Settings::IsDockedMode()) { gesture.points[id] = { .x = static_cast<s32>(active_x * Layout::ScreenDocked::Width), .y = static_cast<s32>(active_y * Layout::ScreenDocked::Height), diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index 28818c813..146bb486d 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -193,7 +193,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { shared_memory->system_properties.use_minus.Assign(1); shared_memory->system_properties.is_charging_joy_dual.Assign( battery_level.dual.is_charging); - shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::SwitchProController; + shared_memory->applet_footer_type = AppletFooterUiType::SwitchProController; shared_memory->sixaxis_fullkey_properties.is_newly_assigned.Assign(1); break; case Core::HID::NpadStyleIndex::Handheld: @@ -216,8 +216,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { shared_memory->system_properties.is_charging_joy_right.Assign( battery_level.right.is_charging); shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual; - shared_memory->applet_nfc_xcd.applet_footer.type = - AppletFooterUiType::HandheldJoyConLeftJoyConRight; + shared_memory->applet_footer_type = AppletFooterUiType::HandheldJoyConLeftJoyConRight; shared_memory->sixaxis_handheld_properties.is_newly_assigned.Assign(1); break; case Core::HID::NpadStyleIndex::JoyconDual: @@ -247,19 +246,19 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual; if (controller.is_dual_left_connected && controller.is_dual_right_connected) { - shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDual; + shared_memory->applet_footer_type = AppletFooterUiType::JoyDual; shared_memory->fullkey_color.fullkey = body_colors.left; shared_memory->battery_level_dual = battery_level.left.battery_level; shared_memory->system_properties.is_charging_joy_dual.Assign( battery_level.left.is_charging); } else if (controller.is_dual_left_connected) { - shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDualLeftOnly; + shared_memory->applet_footer_type = AppletFooterUiType::JoyDualLeftOnly; shared_memory->fullkey_color.fullkey = body_colors.left; shared_memory->battery_level_dual = battery_level.left.battery_level; shared_memory->system_properties.is_charging_joy_dual.Assign( battery_level.left.is_charging); } else { - shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDualRightOnly; + shared_memory->applet_footer_type = AppletFooterUiType::JoyDualRightOnly; shared_memory->fullkey_color.fullkey = body_colors.right; shared_memory->battery_level_dual = battery_level.right.battery_level; shared_memory->system_properties.is_charging_joy_dual.Assign( @@ -278,7 +277,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { shared_memory->system_properties.use_minus.Assign(1); shared_memory->system_properties.is_charging_joy_left.Assign( battery_level.left.is_charging); - shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyLeftHorizontal; + shared_memory->applet_footer_type = AppletFooterUiType::JoyLeftHorizontal; shared_memory->sixaxis_left_properties.is_newly_assigned.Assign(1); break; case Core::HID::NpadStyleIndex::JoyconRight: @@ -293,7 +292,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { shared_memory->system_properties.use_plus.Assign(1); shared_memory->system_properties.is_charging_joy_right.Assign( battery_level.right.is_charging); - shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyRightHorizontal; + shared_memory->applet_footer_type = AppletFooterUiType::JoyRightHorizontal; shared_memory->sixaxis_right_properties.is_newly_assigned.Assign(1); break; case Core::HID::NpadStyleIndex::GameCube: @@ -314,12 +313,12 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) { case Core::HID::NpadStyleIndex::SNES: shared_memory->style_tag.lucia.Assign(1); shared_memory->device_type.fullkey.Assign(1); - shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::Lucia; + shared_memory->applet_footer_type = AppletFooterUiType::Lucia; break; case Core::HID::NpadStyleIndex::N64: shared_memory->style_tag.lagoon.Assign(1); shared_memory->device_type.fullkey.Assign(1); - shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::Lagon; + shared_memory->applet_footer_type = AppletFooterUiType::Lagon; break; case Core::HID::NpadStyleIndex::SegaGenesis: shared_memory->style_tag.lager.Assign(1); @@ -419,9 +418,17 @@ void Controller_NPad::RequestPadStateUpdate(Core::HID::NpadIdType npad_id) { std::scoped_lock lock{mutex}; auto& controller = GetControllerFromNpadIdType(npad_id); const auto controller_type = controller.device->GetNpadStyleIndex(); + + if (!controller.device->IsConnected() && controller.is_connected) { + DisconnectNpad(npad_id); + return; + } if (!controller.device->IsConnected()) { return; } + if (controller.device->IsConnected() && !controller.is_connected) { + InitNewlyAddedController(npad_id); + } // This function is unique to yuzu for the turbo buttons and motion to work properly controller.device->StatusUpdate(); @@ -468,6 +475,10 @@ void Controller_NPad::RequestPadStateUpdate(Core::HID::NpadIdType npad_id) { pad_entry.npad_buttons.l.Assign(button_state.zl); pad_entry.npad_buttons.r.Assign(button_state.zr); } + + if (pad_entry.npad_buttons.raw != Core::HID::NpadButton::None) { + hid_core.SetLastActiveController(npad_id); + } } void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) { @@ -736,14 +747,6 @@ void Controller_NPad::SetSupportedStyleSet(Core::HID::NpadStyleTag style_set) { // Once SetSupportedStyleSet is called controllers are fully initialized is_controller_initialized = true; - - // Connect all active controllers - for (auto& controller : controller_data) { - const auto& device = controller.device; - if (device->IsConnected()) { - AddNewControllerAt(device->GetNpadStyleIndex(), device->GetNpadIdType()); - } - } } Core::HID::NpadStyleTag Controller_NPad::GetSupportedStyleSet() const { @@ -1116,7 +1119,7 @@ Result Controller_NPad::DisconnectNpad(Core::HID::NpadIdType npad_id) { .left = {}, .right = {}, }; - shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::None; + shared_memory->applet_footer_type = AppletFooterUiType::None; controller.is_dual_left_connected = true; controller.is_dual_right_connected = true; @@ -1508,6 +1511,31 @@ Core::HID::NpadButton Controller_NPad::GetAndResetPressState() { return static_cast<Core::HID::NpadButton>(press_state.exchange(0)); } +void Controller_NPad::ApplyNpadSystemCommonPolicy() { + Core::HID::NpadStyleTag styletag{}; + styletag.fullkey.Assign(1); + styletag.handheld.Assign(1); + styletag.joycon_dual.Assign(1); + styletag.system_ext.Assign(1); + styletag.system.Assign(1); + SetSupportedStyleSet(styletag); + + SetNpadHandheldActivationMode(NpadHandheldActivationMode::Dual); + + supported_npad_id_types.clear(); + supported_npad_id_types.resize(10); + supported_npad_id_types[0] = Core::HID::NpadIdType::Player1; + supported_npad_id_types[1] = Core::HID::NpadIdType::Player2; + supported_npad_id_types[2] = Core::HID::NpadIdType::Player3; + supported_npad_id_types[3] = Core::HID::NpadIdType::Player4; + supported_npad_id_types[4] = Core::HID::NpadIdType::Player5; + supported_npad_id_types[5] = Core::HID::NpadIdType::Player6; + supported_npad_id_types[6] = Core::HID::NpadIdType::Player7; + supported_npad_id_types[7] = Core::HID::NpadIdType::Player8; + supported_npad_id_types[8] = Core::HID::NpadIdType::Other; + supported_npad_id_types[9] = Core::HID::NpadIdType::Handheld; +} + bool Controller_NPad::IsControllerSupported(Core::HID::NpadStyleIndex controller) const { if (controller == Core::HID::NpadStyleIndex::Handheld) { const bool support_handheld = @@ -1518,7 +1546,7 @@ bool Controller_NPad::IsControllerSupported(Core::HID::NpadStyleIndex controller return false; } // Handheld shouldn't be supported in docked mode - if (Settings::values.use_docked_mode.GetValue()) { + if (Settings::IsDockedMode()) { return false; } diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h index 776411261..949e58a4c 100644 --- a/src/core/hle/service/hid/controllers/npad.h +++ b/src/core/hle/service/hid/controllers/npad.h @@ -190,6 +190,8 @@ public: // Specifically for cheat engine and other features. Core::HID::NpadButton GetAndResetPressState(); + void ApplyNpadSystemCommonPolicy(); + static bool IsNpadIdValid(Core::HID::NpadIdType npad_id); static Result IsDeviceHandleValid(const Core::HID::VibrationDeviceHandle& device_handle); static Result VerifyValidSixAxisSensorHandle( @@ -360,7 +362,7 @@ private: enum class AppletFooterUiType : u8 { None = 0, HandheldNone = 1, - HandheldJoyConLeftOnly = 1, + HandheldJoyConLeftOnly = 2, HandheldJoyConRightOnly = 3, HandheldJoyConLeftJoyConRight = 4, JoyDual = 5, @@ -382,13 +384,6 @@ private: Lagon = 21, }; - struct AppletFooterUi { - AppletFooterUiAttributes attributes{}; - AppletFooterUiType type{AppletFooterUiType::None}; - INSERT_PADDING_BYTES(0x5B); // Reserved - }; - static_assert(sizeof(AppletFooterUi) == 0x60, "AppletFooterUi is an invalid size"); - // This is nn::hid::NpadLarkType enum class NpadLarkType : u32 { Invalid, @@ -419,13 +414,6 @@ private: U, }; - struct AppletNfcXcd { - union { - AppletFooterUi applet_footer{}; - Lifo<NfcXcdDeviceHandleStateImpl, 0x2> nfc_xcd_device_lifo; - }; - }; - // This is nn::hid::detail::NpadInternalState struct NpadInternalState { Core::HID::NpadStyleTag style_tag{Core::HID::NpadStyleSet::None}; @@ -452,7 +440,9 @@ private: Core::HID::NpadBatteryLevel battery_level_dual{}; Core::HID::NpadBatteryLevel battery_level_left{}; Core::HID::NpadBatteryLevel battery_level_right{}; - AppletNfcXcd applet_nfc_xcd{}; + AppletFooterUiAttributes applet_footer_attributes{}; + AppletFooterUiType applet_footer_type{AppletFooterUiType::None}; + INSERT_PADDING_BYTES(0x5B); // Reserved INSERT_PADDING_BYTES(0x20); // Unknown Lifo<NpadGcTriggerState, hid_entry_count> gc_trigger_lifo{}; NpadLarkType lark_type_l_and_main{}; diff --git a/src/core/hle/service/hid/controllers/touchscreen.h b/src/core/hle/service/hid/controllers/touchscreen.h index e57a3a80e..dd00921fd 100644 --- a/src/core/hle/service/hid/controllers/touchscreen.h +++ b/src/core/hle/service/hid/controllers/touchscreen.h @@ -16,22 +16,6 @@ class EmulatedConsole; namespace Service::HID { class Controller_Touchscreen final : public ControllerBase { public: - // This is nn::hid::TouchScreenModeForNx - enum class TouchScreenModeForNx : u8 { - UseSystemSetting, - Finger, - Heat2, - }; - - // This is nn::hid::TouchScreenConfigurationForNx - struct TouchScreenConfigurationForNx { - TouchScreenModeForNx mode{TouchScreenModeForNx::UseSystemSetting}; - INSERT_PADDING_BYTES_NOINIT(0x7); - INSERT_PADDING_BYTES_NOINIT(0xF); // Reserved - }; - static_assert(sizeof(TouchScreenConfigurationForNx) == 0x17, - "TouchScreenConfigurationForNx is an invalid size"); - explicit Controller_Touchscreen(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_); ~Controller_Touchscreen() override; diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 2bf1d8a27..4d70006c1 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -231,8 +231,10 @@ std::shared_ptr<IAppletResource> Hid::GetAppletResource() { return applet_resource; } -Hid::Hid(Core::System& system_) - : ServiceFramework{system_, "hid"}, service_context{system_, service_name} { +Hid::Hid(Core::System& system_, std::shared_ptr<IAppletResource> applet_resource_) + : ServiceFramework{system_, "hid"}, applet_resource{applet_resource_}, service_context{ + system_, + service_name} { // clang-format off static const FunctionInfo functions[] = { {0, &Hid::CreateAppletResource, "CreateAppletResource"}, @@ -2368,7 +2370,7 @@ void Hid::GetNpadCommunicationMode(HLERequestContext& ctx) { void Hid::SetTouchScreenConfiguration(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto touchscreen_mode{rp.PopRaw<Controller_Touchscreen::TouchScreenConfigurationForNx>()}; + const auto touchscreen_mode{rp.PopRaw<Core::HID::TouchScreenConfigurationForNx>()}; const auto applet_resource_user_id{rp.Pop<u64>()}; LOG_WARNING(Service_HID, "(STUBBED) called, touchscreen_mode={}, applet_resource_user_id={}", @@ -2543,7 +2545,9 @@ public: class HidSys final : public ServiceFramework<HidSys> { public: - explicit HidSys(Core::System& system_) : ServiceFramework{system_, "hid:sys"} { + explicit HidSys(Core::System& system_, std::shared_ptr<IAppletResource> applet_resource_) + : ServiceFramework{system_, "hid:sys"}, service_context{system_, "hid:sys"}, + applet_resource{applet_resource_} { // clang-format off static const FunctionInfo functions[] = { {31, nullptr, "SendKeyboardLockKeyEvent"}, @@ -2568,7 +2572,7 @@ public: {303, &HidSys::ApplyNpadSystemCommonPolicy, "ApplyNpadSystemCommonPolicy"}, {304, nullptr, "EnableAssigningSingleOnSlSrPress"}, {305, nullptr, "DisableAssigningSingleOnSlSrPress"}, - {306, nullptr, "GetLastActiveNpad"}, + {306, &HidSys::GetLastActiveNpad, "GetLastActiveNpad"}, {307, nullptr, "GetNpadSystemExtStyle"}, {308, nullptr, "ApplyNpadSystemCommonPolicyFull"}, {309, nullptr, "GetNpadFullKeyGripColor"}, @@ -2624,7 +2628,7 @@ public: {700, nullptr, "ActivateUniquePad"}, {702, nullptr, "AcquireUniquePadConnectionEventHandle"}, {703, nullptr, "GetUniquePadIds"}, - {751, nullptr, "AcquireJoyDetachOnBluetoothOffEventHandle"}, + {751, &HidSys::AcquireJoyDetachOnBluetoothOffEventHandle, "AcquireJoyDetachOnBluetoothOffEventHandle"}, {800, nullptr, "ListSixAxisSensorHandles"}, {801, nullptr, "IsSixAxisSensorUserCalibrationSupported"}, {802, nullptr, "ResetSixAxisSensorCalibrationValues"}, @@ -2650,7 +2654,7 @@ public: {830, nullptr, "SetNotificationLedPattern"}, {831, nullptr, "SetNotificationLedPatternWithTimeout"}, {832, nullptr, "PrepareHidsForNotificationWake"}, - {850, nullptr, "IsUsbFullKeyControllerEnabled"}, + {850, &HidSys::IsUsbFullKeyControllerEnabled, "IsUsbFullKeyControllerEnabled"}, {851, nullptr, "EnableUsbFullKeyController"}, {852, nullptr, "IsUsbConnected"}, {870, nullptr, "IsHandheldButtonPressedOnConsoleMode"}, @@ -2682,7 +2686,7 @@ public: {1150, nullptr, "SetTouchScreenMagnification"}, {1151, nullptr, "GetTouchScreenFirmwareVersion"}, {1152, nullptr, "SetTouchScreenDefaultConfiguration"}, - {1153, nullptr, "GetTouchScreenDefaultConfiguration"}, + {1153, &HidSys::GetTouchScreenDefaultConfiguration, "GetTouchScreenDefaultConfiguration"}, {1154, nullptr, "IsFirmwareAvailableForNotification"}, {1155, nullptr, "SetForceHandheldStyleVibration"}, {1156, nullptr, "SendConnectionTriggerWithoutTimeoutEvent"}, @@ -2749,37 +2753,102 @@ public: // clang-format on RegisterHandlers(functions); + + joy_detach_event = service_context.CreateEvent("HidSys::JoyDetachEvent"); } private: void ApplyNpadSystemCommonPolicy(HLERequestContext& ctx) { - // We already do this for homebrew so we can just stub it out LOG_WARNING(Service_HID, "called"); + GetAppletResource() + ->GetController<Controller_NPad>(HidController::NPad) + .ApplyNpadSystemCommonPolicy(); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } + void GetLastActiveNpad(HLERequestContext& ctx) { + LOG_DEBUG(Service_HID, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushEnum(system.HIDCore().GetLastActiveController()); + } + void GetUniquePadsFromNpad(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto npad_id_type{rp.PopEnum<Core::HID::NpadIdType>()}; - const s64 total_entries = 0; LOG_WARNING(Service_HID, "(STUBBED) called, npad_id_type={}", npad_id_type); + const std::vector<Core::HID::UniquePadId> unique_pads{}; + + ctx.WriteBuffer(unique_pads); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(static_cast<u32>(unique_pads.size())); + } + + void AcquireJoyDetachOnBluetoothOffEventHandle(HLERequestContext& ctx) { + LOG_INFO(Service_AM, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(joy_detach_event->GetReadableEvent()); + } + + void IsUsbFullKeyControllerEnabled(HLERequestContext& ctx) { + const bool is_enabled = false; + + LOG_WARNING(Service_HID, "(STUBBED) called, is_enabled={}", is_enabled); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(total_entries); + rb.Push(is_enabled); } + + void GetTouchScreenDefaultConfiguration(HLERequestContext& ctx) { + LOG_WARNING(Service_HID, "(STUBBED) called"); + + Core::HID::TouchScreenConfigurationForNx touchscreen_config{ + .mode = Core::HID::TouchScreenModeForNx::Finger, + }; + + if (touchscreen_config.mode != Core::HID::TouchScreenModeForNx::Heat2 && + touchscreen_config.mode != Core::HID::TouchScreenModeForNx::Finger) { + touchscreen_config.mode = Core::HID::TouchScreenModeForNx::UseSystemSetting; + } + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(ResultSuccess); + rb.PushRaw(touchscreen_config); + } + + std::shared_ptr<IAppletResource> GetAppletResource() { + if (applet_resource == nullptr) { + applet_resource = std::make_shared<IAppletResource>(system, service_context); + } + + return applet_resource; + } + + Kernel::KEvent* joy_detach_event; + KernelHelpers::ServiceContext service_context; + std::shared_ptr<IAppletResource> applet_resource; }; void LoopProcess(Core::System& system) { auto server_manager = std::make_unique<ServerManager>(system); + std::shared_ptr<IAppletResource> applet_resource; - server_manager->RegisterNamedService("hid", std::make_shared<Hid>(system)); + server_manager->RegisterNamedService("hid", std::make_shared<Hid>(system, applet_resource)); server_manager->RegisterNamedService("hidbus", std::make_shared<HidBus>(system)); server_manager->RegisterNamedService("hid:dbg", std::make_shared<HidDbg>(system)); - server_manager->RegisterNamedService("hid:sys", std::make_shared<HidSys>(system)); + server_manager->RegisterNamedService("hid:sys", + std::make_shared<HidSys>(system, applet_resource)); server_manager->RegisterNamedService("irs", std::make_shared<Service::IRS::IRS>(system)); server_manager->RegisterNamedService("irs:sys", diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index f247b83c2..0ca43de93 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -95,7 +95,7 @@ private: class Hid final : public ServiceFramework<Hid> { public: - explicit Hid(Core::System& system_); + explicit Hid(Core::System& system_, std::shared_ptr<IAppletResource> applet_resource_); ~Hid() override; std::shared_ptr<IAppletResource> GetAppletResource(); diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp index 055c0a2db..c73035c77 100644 --- a/src/core/hle/service/ldr/ldr.cpp +++ b/src/core/hle/service/ldr/ldr.cpp @@ -357,7 +357,8 @@ public: return ResultSuccess; } - ResultVal<VAddr> MapProcessCodeMemory(Kernel::KProcess* process, VAddr base_addr, u64 size) { + Result MapProcessCodeMemory(VAddr* out_map_location, Kernel::KProcess* process, VAddr base_addr, + u64 size) { auto& page_table{process->GetPageTable()}; VAddr addr{}; @@ -372,20 +373,21 @@ public: R_TRY(result); if (ValidateRegionForMap(page_table, addr, size)) { - return addr; + *out_map_location = addr; + return ResultSuccess; } } return ERROR_INSUFFICIENT_ADDRESS_SPACE; } - ResultVal<VAddr> MapNro(Kernel::KProcess* process, VAddr nro_addr, std::size_t nro_size, - VAddr bss_addr, std::size_t bss_size, std::size_t size) { + Result MapNro(VAddr* out_map_location, Kernel::KProcess* process, VAddr nro_addr, + std::size_t nro_size, VAddr bss_addr, std::size_t bss_size, std::size_t size) { for (std::size_t retry = 0; retry < MAXIMUM_MAP_RETRIES; retry++) { auto& page_table{process->GetPageTable()}; VAddr addr{}; - CASCADE_RESULT(addr, MapProcessCodeMemory(process, nro_addr, nro_size)); + R_TRY(MapProcessCodeMemory(&addr, process, nro_addr, nro_size)); if (bss_size) { auto block_guard = detail::ScopeExit([&] { @@ -411,7 +413,8 @@ public: } if (ValidateRegionForMap(page_table, addr, size)) { - return addr; + *out_map_location = addr; + return ResultSuccess; } } @@ -437,9 +440,9 @@ public: CopyCode(nro_addr + nro_header.segment_headers[DATA_INDEX].memory_offset, data_start, nro_header.segment_headers[DATA_INDEX].memory_size); - CASCADE_CODE(process->GetPageTable().SetProcessMemoryPermission( + R_TRY(process->GetPageTable().SetProcessMemoryPermission( text_start, ro_start - text_start, Kernel::Svc::MemoryPermission::ReadExecute)); - CASCADE_CODE(process->GetPageTable().SetProcessMemoryPermission( + R_TRY(process->GetPageTable().SetProcessMemoryPermission( ro_start, data_start - ro_start, Kernel::Svc::MemoryPermission::Read)); return process->GetPageTable().SetProcessMemoryPermission( @@ -542,31 +545,32 @@ public: } // Map memory for the NRO - const auto map_result{MapNro(system.ApplicationProcess(), nro_address, nro_size, - bss_address, bss_size, nro_size + bss_size)}; - if (map_result.Failed()) { + VAddr map_location{}; + const auto map_result{MapNro(&map_location, system.ApplicationProcess(), nro_address, + nro_size, bss_address, bss_size, nro_size + bss_size)}; + if (map_result != ResultSuccess) { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(map_result.Code()); + rb.Push(map_result); } // Load the NRO into the mapped memory if (const auto result{ - LoadNro(system.ApplicationProcess(), header, nro_address, *map_result)}; + LoadNro(system.ApplicationProcess(), header, nro_address, map_location)}; result.IsError()) { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(map_result.Code()); + rb.Push(result); } // Track the loaded NRO - nro.insert_or_assign(*map_result, - NROInfo{hash, *map_result, nro_size, bss_address, bss_size, + nro.insert_or_assign(map_location, + NROInfo{hash, map_location, nro_size, bss_address, bss_size, header.segment_headers[TEXT_INDEX].memory_size, header.segment_headers[RO_INDEX].memory_size, header.segment_headers[DATA_INDEX].memory_size, nro_address}); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); - rb.Push(*map_result); + rb.Push(map_location); } Result UnmapNro(const NROInfo& info) { @@ -574,19 +578,19 @@ public: auto& page_table{system.ApplicationProcess()->GetPageTable()}; if (info.bss_size != 0) { - CASCADE_CODE(page_table.UnmapCodeMemory( + R_TRY(page_table.UnmapCodeMemory( info.nro_address + info.text_size + info.ro_size + info.data_size, info.bss_address, info.bss_size, Kernel::KPageTable::ICacheInvalidationStrategy::InvalidateRange)); } - CASCADE_CODE(page_table.UnmapCodeMemory( + R_TRY(page_table.UnmapCodeMemory( info.nro_address + info.text_size + info.ro_size, info.src_addr + info.text_size + info.ro_size, info.data_size, Kernel::KPageTable::ICacheInvalidationStrategy::InvalidateRange)); - CASCADE_CODE(page_table.UnmapCodeMemory( + R_TRY(page_table.UnmapCodeMemory( info.nro_address + info.text_size, info.src_addr + info.text_size, info.ro_size, Kernel::KPageTable::ICacheInvalidationStrategy::InvalidateRange)); - CASCADE_CODE(page_table.UnmapCodeMemory( + R_TRY(page_table.UnmapCodeMemory( info.nro_address, info.src_addr, info.text_size, Kernel::KPageTable::ICacheInvalidationStrategy::InvalidateRange)); return ResultSuccess; diff --git a/src/core/hle/service/mii/mii.cpp b/src/core/hle/service/mii/mii.cpp index 5c7adf97d..c28eed926 100644 --- a/src/core/hle/service/mii/mii.cpp +++ b/src/core/hle/service/mii/mii.cpp @@ -7,17 +7,21 @@ #include "core/hle/service/ipc_helpers.h" #include "core/hle/service/mii/mii.h" #include "core/hle/service/mii/mii_manager.h" +#include "core/hle/service/mii/mii_result.h" +#include "core/hle/service/mii/types/char_info.h" +#include "core/hle/service/mii/types/store_data.h" +#include "core/hle/service/mii/types/ver3_store_data.h" #include "core/hle/service/server_manager.h" #include "core/hle/service/service.h" namespace Service::Mii { -constexpr Result ERROR_INVALID_ARGUMENT{ErrorModule::Mii, 1}; - class IDatabaseService final : public ServiceFramework<IDatabaseService> { public: - explicit IDatabaseService(Core::System& system_) - : ServiceFramework{system_, "IDatabaseService"} { + explicit IDatabaseService(Core::System& system_, std::shared_ptr<MiiManager> mii_manager, + bool is_system_) + : ServiceFramework{system_, "IDatabaseService"}, manager{mii_manager}, is_system{ + is_system_} { // clang-format off static const FunctionInfo functions[] = { {0, &IDatabaseService::IsUpdated, "IsUpdated"}, @@ -28,142 +32,134 @@ public: {5, &IDatabaseService::UpdateLatest, "UpdateLatest"}, {6, &IDatabaseService::BuildRandom, "BuildRandom"}, {7, &IDatabaseService::BuildDefault, "BuildDefault"}, - {8, nullptr, "Get2"}, - {9, nullptr, "Get3"}, - {10, nullptr, "UpdateLatest1"}, - {11, nullptr, "FindIndex"}, - {12, nullptr, "Move"}, - {13, nullptr, "AddOrReplace"}, - {14, nullptr, "Delete"}, - {15, nullptr, "DestroyFile"}, - {16, nullptr, "DeleteFile"}, - {17, nullptr, "Format"}, + {8, &IDatabaseService::Get2, "Get2"}, + {9, &IDatabaseService::Get3, "Get3"}, + {10, &IDatabaseService::UpdateLatest1, "UpdateLatest1"}, + {11, &IDatabaseService::FindIndex, "FindIndex"}, + {12, &IDatabaseService::Move, "Move"}, + {13, &IDatabaseService::AddOrReplace, "AddOrReplace"}, + {14, &IDatabaseService::Delete, "Delete"}, + {15, &IDatabaseService::DestroyFile, "DestroyFile"}, + {16, &IDatabaseService::DeleteFile, "DeleteFile"}, + {17, &IDatabaseService::Format, "Format"}, {18, nullptr, "Import"}, {19, nullptr, "Export"}, - {20, nullptr, "IsBrokenDatabaseWithClearFlag"}, + {20, &IDatabaseService::IsBrokenDatabaseWithClearFlag, "IsBrokenDatabaseWithClearFlag"}, {21, &IDatabaseService::GetIndex, "GetIndex"}, {22, &IDatabaseService::SetInterfaceVersion, "SetInterfaceVersion"}, {23, &IDatabaseService::Convert, "Convert"}, - {24, nullptr, "ConvertCoreDataToCharInfo"}, - {25, nullptr, "ConvertCharInfoToCoreData"}, - {26, nullptr, "Append"}, + {24, &IDatabaseService::ConvertCoreDataToCharInfo, "ConvertCoreDataToCharInfo"}, + {25, &IDatabaseService::ConvertCharInfoToCoreData, "ConvertCharInfoToCoreData"}, + {26, &IDatabaseService::Append, "Append"}, }; // clang-format on RegisterHandlers(functions); - } -private: - template <typename T> - std::vector<u8> SerializeArray(const std::vector<T>& values) { - std::vector<u8> out(values.size() * sizeof(T)); - std::size_t offset{}; - for (const auto& value : values) { - std::memcpy(out.data() + offset, &value, sizeof(T)); - offset += sizeof(T); - } - return out; + manager->Initialize(metadata); } +private: void IsUpdated(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto source_flag{rp.PopRaw<SourceFlag>()}; LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); + const bool is_updated = manager->IsUpdated(metadata, source_flag); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(manager.CheckAndResetUpdateCounter(source_flag, current_update_counter)); + rb.Push<u8>(is_updated); } void IsFullDatabase(HLERequestContext& ctx) { LOG_DEBUG(Service_Mii, "called"); + const bool is_full_database = manager->IsFullDatabase(); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(manager.IsFullDatabase()); + rb.Push<u8>(is_full_database); } void GetCount(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto source_flag{rp.PopRaw<SourceFlag>()}; - LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); + const u32 mii_count = manager->GetCount(metadata, source_flag); + + LOG_DEBUG(Service_Mii, "called with source_flag={}, mii_count={}", source_flag, mii_count); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push<u32>(manager.GetCount(source_flag)); + rb.Push(mii_count); } void Get(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto source_flag{rp.PopRaw<SourceFlag>()}; + const auto output_size{ctx.GetWriteBufferNumElements<CharInfoElement>()}; - LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); + u32 mii_count{}; + std::vector<CharInfoElement> char_info_elements(output_size); + const auto result = manager->Get(metadata, char_info_elements, mii_count, source_flag); - const auto result{manager.GetDefault(source_flag)}; - if (result.Failed()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result.Code()); - return; + if (mii_count != 0) { + ctx.WriteBuffer(char_info_elements); } - if (result->size() > 0) { - ctx.WriteBuffer(SerializeArray(*result)); - } + LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag, + output_size, mii_count); IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.Push<u32>(static_cast<u32>(result->size())); + rb.Push(result); + rb.Push(mii_count); } void Get1(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto source_flag{rp.PopRaw<SourceFlag>()}; + const auto output_size{ctx.GetWriteBufferNumElements<CharInfo>()}; - LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); - - const auto result{manager.GetDefault(source_flag)}; - if (result.Failed()) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result.Code()); - return; - } + u32 mii_count{}; + std::vector<CharInfo> char_info(output_size); + const auto result = manager->Get(metadata, char_info, mii_count, source_flag); - std::vector<CharInfo> values; - for (const auto& element : *result) { - values.emplace_back(element.info); + if (mii_count != 0) { + ctx.WriteBuffer(char_info); } - ctx.WriteBuffer(SerializeArray(values)); + LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag, + output_size, mii_count); IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - rb.Push<u32>(static_cast<u32>(result->size())); + rb.Push(result); + rb.Push(mii_count); } void UpdateLatest(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto info{rp.PopRaw<CharInfo>()}; + const auto char_info{rp.PopRaw<CharInfo>()}; const auto source_flag{rp.PopRaw<SourceFlag>()}; - LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag); + LOG_INFO(Service_Mii, "called with source_flag={}", source_flag); - const auto result{manager.UpdateLatest(info, source_flag)}; - if (result.Failed()) { + CharInfo new_char_info{}; + const auto result = manager->UpdateLatest(metadata, new_char_info, char_info, source_flag); + if (result.IsFailure()) { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result.Code()); + rb.Push(result); return; } IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; rb.Push(ResultSuccess); - rb.PushRaw<CharInfo>(*result); + rb.PushRaw(new_char_info); } void BuildRandom(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto age{rp.PopRaw<Age>()}; const auto gender{rp.PopRaw<Gender>()}; const auto race{rp.PopRaw<Race>()}; @@ -172,28 +168,28 @@ private: if (age > Age::All) { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ERROR_INVALID_ARGUMENT); - LOG_ERROR(Service_Mii, "invalid age={}", age); + rb.Push(ResultInvalidArgument); return; } if (gender > Gender::All) { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ERROR_INVALID_ARGUMENT); - LOG_ERROR(Service_Mii, "invalid gender={}", gender); + rb.Push(ResultInvalidArgument); return; } if (race > Race::All) { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ERROR_INVALID_ARGUMENT); - LOG_ERROR(Service_Mii, "invalid race={}", race); + rb.Push(ResultInvalidArgument); return; } + CharInfo char_info{}; + manager->BuildRandom(char_info, age, gender, race); + IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; rb.Push(ResultSuccess); - rb.PushRaw<CharInfo>(manager.BuildRandom(age, gender, race)); + rb.PushRaw(char_info); } void BuildDefault(HLERequestContext& ctx) { @@ -203,16 +199,249 @@ private: LOG_DEBUG(Service_Mii, "called with index={}", index); if (index > 5) { - LOG_ERROR(Service_Mii, "invalid argument, index cannot be greater than 5 but is {:08X}", - index); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ERROR_INVALID_ARGUMENT); + rb.Push(ResultInvalidArgument); return; } + CharInfo char_info{}; + manager->BuildDefault(char_info, index); + IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; rb.Push(ResultSuccess); - rb.PushRaw<CharInfo>(manager.BuildDefault(index)); + rb.PushRaw(char_info); + } + + void Get2(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto source_flag{rp.PopRaw<SourceFlag>()}; + const auto output_size{ctx.GetWriteBufferNumElements<StoreDataElement>()}; + + u32 mii_count{}; + std::vector<StoreDataElement> store_data_elements(output_size); + const auto result = manager->Get(metadata, store_data_elements, mii_count, source_flag); + + if (mii_count != 0) { + ctx.WriteBuffer(store_data_elements); + } + + LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag, + output_size, mii_count); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(result); + rb.Push(mii_count); + } + + void Get3(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto source_flag{rp.PopRaw<SourceFlag>()}; + const auto output_size{ctx.GetWriteBufferNumElements<StoreData>()}; + + u32 mii_count{}; + std::vector<StoreData> store_data(output_size); + const auto result = manager->Get(metadata, store_data, mii_count, source_flag); + + if (mii_count != 0) { + ctx.WriteBuffer(store_data); + } + + LOG_INFO(Service_Mii, "called with source_flag={}, out_size={}, mii_count={}", source_flag, + output_size, mii_count); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(result); + rb.Push(mii_count); + } + + void UpdateLatest1(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto store_data{rp.PopRaw<StoreData>()}; + const auto source_flag{rp.PopRaw<SourceFlag>()}; + + LOG_INFO(Service_Mii, "called with source_flag={}", source_flag); + + Result result = ResultSuccess; + if (!is_system) { + result = ResultPermissionDenied; + } + + StoreData new_store_data{}; + if (result.IsSuccess()) { + result = manager->UpdateLatest(metadata, new_store_data, store_data, source_flag); + } + + if (result.IsFailure()) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } + + IPC::ResponseBuilder rb{ctx, 2 + sizeof(StoreData) / sizeof(u32)}; + rb.Push(ResultSuccess); + rb.PushRaw<StoreData>(new_store_data); + } + + void FindIndex(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto create_id{rp.PopRaw<Common::UUID>()}; + const auto is_special{rp.PopRaw<bool>()}; + + LOG_INFO(Service_Mii, "called with create_id={}, is_special={}", + create_id.FormattedString(), is_special); + + const s32 index = manager->FindIndex(create_id, is_special); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(index); + } + + void Move(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto create_id{rp.PopRaw<Common::UUID>()}; + const auto new_index{rp.PopRaw<s32>()}; + + LOG_INFO(Service_Mii, "called with create_id={}, new_index={}", create_id.FormattedString(), + new_index); + + Result result = ResultSuccess; + if (!is_system) { + result = ResultPermissionDenied; + } + + if (result.IsSuccess()) { + const u32 count = manager->GetCount(metadata, SourceFlag::Database); + if (new_index < 0 || new_index >= static_cast<s32>(count)) { + result = ResultInvalidArgument; + } + } + + if (result.IsSuccess()) { + result = manager->Move(metadata, new_index, create_id); + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + } + + void AddOrReplace(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto store_data{rp.PopRaw<StoreData>()}; + + LOG_INFO(Service_Mii, "called"); + + Result result = ResultSuccess; + + if (!is_system) { + result = ResultPermissionDenied; + } + + if (result.IsSuccess()) { + result = manager->AddOrReplace(metadata, store_data); + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + } + + void Delete(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto create_id{rp.PopRaw<Common::UUID>()}; + + LOG_INFO(Service_Mii, "called, create_id={}", create_id.FormattedString()); + + Result result = ResultSuccess; + + if (!is_system) { + result = ResultPermissionDenied; + } + + if (result.IsSuccess()) { + result = manager->Delete(metadata, create_id); + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + } + + void DestroyFile(HLERequestContext& ctx) { + // This calls nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled"); + const bool is_db_test_mode_enabled = false; + + LOG_INFO(Service_Mii, "called is_db_test_mode_enabled={}", is_db_test_mode_enabled); + + Result result = ResultSuccess; + + if (!is_db_test_mode_enabled) { + result = ResultTestModeOnly; + } + + if (result.IsSuccess()) { + result = manager->DestroyFile(metadata); + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + } + + void DeleteFile(HLERequestContext& ctx) { + // This calls nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled"); + const bool is_db_test_mode_enabled = false; + + LOG_INFO(Service_Mii, "called is_db_test_mode_enabled={}", is_db_test_mode_enabled); + + Result result = ResultSuccess; + + if (!is_db_test_mode_enabled) { + result = ResultTestModeOnly; + } + + if (result.IsSuccess()) { + result = manager->DeleteFile(); + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + } + + void Format(HLERequestContext& ctx) { + // This calls nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled"); + const bool is_db_test_mode_enabled = false; + + LOG_INFO(Service_Mii, "called is_db_test_mode_enabled={}", is_db_test_mode_enabled); + + Result result = ResultSuccess; + + if (!is_db_test_mode_enabled) { + result = ResultTestModeOnly; + } + + if (result.IsSuccess()) { + result = manager->Format(metadata); + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + } + + void IsBrokenDatabaseWithClearFlag(HLERequestContext& ctx) { + LOG_DEBUG(Service_Mii, "called"); + + bool is_broken_with_clear_flag = false; + Result result = ResultSuccess; + + if (!is_system) { + result = ResultPermissionDenied; + } + + if (result.IsSuccess()) { + is_broken_with_clear_flag = manager->IsBrokenWithClearFlag(metadata); + } + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(result); + rb.Push<u8>(is_broken_with_clear_flag); } void GetIndex(HLERequestContext& ctx) { @@ -221,19 +450,21 @@ private: LOG_DEBUG(Service_Mii, "called"); - u32 index{}; + s32 index{}; + const auto result = manager->GetIndex(metadata, info, index); + IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(manager.GetIndex(info, index)); + rb.Push(result); rb.Push(index); } void SetInterfaceVersion(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - current_interface_version = rp.PopRaw<u32>(); + const auto interface_version{rp.PopRaw<u32>()}; - LOG_DEBUG(Service_Mii, "called, interface_version={:08X}", current_interface_version); + LOG_INFO(Service_Mii, "called, interface_version={:08X}", interface_version); - UNIMPLEMENTED_IF(current_interface_version != 1); + manager->SetInterfaceVersion(metadata, interface_version); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); @@ -241,57 +472,101 @@ private: void Convert(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const auto mii_v3{rp.PopRaw<Ver3StoreData>()}; LOG_INFO(Service_Mii, "called"); + CharInfo char_info{}; + const auto result = manager->ConvertV3ToCharInfo(char_info, mii_v3); + IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; - rb.Push(ResultSuccess); - rb.PushRaw<CharInfo>(manager.ConvertV3ToCharInfo(mii_v3)); + rb.Push(result); + rb.PushRaw<CharInfo>(char_info); } - constexpr bool IsInterfaceVersionSupported(u32 interface_version) const { - return current_interface_version >= interface_version; + void ConvertCoreDataToCharInfo(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto core_data{rp.PopRaw<CoreData>()}; + + LOG_INFO(Service_Mii, "called"); + + CharInfo char_info{}; + const auto result = manager->ConvertCoreDataToCharInfo(char_info, core_data); + + IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)}; + rb.Push(result); + rb.PushRaw<CharInfo>(char_info); } - MiiManager manager; + void ConvertCharInfoToCoreData(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto char_info{rp.PopRaw<CharInfo>()}; - u32 current_interface_version{}; - u64 current_update_counter{}; -}; + LOG_INFO(Service_Mii, "called"); -class MiiDBModule final : public ServiceFramework<MiiDBModule> { -public: - explicit MiiDBModule(Core::System& system_, const char* name_) - : ServiceFramework{system_, name_} { - // clang-format off - static const FunctionInfo functions[] = { - {0, &MiiDBModule::GetDatabaseService, "GetDatabaseService"}, - }; - // clang-format on + CoreData core_data{}; + const auto result = manager->ConvertCharInfoToCoreData(core_data, char_info); - RegisterHandlers(functions); + IPC::ResponseBuilder rb{ctx, 2 + sizeof(CoreData) / sizeof(u32)}; + rb.Push(result); + rb.PushRaw<CoreData>(core_data); } -private: - void GetDatabaseService(HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(ResultSuccess); - rb.PushIpcInterface<IDatabaseService>(system); + void Append(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto char_info{rp.PopRaw<CharInfo>()}; - LOG_DEBUG(Service_Mii, "called"); + LOG_INFO(Service_Mii, "called"); + + const auto result = manager->Append(metadata, char_info); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); } + + std::shared_ptr<MiiManager> manager = nullptr; + DatabaseSessionMetadata metadata{}; + bool is_system{}; }; +MiiDBModule::MiiDBModule(Core::System& system_, const char* name_, + std::shared_ptr<MiiManager> mii_manager, bool is_system_) + : ServiceFramework{system_, name_}, manager{mii_manager}, is_system{is_system_} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &MiiDBModule::GetDatabaseService, "GetDatabaseService"}, + }; + // clang-format on + + RegisterHandlers(functions); + + if (manager == nullptr) { + manager = std::make_shared<MiiManager>(); + } +} + +MiiDBModule::~MiiDBModule() = default; + +void MiiDBModule::GetDatabaseService(HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface<IDatabaseService>(system, manager, is_system); + + LOG_DEBUG(Service_Mii, "called"); +} + +std::shared_ptr<MiiManager> MiiDBModule::GetMiiManager() { + return manager; +} + class MiiImg final : public ServiceFramework<MiiImg> { public: explicit MiiImg(Core::System& system_) : ServiceFramework{system_, "miiimg"} { // clang-format off static const FunctionInfo functions[] = { - {0, nullptr, "Initialize"}, + {0, &MiiImg::Initialize, "Initialize"}, {10, nullptr, "Reload"}, - {11, nullptr, "GetCount"}, + {11, &MiiImg::GetCount, "GetCount"}, {12, nullptr, "IsEmpty"}, {13, nullptr, "IsFull"}, {14, nullptr, "GetAttribute"}, @@ -308,13 +583,32 @@ public: RegisterHandlers(functions); } + +private: + void Initialize(HLERequestContext& ctx) { + LOG_INFO(Service_Mii, "called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } + + void GetCount(HLERequestContext& ctx) { + LOG_DEBUG(Service_Mii, "called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(0); + } }; void LoopProcess(Core::System& system) { auto server_manager = std::make_unique<ServerManager>(system); + std::shared_ptr<MiiManager> manager = nullptr; - server_manager->RegisterNamedService("mii:e", std::make_shared<MiiDBModule>(system, "mii:e")); - server_manager->RegisterNamedService("mii:u", std::make_shared<MiiDBModule>(system, "mii:u")); + server_manager->RegisterNamedService( + "mii:e", std::make_shared<MiiDBModule>(system, "mii:e", manager, true)); + server_manager->RegisterNamedService( + "mii:u", std::make_shared<MiiDBModule>(system, "mii:u", manager, false)); server_manager->RegisterNamedService("miiimg", std::make_shared<MiiImg>(system)); ServerManager::RunServer(std::move(server_manager)); } diff --git a/src/core/hle/service/mii/mii.h b/src/core/hle/service/mii/mii.h index ed4e3f62b..9aa4426f6 100644 --- a/src/core/hle/service/mii/mii.h +++ b/src/core/hle/service/mii/mii.h @@ -3,11 +3,29 @@ #pragma once +#include "core/hle/service/service.h" + namespace Core { class System; } namespace Service::Mii { +class MiiManager; + +class MiiDBModule final : public ServiceFramework<MiiDBModule> { +public: + explicit MiiDBModule(Core::System& system_, const char* name_, + std::shared_ptr<MiiManager> mii_manager, bool is_system_); + ~MiiDBModule() override; + + std::shared_ptr<MiiManager> GetMiiManager(); + +private: + void GetDatabaseService(HLERequestContext& ctx); + + std::shared_ptr<MiiManager> manager = nullptr; + bool is_system{}; +}; void LoopProcess(Core::System& system); diff --git a/src/core/hle/service/mii/mii_database.cpp b/src/core/hle/service/mii/mii_database.cpp new file mode 100644 index 000000000..3803e58e2 --- /dev/null +++ b/src/core/hle/service/mii/mii_database.cpp @@ -0,0 +1,142 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/service/mii/mii_database.h" +#include "core/hle/service/mii/mii_result.h" +#include "core/hle/service/mii/mii_util.h" + +namespace Service::Mii { + +u8 NintendoFigurineDatabase::GetDatabaseLength() const { + return database_length; +} + +bool NintendoFigurineDatabase::IsFull() const { + return database_length >= MaxDatabaseLength; +} + +StoreData NintendoFigurineDatabase::Get(std::size_t index) const { + StoreData store_data = miis.at(index); + + // This hack is to make external database dumps compatible + store_data.SetDeviceChecksum(); + + return store_data; +} + +u32 NintendoFigurineDatabase::GetCount(const DatabaseSessionMetadata& metadata) const { + if (magic == MiiMagic) { + return GetDatabaseLength(); + } + + u32 mii_count{}; + for (std::size_t index = 0; index < mii_count; ++index) { + const auto& store_data = Get(index); + if (!store_data.IsSpecial()) { + mii_count++; + } + } + + return mii_count; +} + +bool NintendoFigurineDatabase::GetIndexByCreatorId(u32& out_index, + const Common::UUID& create_id) const { + for (std::size_t index = 0; index < database_length; ++index) { + if (miis[index].GetCreateId() == create_id) { + out_index = static_cast<u32>(index); + return true; + } + } + + return false; +} + +Result NintendoFigurineDatabase::Move(u32 current_index, u32 new_index) { + if (current_index == new_index) { + return ResultNotUpdated; + } + + const StoreData store_data = miis[current_index]; + + if (new_index > current_index) { + // Shift left + const u32 index_diff = new_index - current_index; + for (std::size_t i = 0; i < index_diff; i++) { + miis[current_index + i] = miis[current_index + i + 1]; + } + } else { + // Shift right + const u32 index_diff = current_index - new_index; + for (std::size_t i = 0; i < index_diff; i++) { + miis[current_index - i] = miis[current_index - i - 1]; + } + } + + miis[new_index] = store_data; + crc = GenerateDatabaseCrc(); + return ResultSuccess; +} + +void NintendoFigurineDatabase::Replace(u32 index, const StoreData& store_data) { + miis[index] = store_data; + crc = GenerateDatabaseCrc(); +} + +void NintendoFigurineDatabase::Add(const StoreData& store_data) { + miis[database_length] = store_data; + database_length++; + crc = GenerateDatabaseCrc(); +} + +void NintendoFigurineDatabase::Delete(u32 index) { + // Shift left + const s32 new_database_size = database_length - 1; + if (static_cast<s32>(index) < new_database_size) { + for (std::size_t i = index; i < static_cast<std::size_t>(new_database_size); i++) { + miis[i] = miis[i + 1]; + } + } + + database_length = static_cast<u8>(new_database_size); + crc = GenerateDatabaseCrc(); +} + +void NintendoFigurineDatabase::CleanDatabase() { + miis = {}; + version = 1; + magic = DatabaseMagic; + database_length = 0; + crc = GenerateDatabaseCrc(); +} + +void NintendoFigurineDatabase::CorruptCrc() { + crc = GenerateDatabaseCrc(); + crc = ~crc; +} + +Result NintendoFigurineDatabase::CheckIntegrity() { + if (magic != DatabaseMagic) { + return ResultInvalidDatabaseSignature; + } + + if (version != 1) { + return ResultInvalidDatabaseVersion; + } + + if (crc != GenerateDatabaseCrc()) { + return ResultInvalidDatabaseChecksum; + } + + if (database_length >= MaxDatabaseLength) { + return ResultInvalidDatabaseLength; + } + + return ResultSuccess; +} + +u16 NintendoFigurineDatabase::GenerateDatabaseCrc() { + return MiiUtil::CalculateCrc16(&magic, sizeof(NintendoFigurineDatabase) - sizeof(crc)); +} + +} // namespace Service::Mii diff --git a/src/core/hle/service/mii/mii_database.h b/src/core/hle/service/mii/mii_database.h new file mode 100644 index 000000000..3bd240f93 --- /dev/null +++ b/src/core/hle/service/mii/mii_database.h @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/hle/result.h" +#include "core/hle/service/mii/types/store_data.h" + +namespace Service::Mii { + +constexpr std::size_t MaxDatabaseLength{100}; +constexpr u32 MiiMagic{0xa523b78f}; +constexpr u32 DatabaseMagic{0x4244464e}; // NFDB + +class NintendoFigurineDatabase { +public: + /// Returns the total mii count. + u8 GetDatabaseLength() const; + + /// Returns true if database is full. + bool IsFull() const; + + /// Returns the mii of the specified index. + StoreData Get(std::size_t index) const; + + /// Returns the total mii count. Ignoring special mii. + u32 GetCount(const DatabaseSessionMetadata& metadata) const; + + /// Returns the index of a mii. If the mii isn't found returns false. + bool GetIndexByCreatorId(u32& out_index, const Common::UUID& create_id) const; + + /// Moves the location of a specific mii. + Result Move(u32 current_index, u32 new_index); + + /// Replaces mii with new data. + void Replace(u32 index, const StoreData& store_data); + + /// Adds a new mii to the end of the database. + void Add(const StoreData& store_data); + + /// Removes mii from database and shifts left the remainding data. + void Delete(u32 index); + + /// Deletes all contents with a fresh database + void CleanDatabase(); + + /// Intentionally sets a bad checksum + void CorruptCrc(); + + /// Returns success if database is valid otherwise returns the corresponding error code. + Result CheckIntegrity(); + +private: + /// Returns the checksum of the database + u16 GenerateDatabaseCrc(); + + u32 magic{}; // 'NFDB' + std::array<StoreData, MaxDatabaseLength> miis{}; + u8 version{}; + u8 database_length{}; + u16 crc{}; +}; +static_assert(sizeof(NintendoFigurineDatabase) == 0x1A98, + "NintendoFigurineDatabase has incorrect size."); + +}; // namespace Service::Mii diff --git a/src/core/hle/service/mii/mii_database_manager.cpp b/src/core/hle/service/mii/mii_database_manager.cpp new file mode 100644 index 000000000..c39898594 --- /dev/null +++ b/src/core/hle/service/mii/mii_database_manager.cpp @@ -0,0 +1,420 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/assert.h" +#include "common/fs/file.h" +#include "common/fs/fs.h" +#include "common/fs/path_util.h" +#include "common/logging/log.h" +#include "common/string_util.h" + +#include "core/hle/service/mii/mii_database_manager.h" +#include "core/hle/service/mii/mii_result.h" +#include "core/hle/service/mii/mii_util.h" +#include "core/hle/service/mii/types/char_info.h" +#include "core/hle/service/mii/types/store_data.h" + +namespace Service::Mii { +const char* DbFileName = "MiiDatabase.dat"; + +DatabaseManager::DatabaseManager() {} + +Result DatabaseManager::MountSaveData() { + if (!is_save_data_mounted) { + system_save_dir = + Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / "system/save/8000000000000030"; + if (is_test_db) { + system_save_dir = Common::FS::GetYuzuPath(Common::FS::YuzuPath::NANDDir) / + "system/save/8000000000000031"; + } + + // mount point should be "mii:" + + if (!Common::FS::CreateDirs(system_save_dir)) { + return ResultUnknown; + } + } + + is_save_data_mounted = true; + return ResultSuccess; +} + +Result DatabaseManager::Initialize(DatabaseSessionMetadata& metadata, bool& is_database_broken) { + is_database_broken = false; + if (!is_save_data_mounted) { + return ResultInvalidArgument; + } + + database.CleanDatabase(); + update_counter++; + metadata.update_counter = update_counter; + + const Common::FS::IOFile db_file{system_save_dir / DbFileName, Common::FS::FileAccessMode::Read, + Common::FS::FileType::BinaryFile}; + + if (!db_file.IsOpen()) { + return SaveDatabase(); + } + + if (Common::FS::GetSize(system_save_dir / DbFileName) != sizeof(NintendoFigurineDatabase)) { + is_database_broken = true; + } + + if (db_file.Read(database) != 1) { + is_database_broken = true; + } + + if (is_database_broken) { + // Dragons happen here for simplicity just clean the database + LOG_ERROR(Service_Mii, "Mii database is corrupted"); + database.CleanDatabase(); + return ResultUnknown; + } + + const auto result = database.CheckIntegrity(); + + if (result.IsError()) { + LOG_ERROR(Service_Mii, "Mii database is corrupted 0x{:0x}", result.raw); + database.CleanDatabase(); + return ResultSuccess; + } + + LOG_INFO(Service_Mii, "Successfully loaded mii database. size={}", + database.GetDatabaseLength()); + return ResultSuccess; +} + +bool DatabaseManager::IsFullDatabase() const { + return database.GetDatabaseLength() == MaxDatabaseLength; +} + +bool DatabaseManager::IsModified() const { + return is_moddified; +} + +u64 DatabaseManager::GetUpdateCounter() const { + return update_counter; +} + +u32 DatabaseManager::GetCount(const DatabaseSessionMetadata& metadata) const { + const u32 database_size = database.GetDatabaseLength(); + if (metadata.magic == MiiMagic) { + return database_size; + } + + // Special mii can't be used. Skip those. + + u32 mii_count{}; + for (std::size_t index = 0; index < database_size; ++index) { + const auto& store_data = database.Get(index); + if (store_data.IsSpecial()) { + continue; + } + mii_count++; + } + + return mii_count; +} + +void DatabaseManager::Get(StoreData& out_store_data, std::size_t index, + const DatabaseSessionMetadata& metadata) const { + if (metadata.magic == MiiMagic) { + out_store_data = database.Get(index); + return; + } + + // The index refeers to the mii index without special mii. + // Search on the database until we find it + + u32 virtual_index = 0; + const u32 database_size = database.GetDatabaseLength(); + for (std::size_t i = 0; i < database_size; ++i) { + const auto& store_data = database.Get(i); + if (store_data.IsSpecial()) { + continue; + } + if (virtual_index == index) { + out_store_data = store_data; + return; + } + virtual_index++; + } + + // This function doesn't fail. It returns the first mii instead + out_store_data = database.Get(0); +} + +Result DatabaseManager::FindIndex(s32& out_index, const Common::UUID& create_id, + bool is_special) const { + u32 index{}; + const bool is_found = database.GetIndexByCreatorId(index, create_id); + + if (!is_found) { + return ResultNotFound; + } + + if (is_special) { + out_index = index; + return ResultSuccess; + } + + if (database.Get(index).IsSpecial()) { + return ResultNotFound; + } + + out_index = 0; + + if (index < 1) { + return ResultSuccess; + } + + for (std::size_t i = 0; i <= index; ++i) { + if (database.Get(i).IsSpecial()) { + continue; + } + out_index++; + } + return ResultSuccess; +} + +Result DatabaseManager::FindIndex(const DatabaseSessionMetadata& metadata, u32& out_index, + const Common::UUID& create_id) const { + u32 index{}; + const bool is_found = database.GetIndexByCreatorId(index, create_id); + + if (!is_found) { + return ResultNotFound; + } + + if (metadata.magic == MiiMagic) { + out_index = index; + return ResultSuccess; + } + + if (database.Get(index).IsSpecial()) { + return ResultNotFound; + } + + out_index = 0; + + if (index < 1) { + return ResultSuccess; + } + + // The index refeers to the mii index without special mii. + // Search on the database until we find it + + for (std::size_t i = 0; i <= index; ++i) { + const auto& store_data = database.Get(i); + if (store_data.IsSpecial()) { + continue; + } + out_index++; + } + return ResultSuccess; +} + +Result DatabaseManager::FindMoveIndex(u32& out_index, u32 new_index, + const Common::UUID& create_id) const { + const auto database_size = database.GetDatabaseLength(); + + if (database_size >= 1) { + u32 virtual_index{}; + for (std::size_t i = 0; i < database_size; ++i) { + const StoreData& store_data = database.Get(i); + if (store_data.IsSpecial()) { + continue; + } + if (virtual_index == new_index) { + const bool is_found = database.GetIndexByCreatorId(out_index, create_id); + if (!is_found) { + return ResultNotFound; + } + if (store_data.IsSpecial()) { + return ResultInvalidOperation; + } + return ResultSuccess; + } + virtual_index++; + } + } + + const bool is_found = database.GetIndexByCreatorId(out_index, create_id); + if (!is_found) { + return ResultNotFound; + } + const StoreData& store_data = database.Get(out_index); + if (store_data.IsSpecial()) { + return ResultInvalidOperation; + } + return ResultSuccess; +} + +Result DatabaseManager::Move(DatabaseSessionMetadata& metadata, u32 new_index, + const Common::UUID& create_id) { + u32 current_index{}; + if (metadata.magic == MiiMagic) { + const bool is_found = database.GetIndexByCreatorId(current_index, create_id); + if (!is_found) { + return ResultNotFound; + } + } else { + const auto result = FindMoveIndex(current_index, new_index, create_id); + if (result.IsError()) { + return result; + } + } + + const auto result = database.Move(current_index, new_index); + if (result.IsFailure()) { + return result; + } + + is_moddified = true; + update_counter++; + metadata.update_counter = update_counter; + return ResultSuccess; +} + +Result DatabaseManager::AddOrReplace(DatabaseSessionMetadata& metadata, + const StoreData& store_data) { + if (store_data.IsValid() != ValidationResult::NoErrors) { + return ResultInvalidStoreData; + } + if (metadata.magic != MiiMagic && store_data.IsSpecial()) { + return ResultInvalidOperation; + } + + u32 index{}; + const bool is_found = database.GetIndexByCreatorId(index, store_data.GetCreateId()); + if (is_found) { + const StoreData& old_store_data = database.Get(index); + + if (store_data.IsSpecial() != old_store_data.IsSpecial()) { + return ResultInvalidOperation; + } + + database.Replace(index, store_data); + } else { + if (database.IsFull()) { + return ResultDatabaseFull; + } + + database.Add(store_data); + } + + is_moddified = true; + update_counter++; + metadata.update_counter = update_counter; + return ResultSuccess; +} + +Result DatabaseManager::Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id) { + u32 index{}; + const bool is_found = database.GetIndexByCreatorId(index, create_id); + if (!is_found) { + return ResultNotFound; + } + + if (metadata.magic != MiiMagic) { + const auto& store_data = database.Get(index); + if (store_data.IsSpecial()) { + return ResultInvalidOperation; + } + } + + database.Delete(index); + + is_moddified = true; + update_counter++; + metadata.update_counter = update_counter; + return ResultSuccess; +} + +Result DatabaseManager::Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info) { + if (char_info.Verify() != ValidationResult::NoErrors) { + return ResultInvalidCharInfo2; + } + if (char_info.GetType() == 1) { + return ResultInvalidCharInfoType; + } + + u32 index{}; + StoreData store_data{}; + + // Loop until the mii we created is not on the database + do { + store_data.BuildWithCharInfo(char_info); + } while (database.GetIndexByCreatorId(index, store_data.GetCreateId())); + + const Result result = store_data.Restore(); + + if (result.IsSuccess() || result == ResultNotUpdated) { + return AddOrReplace(metadata, store_data); + } + + return result; +} + +Result DatabaseManager::DestroyFile(DatabaseSessionMetadata& metadata) { + database.CorruptCrc(); + + is_moddified = true; + update_counter++; + metadata.update_counter = update_counter; + + const auto result = SaveDatabase(); + database.CleanDatabase(); + + return result; +} + +Result DatabaseManager::DeleteFile() { + const bool result = Common::FS::RemoveFile(system_save_dir / DbFileName); + // TODO: Return proper FS error here + return result ? ResultSuccess : ResultUnknown; +} + +void DatabaseManager::Format(DatabaseSessionMetadata& metadata) { + database.CleanDatabase(); + is_moddified = true; + update_counter++; + metadata.update_counter = update_counter; +} + +Result DatabaseManager::SaveDatabase() { + // TODO: Replace unknown error codes with proper FS error codes when available + + if (!Common::FS::Exists(system_save_dir / DbFileName)) { + if (!Common::FS::NewFile(system_save_dir / DbFileName)) { + LOG_ERROR(Service_Mii, "Failed to create mii database"); + return ResultUnknown; + } + } + + const auto file_size = Common::FS::GetSize(system_save_dir / DbFileName); + if (file_size != 0 && file_size != sizeof(NintendoFigurineDatabase)) { + if (!Common::FS::RemoveFile(system_save_dir / DbFileName)) { + LOG_ERROR(Service_Mii, "Failed to delete mii database"); + return ResultUnknown; + } + if (!Common::FS::NewFile(system_save_dir / DbFileName)) { + LOG_ERROR(Service_Mii, "Failed to create mii database"); + return ResultUnknown; + } + } + + const Common::FS::IOFile db_file{system_save_dir / DbFileName, + Common::FS::FileAccessMode::ReadWrite, + Common::FS::FileType::BinaryFile}; + + if (db_file.Write(database) != 1) { + LOG_ERROR(Service_Mii, "Failed to save mii database"); + return ResultUnknown; + } + + is_moddified = false; + return ResultSuccess; +} + +} // namespace Service::Mii diff --git a/src/core/hle/service/mii/mii_database_manager.h b/src/core/hle/service/mii/mii_database_manager.h new file mode 100644 index 000000000..52c32be82 --- /dev/null +++ b/src/core/hle/service/mii/mii_database_manager.h @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/fs/fs.h" +#include "core/hle/result.h" +#include "core/hle/service/mii/mii_database.h" + +namespace Service::Mii { +class CharInfo; +class StoreData; + +class DatabaseManager { +public: + DatabaseManager(); + Result MountSaveData(); + Result Initialize(DatabaseSessionMetadata& metadata, bool& is_database_broken); + + bool IsFullDatabase() const; + bool IsModified() const; + u64 GetUpdateCounter() const; + + void Get(StoreData& out_store_data, std::size_t index, + const DatabaseSessionMetadata& metadata) const; + u32 GetCount(const DatabaseSessionMetadata& metadata) const; + + Result FindIndex(s32& out_index, const Common::UUID& create_id, bool is_special) const; + Result FindIndex(const DatabaseSessionMetadata& metadata, u32& out_index, + const Common::UUID& create_id) const; + Result FindMoveIndex(u32& out_index, u32 new_index, const Common::UUID& create_id) const; + + Result Move(DatabaseSessionMetadata& metadata, u32 current_index, + const Common::UUID& create_id); + Result AddOrReplace(DatabaseSessionMetadata& metadata, const StoreData& out_store_data); + Result Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id); + Result Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info); + + Result DestroyFile(DatabaseSessionMetadata& metadata); + Result DeleteFile(); + void Format(DatabaseSessionMetadata& metadata); + + Result SaveDatabase(); + +private: + // This is the global value of + // nn::settings::fwdbg::GetSettingsItemValue("is_db_test_mode_enabled"); + bool is_test_db{}; + + bool is_moddified{}; + bool is_save_data_mounted{}; + u64 update_counter{}; + NintendoFigurineDatabase database{}; + + std::filesystem::path system_save_dir{}; +}; + +}; // namespace Service::Mii diff --git a/src/core/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp index c920650f5..dcfd6b2e2 100644 --- a/src/core/hle/service/mii/mii_manager.cpp +++ b/src/core/hle/service/mii/mii_manager.cpp @@ -1,698 +1,486 @@ // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include <cstring> -#include <random> - -#include "common/assert.h" #include "common/logging/log.h" -#include "common/string_util.h" - -#include "core/hle/service/acc/profile_manager.h" +#include "core/hle/service/mii/mii_database_manager.h" #include "core/hle/service/mii/mii_manager.h" -#include "core/hle/service/mii/raw_data.h" +#include "core/hle/service/mii/mii_result.h" +#include "core/hle/service/mii/mii_util.h" +#include "core/hle/service/mii/types/char_info.h" +#include "core/hle/service/mii/types/core_data.h" +#include "core/hle/service/mii/types/raw_data.h" +#include "core/hle/service/mii/types/store_data.h" +#include "core/hle/service/mii/types/ver3_store_data.h" namespace Service::Mii { +constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()}; -namespace { +MiiManager::MiiManager() {} -constexpr Result ERROR_CANNOT_FIND_ENTRY{ErrorModule::Mii, 4}; +Result MiiManager::Initialize(DatabaseSessionMetadata& metadata) { + database_manager.MountSaveData(); + database_manager.Initialize(metadata, is_broken_with_clear_flag); + return ResultSuccess; +} -constexpr std::size_t BaseMiiCount{2}; -constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()}; +void MiiManager::BuildDefault(CharInfo& out_char_info, u32 index) const { + StoreData store_data{}; + store_data.BuildDefault(index); + out_char_info.SetFromStoreData(store_data); +} -constexpr MiiStoreData::Name DefaultMiiName{u'y', u'u', u'z', u'u'}; -constexpr std::array<u8, 8> HairColorLookup{8, 1, 2, 3, 4, 5, 6, 7}; -constexpr std::array<u8, 6> EyeColorLookup{8, 9, 10, 11, 12, 13}; -constexpr std::array<u8, 5> MouthColorLookup{19, 20, 21, 22, 23}; -constexpr std::array<u8, 7> GlassesColorLookup{8, 14, 15, 16, 17, 18, 0}; -constexpr std::array<u8, 62> EyeRotateLookup{ - {0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x04, - 0x04, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04, - 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, - 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04}}; -constexpr std::array<u8, 24> EyebrowRotateLookup{{0x06, 0x06, 0x05, 0x07, 0x06, 0x07, 0x06, 0x07, - 0x04, 0x07, 0x06, 0x08, 0x05, 0x05, 0x06, 0x06, - 0x07, 0x07, 0x06, 0x06, 0x05, 0x06, 0x07, 0x05}}; - -template <typename T, std::size_t SourceArraySize, std::size_t DestArraySize> -std::array<T, DestArraySize> ResizeArray(const std::array<T, SourceArraySize>& in) { - std::array<T, DestArraySize> out{}; - std::memcpy(out.data(), in.data(), sizeof(T) * std::min(SourceArraySize, DestArraySize)); - return out; +void MiiManager::BuildBase(CharInfo& out_char_info, Gender gender) const { + StoreData store_data{}; + store_data.BuildBase(gender); + out_char_info.SetFromStoreData(store_data); } -CharInfo ConvertStoreDataToInfo(const MiiStoreData& data) { - MiiStoreBitFields bf; - std::memcpy(&bf, data.data.data.data(), sizeof(MiiStoreBitFields)); - - return { - .uuid = data.data.uuid, - .name = ResizeArray<char16_t, 10, 11>(data.data.name), - .font_region = static_cast<u8>(bf.font_region.Value()), - .favorite_color = static_cast<u8>(bf.favorite_color.Value()), - .gender = static_cast<u8>(bf.gender.Value()), - .height = static_cast<u8>(bf.height.Value()), - .build = static_cast<u8>(bf.build.Value()), - .type = static_cast<u8>(bf.type.Value()), - .region_move = static_cast<u8>(bf.region_move.Value()), - .faceline_type = static_cast<u8>(bf.faceline_type.Value()), - .faceline_color = static_cast<u8>(bf.faceline_color.Value()), - .faceline_wrinkle = static_cast<u8>(bf.faceline_wrinkle.Value()), - .faceline_make = static_cast<u8>(bf.faceline_makeup.Value()), - .hair_type = static_cast<u8>(bf.hair_type.Value()), - .hair_color = static_cast<u8>(bf.hair_color.Value()), - .hair_flip = static_cast<u8>(bf.hair_flip.Value()), - .eye_type = static_cast<u8>(bf.eye_type.Value()), - .eye_color = static_cast<u8>(bf.eye_color.Value()), - .eye_scale = static_cast<u8>(bf.eye_scale.Value()), - .eye_aspect = static_cast<u8>(bf.eye_aspect.Value()), - .eye_rotate = static_cast<u8>(bf.eye_rotate.Value()), - .eye_x = static_cast<u8>(bf.eye_x.Value()), - .eye_y = static_cast<u8>(bf.eye_y.Value()), - .eyebrow_type = static_cast<u8>(bf.eyebrow_type.Value()), - .eyebrow_color = static_cast<u8>(bf.eyebrow_color.Value()), - .eyebrow_scale = static_cast<u8>(bf.eyebrow_scale.Value()), - .eyebrow_aspect = static_cast<u8>(bf.eyebrow_aspect.Value()), - .eyebrow_rotate = static_cast<u8>(bf.eyebrow_rotate.Value()), - .eyebrow_x = static_cast<u8>(bf.eyebrow_x.Value()), - .eyebrow_y = static_cast<u8>(bf.eyebrow_y.Value() + 3), - .nose_type = static_cast<u8>(bf.nose_type.Value()), - .nose_scale = static_cast<u8>(bf.nose_scale.Value()), - .nose_y = static_cast<u8>(bf.nose_y.Value()), - .mouth_type = static_cast<u8>(bf.mouth_type.Value()), - .mouth_color = static_cast<u8>(bf.mouth_color.Value()), - .mouth_scale = static_cast<u8>(bf.mouth_scale.Value()), - .mouth_aspect = static_cast<u8>(bf.mouth_aspect.Value()), - .mouth_y = static_cast<u8>(bf.mouth_y.Value()), - .beard_color = static_cast<u8>(bf.beard_color.Value()), - .beard_type = static_cast<u8>(bf.beard_type.Value()), - .mustache_type = static_cast<u8>(bf.mustache_type.Value()), - .mustache_scale = static_cast<u8>(bf.mustache_scale.Value()), - .mustache_y = static_cast<u8>(bf.mustache_y.Value()), - .glasses_type = static_cast<u8>(bf.glasses_type.Value()), - .glasses_color = static_cast<u8>(bf.glasses_color.Value()), - .glasses_scale = static_cast<u8>(bf.glasses_scale.Value()), - .glasses_y = static_cast<u8>(bf.glasses_y.Value()), - .mole_type = static_cast<u8>(bf.mole_type.Value()), - .mole_scale = static_cast<u8>(bf.mole_scale.Value()), - .mole_x = static_cast<u8>(bf.mole_x.Value()), - .mole_y = static_cast<u8>(bf.mole_y.Value()), - .padding = 0, - }; +void MiiManager::BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Race race) const { + StoreData store_data{}; + store_data.BuildRandom(age, gender, race); + out_char_info.SetFromStoreData(store_data); } -u16 GenerateCrc16(const void* data, std::size_t size) { - s32 crc{}; - for (std::size_t i = 0; i < size; i++) { - crc ^= static_cast<const u8*>(data)[i] << 8; - for (std::size_t j = 0; j < 8; j++) { - crc <<= 1; - if ((crc & 0x10000) != 0) { - crc = (crc ^ 0x1021) & 0xFFFF; - } - } - } - return Common::swap16(static_cast<u16>(crc)); +bool MiiManager::IsFullDatabase() const { + return database_manager.IsFullDatabase(); } -template <typename T> -T GetRandomValue(T min, T max) { - std::random_device device; - std::mt19937 gen(device()); - std::uniform_int_distribution<u64> distribution(static_cast<u64>(min), static_cast<u64>(max)); - return static_cast<T>(distribution(gen)); +void MiiManager::SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version) const { + metadata.interface_version = version; } -template <typename T> -T GetRandomValue(T max) { - return GetRandomValue<T>({}, max); +bool MiiManager::IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const { + if ((source_flag & SourceFlag::Database) == SourceFlag::None) { + return false; + } + + const u64 metadata_update_counter = metadata.update_counter; + const u64 database_update_counter = database_manager.GetUpdateCounter(); + metadata.update_counter = database_update_counter; + return metadata_update_counter != database_update_counter; } -MiiStoreData BuildRandomStoreData(Age age, Gender gender, Race race, const Common::UUID& user_id) { - MiiStoreBitFields bf{}; - - if (gender == Gender::All) { - gender = GetRandomValue<Gender>(Gender::Maximum); - } - - bf.gender.Assign(gender); - bf.favorite_color.Assign(GetRandomValue<u8>(11)); - bf.region_move.Assign(0); - bf.font_region.Assign(FontRegion::Standard); - bf.type.Assign(0); - bf.height.Assign(64); - bf.build.Assign(64); - - if (age == Age::All) { - const auto temp{GetRandomValue<int>(10)}; - if (temp >= 8) { - age = Age::Old; - } else if (temp >= 4) { - age = Age::Normal; - } else { - age = Age::Young; - } +u32 MiiManager::GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const { + u32 mii_count{}; + if ((source_flag & SourceFlag::Default) != SourceFlag::None) { + mii_count += DefaultMiiCount; + } + if ((source_flag & SourceFlag::Database) != SourceFlag::None) { + mii_count += database_manager.GetCount(metadata); } + return mii_count; +} - if (race == Race::All) { - const auto temp{GetRandomValue<int>(10)}; - if (temp >= 8) { - race = Race::Black; - } else if (temp >= 4) { - race = Race::White; - } else { - race = Race::Asian; - } +Result MiiManager::Move(DatabaseSessionMetadata& metadata, u32 index, + const Common::UUID& create_id) { + const auto result = database_manager.Move(metadata, index, create_id); + + if (result.IsFailure()) { + return result; } - u32 axis_y{}; - if (gender == Gender::Female && age == Age::Young) { - axis_y = GetRandomValue<u32>(3); - } - - const std::size_t index{3 * static_cast<std::size_t>(age) + - 9 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race)}; - - const auto faceline_type_info{RawData::RandomMiiFaceline.at(index)}; - const auto faceline_color_info{RawData::RandomMiiFacelineColor.at( - 3 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race))}; - const auto faceline_wrinkle_info{RawData::RandomMiiFacelineWrinkle.at(index)}; - const auto faceline_makeup_info{RawData::RandomMiiFacelineMakeup.at(index)}; - const auto hair_type_info{RawData::RandomMiiHairType.at(index)}; - const auto hair_color_info{RawData::RandomMiiHairColor.at(3 * static_cast<std::size_t>(race) + - static_cast<std::size_t>(age))}; - const auto eye_type_info{RawData::RandomMiiEyeType.at(index)}; - const auto eye_color_info{RawData::RandomMiiEyeColor.at(static_cast<std::size_t>(race))}; - const auto eyebrow_type_info{RawData::RandomMiiEyebrowType.at(index)}; - const auto nose_type_info{RawData::RandomMiiNoseType.at(index)}; - const auto mouth_type_info{RawData::RandomMiiMouthType.at(index)}; - const auto glasses_type_info{RawData::RandomMiiGlassType.at(static_cast<std::size_t>(age))}; - - bf.faceline_type.Assign( - faceline_type_info.values[GetRandomValue<std::size_t>(faceline_type_info.values_count)]); - bf.faceline_color.Assign( - faceline_color_info.values[GetRandomValue<std::size_t>(faceline_color_info.values_count)]); - bf.faceline_wrinkle.Assign( - faceline_wrinkle_info - .values[GetRandomValue<std::size_t>(faceline_wrinkle_info.values_count)]); - bf.faceline_makeup.Assign( - faceline_makeup_info - .values[GetRandomValue<std::size_t>(faceline_makeup_info.values_count)]); - - bf.hair_type.Assign( - hair_type_info.values[GetRandomValue<std::size_t>(hair_type_info.values_count)]); - bf.hair_color.Assign( - HairColorLookup[hair_color_info - .values[GetRandomValue<std::size_t>(hair_color_info.values_count)]]); - bf.hair_flip.Assign(GetRandomValue<HairFlip>(HairFlip::Maximum)); - - bf.eye_type.Assign( - eye_type_info.values[GetRandomValue<std::size_t>(eye_type_info.values_count)]); - - const auto eye_rotate_1{gender != Gender::Male ? 4 : 2}; - const auto eye_rotate_2{gender != Gender::Male ? 3 : 4}; - const auto eye_rotate_offset{32 - EyeRotateLookup[eye_rotate_1] + eye_rotate_2}; - const auto eye_rotate{32 - EyeRotateLookup[bf.eye_type]}; - - bf.eye_color.Assign( - EyeColorLookup[eye_color_info - .values[GetRandomValue<std::size_t>(eye_color_info.values_count)]]); - bf.eye_scale.Assign(4); - bf.eye_aspect.Assign(3); - bf.eye_rotate.Assign(eye_rotate_offset - eye_rotate); - bf.eye_x.Assign(2); - bf.eye_y.Assign(axis_y + 12); - - bf.eyebrow_type.Assign( - eyebrow_type_info.values[GetRandomValue<std::size_t>(eyebrow_type_info.values_count)]); - - const auto eyebrow_rotate_1{race == Race::Asian ? 6 : 0}; - const auto eyebrow_y{race == Race::Asian ? 9 : 10}; - const auto eyebrow_rotate_offset{32 - EyebrowRotateLookup[eyebrow_rotate_1] + 6}; - const auto eyebrow_rotate{ - 32 - EyebrowRotateLookup[static_cast<std::size_t>(bf.eyebrow_type.Value())]}; - - bf.eyebrow_color.Assign(bf.hair_color); - bf.eyebrow_scale.Assign(4); - bf.eyebrow_aspect.Assign(3); - bf.eyebrow_rotate.Assign(eyebrow_rotate_offset - eyebrow_rotate); - bf.eyebrow_x.Assign(2); - bf.eyebrow_y.Assign(axis_y + eyebrow_y); - - const auto nose_scale{gender == Gender::Female ? 3 : 4}; - - bf.nose_type.Assign( - nose_type_info.values[GetRandomValue<std::size_t>(nose_type_info.values_count)]); - bf.nose_scale.Assign(nose_scale); - bf.nose_y.Assign(axis_y + 9); - - const auto mouth_color{gender == Gender::Female ? GetRandomValue<int>(4) : 0}; - - bf.mouth_type.Assign( - mouth_type_info.values[GetRandomValue<std::size_t>(mouth_type_info.values_count)]); - bf.mouth_color.Assign(MouthColorLookup[mouth_color]); - bf.mouth_scale.Assign(4); - bf.mouth_aspect.Assign(3); - bf.mouth_y.Assign(axis_y + 13); - - bf.beard_color.Assign(bf.hair_color); - bf.mustache_scale.Assign(4); - - if (gender == Gender::Male && age != Age::Young && GetRandomValue<int>(10) < 2) { - const auto mustache_and_beard_flag{ - GetRandomValue<BeardAndMustacheFlag>(BeardAndMustacheFlag::All)}; - - auto beard_type{BeardType::None}; - auto mustache_type{MustacheType::None}; - - if ((mustache_and_beard_flag & BeardAndMustacheFlag::Beard) == - BeardAndMustacheFlag::Beard) { - beard_type = GetRandomValue<BeardType>(BeardType::Beard1, BeardType::Beard5); - } + if (!database_manager.IsModified()) { + return ResultNotUpdated; + } - if ((mustache_and_beard_flag & BeardAndMustacheFlag::Mustache) == - BeardAndMustacheFlag::Mustache) { - mustache_type = - GetRandomValue<MustacheType>(MustacheType::Mustache1, MustacheType::Mustache5); - } + return database_manager.SaveDatabase(); +} - bf.mustache_type.Assign(mustache_type); - bf.beard_type.Assign(beard_type); - bf.mustache_y.Assign(10); - } else { - bf.mustache_type.Assign(MustacheType::None); - bf.beard_type.Assign(BeardType::None); - bf.mustache_y.Assign(axis_y + 10); - } - - const auto glasses_type_start{GetRandomValue<std::size_t>(100)}; - u8 glasses_type{}; - while (glasses_type_start < glasses_type_info.values[glasses_type]) { - if (++glasses_type >= glasses_type_info.values_count) { - ASSERT(false); - break; - } +Result MiiManager::AddOrReplace(DatabaseSessionMetadata& metadata, const StoreData& store_data) { + const auto result = database_manager.AddOrReplace(metadata, store_data); + + if (result.IsFailure()) { + return result; + } + + if (!database_manager.IsModified()) { + return ResultNotUpdated; } - bf.glasses_type.Assign(glasses_type); - bf.glasses_color.Assign(GlassesColorLookup[0]); - bf.glasses_scale.Assign(4); - bf.glasses_y.Assign(axis_y + 10); + return database_manager.SaveDatabase(); +} - bf.mole_type.Assign(0); - bf.mole_scale.Assign(4); - bf.mole_x.Assign(2); - bf.mole_y.Assign(20); +Result MiiManager::Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id) { + const auto result = database_manager.Delete(metadata, create_id); - return {DefaultMiiName, bf, user_id}; + if (result.IsFailure()) { + return result; + } + + if (!database_manager.IsModified()) { + return ResultNotUpdated; + } + + return database_manager.SaveDatabase(); } -MiiStoreData BuildDefaultStoreData(const DefaultMii& info, const Common::UUID& user_id) { - MiiStoreBitFields bf{}; - - bf.font_region.Assign(info.font_region); - bf.favorite_color.Assign(info.favorite_color); - bf.gender.Assign(info.gender); - bf.height.Assign(info.height); - bf.build.Assign(info.weight); - bf.type.Assign(info.type); - bf.region_move.Assign(info.region); - bf.faceline_type.Assign(info.face_type); - bf.faceline_color.Assign(info.face_color); - bf.faceline_wrinkle.Assign(info.face_wrinkle); - bf.faceline_makeup.Assign(info.face_makeup); - bf.hair_type.Assign(info.hair_type); - bf.hair_color.Assign(HairColorLookup[info.hair_color]); - bf.hair_flip.Assign(static_cast<HairFlip>(info.hair_flip)); - bf.eye_type.Assign(info.eye_type); - bf.eye_color.Assign(EyeColorLookup[info.eye_color]); - bf.eye_scale.Assign(info.eye_scale); - bf.eye_aspect.Assign(info.eye_aspect); - bf.eye_rotate.Assign(info.eye_rotate); - bf.eye_x.Assign(info.eye_x); - bf.eye_y.Assign(info.eye_y); - bf.eyebrow_type.Assign(info.eyebrow_type); - bf.eyebrow_color.Assign(HairColorLookup[info.eyebrow_color]); - bf.eyebrow_scale.Assign(info.eyebrow_scale); - bf.eyebrow_aspect.Assign(info.eyebrow_aspect); - bf.eyebrow_rotate.Assign(info.eyebrow_rotate); - bf.eyebrow_x.Assign(info.eyebrow_x); - bf.eyebrow_y.Assign(info.eyebrow_y - 3); - bf.nose_type.Assign(info.nose_type); - bf.nose_scale.Assign(info.nose_scale); - bf.nose_y.Assign(info.nose_y); - bf.mouth_type.Assign(info.mouth_type); - bf.mouth_color.Assign(MouthColorLookup[info.mouth_color]); - bf.mouth_scale.Assign(info.mouth_scale); - bf.mouth_aspect.Assign(info.mouth_aspect); - bf.mouth_y.Assign(info.mouth_y); - bf.beard_color.Assign(HairColorLookup[info.beard_color]); - bf.beard_type.Assign(static_cast<BeardType>(info.beard_type)); - bf.mustache_type.Assign(static_cast<MustacheType>(info.mustache_type)); - bf.mustache_scale.Assign(info.mustache_scale); - bf.mustache_y.Assign(info.mustache_y); - bf.glasses_type.Assign(info.glasses_type); - bf.glasses_color.Assign(GlassesColorLookup[info.glasses_color]); - bf.glasses_scale.Assign(info.glasses_scale); - bf.glasses_y.Assign(info.glasses_y); - bf.mole_type.Assign(info.mole_type); - bf.mole_scale.Assign(info.mole_scale); - bf.mole_x.Assign(info.mole_x); - bf.mole_y.Assign(info.mole_y); - - return {DefaultMiiName, bf, user_id}; +s32 MiiManager::FindIndex(const Common::UUID& create_id, bool is_special) const { + s32 index{}; + const auto result = database_manager.FindIndex(index, create_id, is_special); + if (result.IsError()) { + index = -1; + } + return index; } -} // namespace +Result MiiManager::GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info, + s32& out_index) const { + if (char_info.Verify() != ValidationResult::NoErrors) { + return ResultInvalidCharInfo; + } + + s32 index{}; + const bool is_special = metadata.magic == MiiMagic; + const auto result = database_manager.FindIndex(index, char_info.GetCreateId(), is_special); -MiiStoreData::MiiStoreData() = default; + if (result.IsError()) { + index = -1; + } -MiiStoreData::MiiStoreData(const MiiStoreData::Name& name, const MiiStoreBitFields& bit_fields, - const Common::UUID& user_id) { - data.name = name; - data.uuid = Common::UUID::MakeRandomRFC4122V4(); + if (index == -1) { + return ResultNotFound; + } - std::memcpy(data.data.data(), &bit_fields, sizeof(MiiStoreBitFields)); - data_crc = GenerateCrc16(data.data.data(), sizeof(data)); - device_crc = GenerateCrc16(&user_id, sizeof(Common::UUID)); + out_index = index; + return ResultSuccess; } -MiiManager::MiiManager() : user_id{Service::Account::ProfileManager().GetLastOpenedUser()} {} +Result MiiManager::Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info) { + const auto result = database_manager.Append(metadata, char_info); -bool MiiManager::CheckAndResetUpdateCounter(SourceFlag source_flag, u64& current_update_counter) { - if ((source_flag & SourceFlag::Database) == SourceFlag::None) { - return false; + if (result.IsError()) { + return ResultNotFound; } - const bool result{current_update_counter != update_counter}; + if (!database_manager.IsModified()) { + return ResultNotUpdated; + } - current_update_counter = update_counter; + return database_manager.SaveDatabase(); +} - return result; +bool MiiManager::IsBrokenWithClearFlag(DatabaseSessionMetadata& metadata) { + const bool is_broken = is_broken_with_clear_flag; + if (is_broken_with_clear_flag) { + is_broken_with_clear_flag = false; + database_manager.Format(metadata); + database_manager.SaveDatabase(); + } + return is_broken; } -bool MiiManager::IsFullDatabase() const { - // TODO(bunnei): We don't implement the Mii database, so it cannot be full - return false; +Result MiiManager::DestroyFile(DatabaseSessionMetadata& metadata) { + is_broken_with_clear_flag = true; + return database_manager.DestroyFile(metadata); } -u32 MiiManager::GetCount(SourceFlag source_flag) const { - std::size_t count{}; - if ((source_flag & SourceFlag::Database) != SourceFlag::None) { - // TODO(bunnei): We don't implement the Mii database, but when we do, update this - count += 0; - } - if ((source_flag & SourceFlag::Default) != SourceFlag::None) { - count += (DefaultMiiCount - BaseMiiCount); +Result MiiManager::DeleteFile() { + return database_manager.DeleteFile(); +} + +Result MiiManager::Format(DatabaseSessionMetadata& metadata) { + database_manager.Format(metadata); + + if (!database_manager.IsModified()) { + return ResultNotUpdated; } - return static_cast<u32>(count); + return database_manager.SaveDatabase(); } -ResultVal<CharInfo> MiiManager::UpdateLatest([[maybe_unused]] const CharInfo& info, - SourceFlag source_flag) { - if ((source_flag & SourceFlag::Database) == SourceFlag::None) { - return ERROR_CANNOT_FIND_ENTRY; +Result MiiManager::ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const { + if (!mii_v3.IsValid()) { + return ResultInvalidCharInfo; } - // TODO(bunnei): We don't implement the Mii database, so we can't have an entry - return ERROR_CANNOT_FIND_ENTRY; -} + StoreData store_data{}; + mii_v3.BuildToStoreData(store_data); + const auto name = store_data.GetNickname(); + if (!MiiUtil::IsFontRegionValid(store_data.GetFontRegion(), name.data)) { + store_data.SetInvalidName(); + } -CharInfo MiiManager::BuildRandom(Age age, Gender gender, Race race) { - return ConvertStoreDataToInfo(BuildRandomStoreData(age, gender, race, user_id)); + out_char_info.SetFromStoreData(store_data); + return ResultSuccess; } -CharInfo MiiManager::BuildDefault(std::size_t index) { - return ConvertStoreDataToInfo(BuildDefaultStoreData(RawData::DefaultMii.at(index), user_id)); +Result MiiManager::ConvertCoreDataToCharInfo(CharInfo& out_char_info, + const CoreData& core_data) const { + if (core_data.IsValid() != ValidationResult::NoErrors) { + return ResultInvalidCharInfo; + } + + StoreData store_data{}; + store_data.BuildWithCoreData(core_data); + const auto name = store_data.GetNickname(); + if (!MiiUtil::IsFontRegionValid(store_data.GetFontRegion(), name.data)) { + store_data.SetInvalidName(); + } + + out_char_info.SetFromStoreData(store_data); + return ResultSuccess; } -CharInfo MiiManager::ConvertV3ToCharInfo(const Ver3StoreData& mii_v3) const { - Service::Mii::MiiManager manager; - auto mii = manager.BuildDefault(0); +Result MiiManager::ConvertCharInfoToCoreData(CoreData& out_core_data, + const CharInfo& char_info) const { + if (char_info.Verify() != ValidationResult::NoErrors) { + return ResultInvalidCharInfo; + } - if (!ValidateV3Info(mii_v3)) { - return mii; + out_core_data.BuildFromCharInfo(char_info); + const auto name = out_core_data.GetNickname(); + if (!MiiUtil::IsFontRegionValid(out_core_data.GetFontRegion(), name.data)) { + out_core_data.SetNickname(out_core_data.GetInvalidNickname()); } - // TODO: We are ignoring a bunch of data from the mii_v3 + return ResultSuccess; +} - mii.gender = static_cast<u8>(mii_v3.mii_information.gender); - mii.favorite_color = static_cast<u8>(mii_v3.mii_information.favorite_color); - mii.height = mii_v3.height; - mii.build = mii_v3.build; +Result MiiManager::UpdateLatest(const DatabaseSessionMetadata& metadata, CharInfo& out_char_info, + const CharInfo& char_info, SourceFlag source_flag) const { + if ((source_flag & SourceFlag::Database) == SourceFlag::None) { + return ResultNotFound; + } - // Copy name until string terminator - mii.name = {}; - for (std::size_t index = 0; index < mii.name.size() - 1; index++) { - mii.name[index] = mii_v3.mii_name[index]; - if (mii.name[index] == 0) { - break; + if (metadata.IsInterfaceVersionSupported(1)) { + if (char_info.Verify() != ValidationResult::NoErrors) { + return ResultInvalidCharInfo; } } - mii.font_region = mii_v3.region_information.character_set; + u32 index{}; + Result result = database_manager.FindIndex(metadata, index, char_info.GetCreateId()); - mii.faceline_type = mii_v3.appearance_bits1.face_shape; - mii.faceline_color = mii_v3.appearance_bits1.skin_color; - mii.faceline_wrinkle = mii_v3.appearance_bits2.wrinkles; - mii.faceline_make = mii_v3.appearance_bits2.makeup; + if (result.IsError()) { + return result; + } + + StoreData store_data{}; + database_manager.Get(store_data, index, metadata); + + if (store_data.GetType() != char_info.GetType()) { + return ResultNotFound; + } - mii.hair_type = mii_v3.hair_style; - mii.hair_color = mii_v3.appearance_bits3.hair_color; - mii.hair_flip = mii_v3.appearance_bits3.flip_hair; + out_char_info.SetFromStoreData(store_data); - mii.eye_type = static_cast<u8>(mii_v3.appearance_bits4.eye_type); - mii.eye_color = static_cast<u8>(mii_v3.appearance_bits4.eye_color); - mii.eye_scale = static_cast<u8>(mii_v3.appearance_bits4.eye_scale); - mii.eye_aspect = static_cast<u8>(mii_v3.appearance_bits4.eye_vertical_stretch); - mii.eye_rotate = static_cast<u8>(mii_v3.appearance_bits4.eye_rotation); - mii.eye_x = static_cast<u8>(mii_v3.appearance_bits4.eye_spacing); - mii.eye_y = static_cast<u8>(mii_v3.appearance_bits4.eye_y_position); + if (char_info == out_char_info) { + return ResultNotUpdated; + } - mii.eyebrow_type = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_style); - mii.eyebrow_color = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_color); - mii.eyebrow_scale = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_scale); - mii.eyebrow_aspect = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_yscale); - mii.eyebrow_rotate = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_rotation); - mii.eyebrow_x = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_spacing); - mii.eyebrow_y = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_y_position); + return ResultSuccess; +} - mii.nose_type = static_cast<u8>(mii_v3.appearance_bits6.nose_type); - mii.nose_scale = static_cast<u8>(mii_v3.appearance_bits6.nose_scale); - mii.nose_y = static_cast<u8>(mii_v3.appearance_bits6.nose_y_position); +Result MiiManager::UpdateLatest(const DatabaseSessionMetadata& metadata, StoreData& out_store_data, + const StoreData& store_data, SourceFlag source_flag) const { + if ((source_flag & SourceFlag::Database) == SourceFlag::None) { + return ResultNotFound; + } - mii.mouth_type = static_cast<u8>(mii_v3.appearance_bits7.mouth_type); - mii.mouth_color = static_cast<u8>(mii_v3.appearance_bits7.mouth_color); - mii.mouth_scale = static_cast<u8>(mii_v3.appearance_bits7.mouth_scale); - mii.mouth_aspect = static_cast<u8>(mii_v3.appearance_bits7.mouth_horizontal_stretch); - mii.mouth_y = static_cast<u8>(mii_v3.appearance_bits8.mouth_y_position); + if (metadata.IsInterfaceVersionSupported(1)) { + if (store_data.IsValid() != ValidationResult::NoErrors) { + return ResultInvalidCharInfo; + } + } - mii.mustache_type = static_cast<u8>(mii_v3.appearance_bits8.mustache_type); - mii.mustache_scale = static_cast<u8>(mii_v3.appearance_bits9.mustache_scale); - mii.mustache_y = static_cast<u8>(mii_v3.appearance_bits9.mustache_y_position); + u32 index{}; + Result result = database_manager.FindIndex(metadata, index, store_data.GetCreateId()); - mii.beard_type = static_cast<u8>(mii_v3.appearance_bits9.bear_type); - mii.beard_color = static_cast<u8>(mii_v3.appearance_bits9.facial_hair_color); + if (result.IsError()) { + return result; + } - mii.glasses_type = static_cast<u8>(mii_v3.appearance_bits10.glasses_type); - mii.glasses_color = static_cast<u8>(mii_v3.appearance_bits10.glasses_color); - mii.glasses_scale = static_cast<u8>(mii_v3.appearance_bits10.glasses_scale); - mii.glasses_y = static_cast<u8>(mii_v3.appearance_bits10.glasses_y_position); + database_manager.Get(out_store_data, index, metadata); - mii.mole_type = static_cast<u8>(mii_v3.appearance_bits11.mole_enabled); - mii.mole_scale = static_cast<u8>(mii_v3.appearance_bits11.mole_scale); - mii.mole_x = static_cast<u8>(mii_v3.appearance_bits11.mole_x_position); - mii.mole_y = static_cast<u8>(mii_v3.appearance_bits11.mole_y_position); + if (out_store_data.GetType() != store_data.GetType()) { + return ResultNotFound; + } - // TODO: Validate mii data + if (store_data == out_store_data) { + return ResultNotUpdated; + } - return mii; + return ResultSuccess; } -Ver3StoreData MiiManager::BuildFromStoreData(const CharInfo& mii) const { - Service::Mii::MiiManager manager; - Ver3StoreData mii_v3{}; - - // TODO: We are ignoring a bunch of data from the mii_v3 +Result MiiManager::Get(const DatabaseSessionMetadata& metadata, + std::span<CharInfoElement> out_elements, u32& out_count, + SourceFlag source_flag) const { + if ((source_flag & SourceFlag::Database) == SourceFlag::None) { + return BuildDefault(out_elements, out_count, source_flag); + } - mii_v3.version = 1; - mii_v3.mii_information.gender.Assign(mii.gender); - mii_v3.mii_information.favorite_color.Assign(mii.favorite_color); - mii_v3.height = mii.height; - mii_v3.build = mii.build; + const auto mii_count = database_manager.GetCount(metadata); - // Copy name until string terminator - mii_v3.mii_name = {}; - for (std::size_t index = 0; index < mii.name.size() - 1; index++) { - mii_v3.mii_name[index] = mii.name[index]; - if (mii_v3.mii_name[index] == 0) { - break; + for (std::size_t index = 0; index < mii_count; ++index) { + if (out_elements.size() <= static_cast<std::size_t>(out_count)) { + return ResultInvalidArgumentSize; } - } - mii_v3.region_information.character_set.Assign(mii.font_region); + StoreData store_data{}; + database_manager.Get(store_data, index, metadata); - mii_v3.appearance_bits1.face_shape.Assign(mii.faceline_type); - mii_v3.appearance_bits2.wrinkles.Assign(mii.faceline_wrinkle); - mii_v3.appearance_bits2.makeup.Assign(mii.faceline_make); + out_elements[out_count].source = Source::Database; + out_elements[out_count].char_info.SetFromStoreData(store_data); + out_count++; + } - mii_v3.hair_style = mii.hair_type; - mii_v3.appearance_bits3.flip_hair.Assign(mii.hair_flip); + // Include default Mii at the end of the list + return BuildDefault(out_elements, out_count, source_flag); +} - mii_v3.appearance_bits4.eye_type.Assign(mii.eye_type); - mii_v3.appearance_bits4.eye_scale.Assign(mii.eye_scale); - mii_v3.appearance_bits4.eye_vertical_stretch.Assign(mii.eye_aspect); - mii_v3.appearance_bits4.eye_rotation.Assign(mii.eye_rotate); - mii_v3.appearance_bits4.eye_spacing.Assign(mii.eye_x); - mii_v3.appearance_bits4.eye_y_position.Assign(mii.eye_y); +Result MiiManager::Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info, + u32& out_count, SourceFlag source_flag) const { + if ((source_flag & SourceFlag::Database) == SourceFlag::None) { + return BuildDefault(out_char_info, out_count, source_flag); + } - mii_v3.appearance_bits5.eyebrow_style.Assign(mii.eyebrow_type); - mii_v3.appearance_bits5.eyebrow_scale.Assign(mii.eyebrow_scale); - mii_v3.appearance_bits5.eyebrow_yscale.Assign(mii.eyebrow_aspect); - mii_v3.appearance_bits5.eyebrow_rotation.Assign(mii.eyebrow_rotate); - mii_v3.appearance_bits5.eyebrow_spacing.Assign(mii.eyebrow_x); - mii_v3.appearance_bits5.eyebrow_y_position.Assign(mii.eyebrow_y); + const auto mii_count = database_manager.GetCount(metadata); - mii_v3.appearance_bits6.nose_type.Assign(mii.nose_type); - mii_v3.appearance_bits6.nose_scale.Assign(mii.nose_scale); - mii_v3.appearance_bits6.nose_y_position.Assign(mii.nose_y); + for (std::size_t index = 0; index < mii_count; ++index) { + if (out_char_info.size() <= static_cast<std::size_t>(out_count)) { + return ResultInvalidArgumentSize; + } - mii_v3.appearance_bits7.mouth_type.Assign(mii.mouth_type); - mii_v3.appearance_bits7.mouth_scale.Assign(mii.mouth_scale); - mii_v3.appearance_bits7.mouth_horizontal_stretch.Assign(mii.mouth_aspect); - mii_v3.appearance_bits8.mouth_y_position.Assign(mii.mouth_y); + StoreData store_data{}; + database_manager.Get(store_data, index, metadata); - mii_v3.appearance_bits8.mustache_type.Assign(mii.mustache_type); - mii_v3.appearance_bits9.mustache_scale.Assign(mii.mustache_scale); - mii_v3.appearance_bits9.mustache_y_position.Assign(mii.mustache_y); + out_char_info[out_count].SetFromStoreData(store_data); + out_count++; + } - mii_v3.appearance_bits9.bear_type.Assign(mii.beard_type); + // Include default Mii at the end of the list + return BuildDefault(out_char_info, out_count, source_flag); +} - mii_v3.appearance_bits10.glasses_scale.Assign(mii.glasses_scale); - mii_v3.appearance_bits10.glasses_y_position.Assign(mii.glasses_y); +Result MiiManager::Get(const DatabaseSessionMetadata& metadata, + std::span<StoreDataElement> out_elements, u32& out_count, + SourceFlag source_flag) const { + if ((source_flag & SourceFlag::Database) == SourceFlag::None) { + return BuildDefault(out_elements, out_count, source_flag); + } - mii_v3.appearance_bits11.mole_enabled.Assign(mii.mole_type); - mii_v3.appearance_bits11.mole_scale.Assign(mii.mole_scale); - mii_v3.appearance_bits11.mole_x_position.Assign(mii.mole_x); - mii_v3.appearance_bits11.mole_y_position.Assign(mii.mole_y); + const auto mii_count = database_manager.GetCount(metadata); - // These types are converted to V3 from a table - mii_v3.appearance_bits1.skin_color.Assign(Ver3FacelineColorTable[mii.faceline_color]); - mii_v3.appearance_bits3.hair_color.Assign(Ver3HairColorTable[mii.hair_color]); - mii_v3.appearance_bits4.eye_color.Assign(Ver3EyeColorTable[mii.eye_color]); - mii_v3.appearance_bits5.eyebrow_color.Assign(Ver3HairColorTable[mii.eyebrow_color]); - mii_v3.appearance_bits7.mouth_color.Assign(Ver3MouthlineColorTable[mii.mouth_color]); - mii_v3.appearance_bits9.facial_hair_color.Assign(Ver3HairColorTable[mii.beard_color]); - mii_v3.appearance_bits10.glasses_color.Assign(Ver3GlassColorTable[mii.glasses_color]); - mii_v3.appearance_bits10.glasses_type.Assign(Ver3GlassTypeTable[mii.glasses_type]); + for (std::size_t index = 0; index < mii_count; ++index) { + if (out_elements.size() <= static_cast<std::size_t>(out_count)) { + return ResultInvalidArgumentSize; + } - mii_v3.crc = GenerateCrc16(&mii_v3, sizeof(Ver3StoreData) - sizeof(u16)); + StoreData store_data{}; + database_manager.Get(store_data, index, metadata); - // TODO: Validate mii_v3 data + out_elements[out_count].store_data = store_data; + out_elements[out_count].source = Source::Database; + out_count++; + } - return mii_v3; + // Include default Mii at the end of the list + return BuildDefault(out_elements, out_count, source_flag); } -NfpStoreDataExtension MiiManager::SetFromStoreData(const CharInfo& mii) const { - return { - .faceline_color = static_cast<u8>(mii.faceline_color & 0xf), - .hair_color = static_cast<u8>(mii.hair_color & 0x7f), - .eye_color = static_cast<u8>(mii.eyebrow_color & 0x7f), - .eyebrow_color = static_cast<u8>(mii.eyebrow_color & 0x7f), - .mouth_color = static_cast<u8>(mii.mouth_color & 0x7f), - .beard_color = static_cast<u8>(mii.beard_color & 0x7f), - .glass_color = static_cast<u8>(mii.glasses_color & 0x7f), - .glass_type = static_cast<u8>(mii.glasses_type & 0x1f), - }; +Result MiiManager::Get(const DatabaseSessionMetadata& metadata, std::span<StoreData> out_store_data, + u32& out_count, SourceFlag source_flag) const { + if ((source_flag & SourceFlag::Database) == SourceFlag::None) { + return BuildDefault(out_store_data, out_count, source_flag); + } + + const auto mii_count = database_manager.GetCount(metadata); + + for (std::size_t index = 0; index < mii_count; ++index) { + if (out_store_data.size() <= static_cast<std::size_t>(out_count)) { + return ResultInvalidArgumentSize; + } + + StoreData store_data{}; + database_manager.Get(store_data, index, metadata); + + out_store_data[out_count] = store_data; + out_count++; + } + + // Include default Mii at the end of the list + return BuildDefault(out_store_data, out_count, source_flag); } +Result MiiManager::BuildDefault(std::span<CharInfoElement> out_elements, u32& out_count, + SourceFlag source_flag) const { + if ((source_flag & SourceFlag::Default) == SourceFlag::None) { + return ResultSuccess; + } -bool MiiManager::ValidateV3Info(const Ver3StoreData& mii_v3) const { - bool is_valid = mii_v3.version == 0 || mii_v3.version == 3; - - is_valid = is_valid && (mii_v3.mii_name[0] != 0); - - is_valid = is_valid && (mii_v3.mii_information.birth_month < 13); - is_valid = is_valid && (mii_v3.mii_information.birth_day < 32); - is_valid = is_valid && (mii_v3.mii_information.favorite_color < 12); - is_valid = is_valid && (mii_v3.height < 128); - is_valid = is_valid && (mii_v3.build < 128); - - is_valid = is_valid && (mii_v3.appearance_bits1.face_shape < 12); - is_valid = is_valid && (mii_v3.appearance_bits1.skin_color < 7); - is_valid = is_valid && (mii_v3.appearance_bits2.wrinkles < 12); - is_valid = is_valid && (mii_v3.appearance_bits2.makeup < 12); - - is_valid = is_valid && (mii_v3.hair_style < 132); - is_valid = is_valid && (mii_v3.appearance_bits3.hair_color < 8); - - is_valid = is_valid && (mii_v3.appearance_bits4.eye_type < 60); - is_valid = is_valid && (mii_v3.appearance_bits4.eye_color < 6); - is_valid = is_valid && (mii_v3.appearance_bits4.eye_scale < 8); - is_valid = is_valid && (mii_v3.appearance_bits4.eye_vertical_stretch < 7); - is_valid = is_valid && (mii_v3.appearance_bits4.eye_rotation < 8); - is_valid = is_valid && (mii_v3.appearance_bits4.eye_spacing < 13); - is_valid = is_valid && (mii_v3.appearance_bits4.eye_y_position < 19); - - is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_style < 25); - is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_color < 8); - is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_scale < 9); - is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_yscale < 7); - is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_rotation < 12); - is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_spacing < 12); - is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_y_position < 19); - - is_valid = is_valid && (mii_v3.appearance_bits6.nose_type < 18); - is_valid = is_valid && (mii_v3.appearance_bits6.nose_scale < 9); - is_valid = is_valid && (mii_v3.appearance_bits6.nose_y_position < 19); - - is_valid = is_valid && (mii_v3.appearance_bits7.mouth_type < 36); - is_valid = is_valid && (mii_v3.appearance_bits7.mouth_color < 5); - is_valid = is_valid && (mii_v3.appearance_bits7.mouth_scale < 9); - is_valid = is_valid && (mii_v3.appearance_bits7.mouth_horizontal_stretch < 7); - is_valid = is_valid && (mii_v3.appearance_bits8.mouth_y_position < 19); - - is_valid = is_valid && (mii_v3.appearance_bits8.mustache_type < 6); - is_valid = is_valid && (mii_v3.appearance_bits9.mustache_scale < 7); - is_valid = is_valid && (mii_v3.appearance_bits9.mustache_y_position < 17); - - is_valid = is_valid && (mii_v3.appearance_bits9.bear_type < 6); - is_valid = is_valid && (mii_v3.appearance_bits9.facial_hair_color < 8); - - is_valid = is_valid && (mii_v3.appearance_bits10.glasses_type < 9); - is_valid = is_valid && (mii_v3.appearance_bits10.glasses_color < 6); - is_valid = is_valid && (mii_v3.appearance_bits10.glasses_scale < 8); - is_valid = is_valid && (mii_v3.appearance_bits10.glasses_y_position < 21); - - is_valid = is_valid && (mii_v3.appearance_bits11.mole_enabled < 2); - is_valid = is_valid && (mii_v3.appearance_bits11.mole_scale < 9); - is_valid = is_valid && (mii_v3.appearance_bits11.mole_x_position < 17); - is_valid = is_valid && (mii_v3.appearance_bits11.mole_y_position < 31); - - return is_valid; + StoreData store_data{}; + + for (std::size_t index = 0; index < DefaultMiiCount; ++index) { + if (out_elements.size() <= static_cast<std::size_t>(out_count)) { + return ResultInvalidArgumentSize; + } + + store_data.BuildDefault(static_cast<u32>(index)); + + out_elements[out_count].source = Source::Default; + out_elements[out_count].char_info.SetFromStoreData(store_data); + out_count++; + } + + return ResultSuccess; } -ResultVal<std::vector<MiiInfoElement>> MiiManager::GetDefault(SourceFlag source_flag) { - std::vector<MiiInfoElement> result; +Result MiiManager::BuildDefault(std::span<CharInfo> out_char_info, u32& out_count, + SourceFlag source_flag) const { + if ((source_flag & SourceFlag::Default) == SourceFlag::None) { + return ResultSuccess; + } + + StoreData store_data{}; + + for (std::size_t index = 0; index < DefaultMiiCount; ++index) { + if (out_char_info.size() <= static_cast<std::size_t>(out_count)) { + return ResultInvalidArgumentSize; + } + + store_data.BuildDefault(static_cast<u32>(index)); + + out_char_info[out_count].SetFromStoreData(store_data); + out_count++; + } + + return ResultSuccess; +} +Result MiiManager::BuildDefault(std::span<StoreDataElement> out_elements, u32& out_count, + SourceFlag source_flag) const { if ((source_flag & SourceFlag::Default) == SourceFlag::None) { - return result; + return ResultSuccess; } - for (std::size_t index = BaseMiiCount; index < DefaultMiiCount; index++) { - result.emplace_back(BuildDefault(index), Source::Default); + for (std::size_t index = 0; index < DefaultMiiCount; ++index) { + if (out_elements.size() <= static_cast<std::size_t>(out_count)) { + return ResultInvalidArgumentSize; + } + + out_elements[out_count].store_data.BuildDefault(static_cast<u32>(index)); + out_elements[out_count].source = Source::Default; + out_count++; } - return result; + return ResultSuccess; } -Result MiiManager::GetIndex([[maybe_unused]] const CharInfo& info, u32& index) { - constexpr u32 INVALID_INDEX{0xFFFFFFFF}; +Result MiiManager::BuildDefault(std::span<StoreData> out_char_info, u32& out_count, + SourceFlag source_flag) const { + if ((source_flag & SourceFlag::Default) == SourceFlag::None) { + return ResultSuccess; + } + + for (std::size_t index = 0; index < DefaultMiiCount; ++index) { + if (out_char_info.size() <= static_cast<std::size_t>(out_count)) { + return ResultInvalidArgumentSize; + } - index = INVALID_INDEX; + out_char_info[out_count].BuildDefault(static_cast<u32>(index)); + out_count++; + } - // TODO(bunnei): We don't implement the Mii database, so we can't have an index - return ERROR_CANNOT_FIND_ENTRY; + return ResultSuccess; } } // namespace Service::Mii diff --git a/src/core/hle/service/mii/mii_manager.h b/src/core/hle/service/mii/mii_manager.h index 5525fcd1c..48d8e8bb7 100644 --- a/src/core/hle/service/mii/mii_manager.h +++ b/src/core/hle/service/mii/mii_manager.h @@ -3,39 +3,85 @@ #pragma once -#include <vector> +#include <span> #include "core/hle/result.h" -#include "core/hle/service/mii/types.h" +#include "core/hle/service/mii/mii_database_manager.h" +#include "core/hle/service/mii/mii_types.h" namespace Service::Mii { +class CharInfo; +class CoreData; +class StoreData; +class Ver3StoreData; -// The Mii manager is responsible for loading and storing the Miis to the database in NAND along -// with providing an easy interface for HLE emulation of the mii service. +struct CharInfoElement; +struct StoreDataElement; + +// The Mii manager is responsible for handling mii operations along with providing an easy interface +// for HLE emulation of the mii service. class MiiManager { public: MiiManager(); + Result Initialize(DatabaseSessionMetadata& metadata); + + // Auto generated mii + void BuildDefault(CharInfo& out_char_info, u32 index) const; + void BuildBase(CharInfo& out_char_info, Gender gender) const; + void BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Race race) const; - bool CheckAndResetUpdateCounter(SourceFlag source_flag, u64& current_update_counter); + // Database operations bool IsFullDatabase() const; - u32 GetCount(SourceFlag source_flag) const; - ResultVal<CharInfo> UpdateLatest(const CharInfo& info, SourceFlag source_flag); - CharInfo BuildRandom(Age age, Gender gender, Race race); - CharInfo BuildDefault(std::size_t index); - CharInfo ConvertV3ToCharInfo(const Ver3StoreData& mii_v3) const; - bool ValidateV3Info(const Ver3StoreData& mii_v3) const; - ResultVal<std::vector<MiiInfoElement>> GetDefault(SourceFlag source_flag); - Result GetIndex(const CharInfo& info, u32& index); + void SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version) const; + bool IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const; + u32 GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const; + Result Move(DatabaseSessionMetadata& metadata, u32 index, const Common::UUID& create_id); + Result AddOrReplace(DatabaseSessionMetadata& metadata, const StoreData& store_data); + Result Delete(DatabaseSessionMetadata& metadata, const Common::UUID& create_id); + s32 FindIndex(const Common::UUID& create_id, bool is_special) const; + Result GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info, + s32& out_index) const; + Result Append(DatabaseSessionMetadata& metadata, const CharInfo& char_info); + + // Test database operations + bool IsBrokenWithClearFlag(DatabaseSessionMetadata& metadata); + Result DestroyFile(DatabaseSessionMetadata& metadata); + Result DeleteFile(); + Result Format(DatabaseSessionMetadata& metadata); - // This is nn::mii::detail::Ver::StoreDataRaw::BuildFromStoreData - Ver3StoreData BuildFromStoreData(const CharInfo& mii) const; + // Mii conversions + Result ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const; + Result ConvertCoreDataToCharInfo(CharInfo& out_char_info, const CoreData& core_data) const; + Result ConvertCharInfoToCoreData(CoreData& out_core_data, const CharInfo& char_info) const; + Result UpdateLatest(const DatabaseSessionMetadata& metadata, CharInfo& out_char_info, + const CharInfo& char_info, SourceFlag source_flag) const; + Result UpdateLatest(const DatabaseSessionMetadata& metadata, StoreData& out_store_data, + const StoreData& store_data, SourceFlag source_flag) const; - // This is nn::mii::detail::NfpStoreDataExtentionRaw::SetFromStoreData - NfpStoreDataExtension SetFromStoreData(const CharInfo& mii) const; + // Overloaded getters + Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfoElement> out_elements, + u32& out_count, SourceFlag source_flag) const; + Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info, + u32& out_count, SourceFlag source_flag) const; + Result Get(const DatabaseSessionMetadata& metadata, std::span<StoreDataElement> out_elements, + u32& out_count, SourceFlag source_flag) const; + Result Get(const DatabaseSessionMetadata& metadata, std::span<StoreData> out_store_data, + u32& out_count, SourceFlag source_flag) const; private: - const Common::UUID user_id{}; - u64 update_counter{}; + Result BuildDefault(std::span<CharInfoElement> out_elements, u32& out_count, + SourceFlag source_flag) const; + Result BuildDefault(std::span<CharInfo> out_char_info, u32& out_count, + SourceFlag source_flag) const; + Result BuildDefault(std::span<StoreDataElement> out_char_info, u32& out_count, + SourceFlag source_flag) const; + Result BuildDefault(std::span<StoreData> out_char_info, u32& out_count, + SourceFlag source_flag) const; + + DatabaseManager database_manager{}; + + // This should be a global value + bool is_broken_with_clear_flag{}; }; }; // namespace Service::Mii diff --git a/src/core/hle/service/mii/mii_result.h b/src/core/hle/service/mii/mii_result.h new file mode 100644 index 000000000..e2c36e556 --- /dev/null +++ b/src/core/hle/service/mii/mii_result.h @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/hle/result.h" + +namespace Service::Mii { + +constexpr Result ResultInvalidArgument{ErrorModule::Mii, 1}; +constexpr Result ResultInvalidArgumentSize{ErrorModule::Mii, 2}; +constexpr Result ResultNotUpdated{ErrorModule::Mii, 3}; +constexpr Result ResultNotFound{ErrorModule::Mii, 4}; +constexpr Result ResultDatabaseFull{ErrorModule::Mii, 5}; +constexpr Result ResultInvalidCharInfo{ErrorModule::Mii, 100}; +constexpr Result ResultInvalidDatabaseChecksum{ErrorModule::Mii, 101}; +constexpr Result ResultInvalidDatabaseSignature{ErrorModule::Mii, 103}; +constexpr Result ResultInvalidDatabaseVersion{ErrorModule::Mii, 104}; +constexpr Result ResultInvalidDatabaseLength{ErrorModule::Mii, 105}; +constexpr Result ResultInvalidCharInfo2{ErrorModule::Mii, 107}; +constexpr Result ResultInvalidStoreData{ErrorModule::Mii, 109}; +constexpr Result ResultInvalidOperation{ErrorModule::Mii, 202}; +constexpr Result ResultPermissionDenied{ErrorModule::Mii, 203}; +constexpr Result ResultTestModeOnly{ErrorModule::Mii, 204}; +constexpr Result ResultInvalidCharInfoType{ErrorModule::Mii, 205}; + +}; // namespace Service::Mii diff --git a/src/core/hle/service/mii/mii_types.h b/src/core/hle/service/mii/mii_types.h new file mode 100644 index 000000000..f43efd83c --- /dev/null +++ b/src/core/hle/service/mii/mii_types.h @@ -0,0 +1,692 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> +#include <type_traits> + +#include "common/bit_field.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/uuid.h" + +namespace Service::Mii { + +constexpr std::size_t MaxNameSize = 10; +constexpr u8 MaxHeight = 127; +constexpr u8 MaxBuild = 127; +constexpr u8 MaxType = 1; +constexpr u8 MaxRegionMove = 3; +constexpr u8 MaxEyeScale = 7; +constexpr u8 MaxEyeAspect = 6; +constexpr u8 MaxEyeRotate = 7; +constexpr u8 MaxEyeX = 12; +constexpr u8 MaxEyeY = 18; +constexpr u8 MaxEyebrowScale = 8; +constexpr u8 MaxEyebrowAspect = 6; +constexpr u8 MaxEyebrowRotate = 11; +constexpr u8 MaxEyebrowX = 12; +constexpr u8 MaxEyebrowY = 15; +constexpr u8 MaxNoseScale = 8; +constexpr u8 MaxNoseY = 18; +constexpr u8 MaxMouthScale = 8; +constexpr u8 MaxMoutAspect = 6; +constexpr u8 MaxMouthY = 18; +constexpr u8 MaxMustacheScale = 8; +constexpr u8 MaxMustacheY = 16; +constexpr u8 MaxGlassScale = 7; +constexpr u8 MaxGlassY = 20; +constexpr u8 MaxMoleScale = 8; +constexpr u8 MaxMoleX = 16; +constexpr u8 MaxMoleY = 30; +constexpr u8 MaxVer3CommonColor = 7; +constexpr u8 MaxVer3GlassType = 8; + +enum class Age : u8 { + Young, + Normal, + Old, + All, // Default + + Max = All, +}; + +enum class Gender : u8 { + Male, + Female, + All, // Default + + Max = Female, +}; + +enum class Race : u8 { + Black, + White, + Asian, + All, // Default + + Max = All, +}; + +enum class HairType : u8 { + NormalLong, // Default + NormalShort, + NormalMedium, + NormalExtraLong, + NormalLongBottom, + NormalTwoPeaks, + PartingLong, + FrontLock, + PartingShort, + PartingExtraLongCurved, + PartingExtraLong, + PartingMiddleLong, + PartingSquared, + PartingLongBottom, + PeaksTop, + PeaksSquared, + PartingPeaks, + PeaksLongBottom, + Peaks, + PeaksRounded, + PeaksSide, + PeaksMedium, + PeaksLong, + PeaksRoundedLong, + PartingFrontPeaks, + PartingLongFront, + PartingLongRounded, + PartingFrontPeaksLong, + PartingExtraLongRounded, + LongRounded, + NormalUnknown1, + NormalUnknown2, + NormalUnknown3, + NormalUnknown4, + NormalUnknown5, + NormalUnknown6, + DreadLocks, + PlatedMats, + Caps, + Afro, + PlatedMatsLong, + Beanie, + Short, + ShortTopLongSide, + ShortUnknown1, + ShortUnknown2, + MilitaryParting, + Military, + ShortUnknown3, + ShortUnknown4, + ShortUnknown5, + ShortUnknown6, + NoneTop, + None, + LongUnknown1, + LongUnknown2, + LongUnknown3, + LongUnknown4, + LongUnknown5, + LongUnknown6, + LongUnknown7, + LongUnknown8, + LongUnknown9, + LongUnknown10, + LongUnknown11, + LongUnknown12, + LongUnknown13, + LongUnknown14, + LongUnknown15, + LongUnknown16, + LongUnknown17, + LongUnknown18, + LongUnknown19, + LongUnknown20, + LongUnknown21, + LongUnknown22, + LongUnknown23, + LongUnknown24, + LongUnknown25, + LongUnknown26, + LongUnknown27, + LongUnknown28, + LongUnknown29, + LongUnknown30, + LongUnknown31, + LongUnknown32, + LongUnknown33, + LongUnknown34, + LongUnknown35, + LongUnknown36, + LongUnknown37, + LongUnknown38, + LongUnknown39, + LongUnknown40, + LongUnknown41, + LongUnknown42, + LongUnknown43, + LongUnknown44, + LongUnknown45, + LongUnknown46, + LongUnknown47, + LongUnknown48, + LongUnknown49, + LongUnknown50, + LongUnknown51, + LongUnknown52, + LongUnknown53, + LongUnknown54, + LongUnknown55, + LongUnknown56, + LongUnknown57, + LongUnknown58, + LongUnknown59, + LongUnknown60, + LongUnknown61, + LongUnknown62, + LongUnknown63, + LongUnknown64, + LongUnknown65, + LongUnknown66, + TwoMediumFrontStrandsOneLongBackPonyTail, + TwoFrontStrandsLongBackPonyTail, + PartingFrontTwoLongBackPonyTails, + TwoFrontStrandsOneLongBackPonyTail, + LongBackPonyTail, + LongFrontTwoLongBackPonyTails, + StrandsTwoShortSidedPonyTails, + TwoMediumSidedPonyTails, + ShortFrontTwoBackPonyTails, + TwoShortSidedPonyTails, + TwoLongSidedPonyTails, + LongFrontTwoBackPonyTails, + + Max = LongFrontTwoBackPonyTails, +}; + +enum class MoleType : u8 { + None, // Default + OneDot, + + Max = OneDot, +}; + +enum class HairFlip : u8 { + Left, // Default + Right, + + Max = Right, +}; + +enum class CommonColor : u8 { + // For simplicity common colors aren't listed + Max = 99, + Count = 100, +}; + +enum class FavoriteColor : u8 { + Red, // Default + Orange, + Yellow, + LimeGreen, + Green, + Blue, + LightBlue, + Pink, + Purple, + Brown, + White, + Black, + + Max = Black, +}; + +enum class EyeType : u8 { + Normal, // Default + NormalLash, + WhiteLash, + WhiteNoBottom, + OvalAngledWhite, + AngryWhite, + DotLashType1, + Line, + DotLine, + OvalWhite, + RoundedWhite, + NormalShadow, + CircleWhite, + Circle, + CircleWhiteStroke, + NormalOvalNoBottom, + NormalOvalLarge, + NormalRoundedNoBottom, + SmallLash, + Small, + TwoSmall, + NormalLongLash, + WhiteTwoLashes, + WhiteThreeLashes, + DotAngry, + DotAngled, + Oval, + SmallWhite, + WhiteAngledNoBottom, + WhiteAngledNoLeft, + SmallWhiteTwoLashes, + LeafWhiteLash, + WhiteLargeNoBottom, + Dot, + DotLashType2, + DotThreeLashes, + WhiteOvalTop, + WhiteOvalBottom, + WhiteOvalBottomFlat, + WhiteOvalTwoLashes, + WhiteOvalThreeLashes, + WhiteOvalNoBottomTwoLashes, + DotWhite, + WhiteOvalTopFlat, + WhiteThinLeaf, + StarThreeLashes, + LineTwoLashes, + CrowsFeet, + WhiteNoBottomFlat, + WhiteNoBottomRounded, + WhiteSmallBottomLine, + WhiteNoBottomLash, + WhiteNoPartialBottomLash, + WhiteOvalBottomLine, + WhiteNoBottomLashTopLine, + WhiteNoPartialBottomTwoLashes, + NormalTopLine, + WhiteOvalLash, + RoundTired, + WhiteLarge, + + Max = WhiteLarge, +}; + +enum class MouthType : u8 { + Neutral, // Default + NeutralLips, + Smile, + SmileStroke, + SmileTeeth, + LipsSmall, + LipsLarge, + Wave, + WaveAngrySmall, + NeutralStrokeLarge, + TeethSurprised, + LipsExtraLarge, + LipsUp, + NeutralDown, + Surprised, + TeethMiddle, + NeutralStroke, + LipsExtraSmall, + Malicious, + LipsDual, + NeutralComma, + NeutralUp, + TeethLarge, + WaveAngry, + LipsSexy, + SmileInverted, + LipsSexyOutline, + SmileRounded, + LipsTeeth, + NeutralOpen, + TeethRounded, + WaveAngrySmallInverted, + NeutralCommaInverted, + TeethFull, + SmileDownLine, + Kiss, + + Max = Kiss, +}; + +enum class FontRegion : u8 { + Standard, // Default + China, + Korea, + Taiwan, + + Max = Taiwan, +}; + +enum class FacelineType : u8 { + Sharp, // Default + Rounded, + SharpRounded, + SharpRoundedSmall, + Large, + LargeRounded, + SharpSmall, + Flat, + Bump, + Angular, + FlatRounded, + AngularSmall, + + Max = AngularSmall, +}; + +enum class FacelineColor : u8 { + Beige, // Default + WarmBeige, + Natural, + Honey, + Chestnut, + Porcelain, + Ivory, + WarmIvory, + Almond, + Espresso, + + Max = Espresso, + Count = Max + 1, +}; + +enum class FacelineWrinkle : u8 { + None, // Default + TearTroughs, + FacialPain, + Cheeks, + Folds, + UnderTheEyes, + SplitChin, + Chin, + BrowDroop, + MouthFrown, + CrowsFeet, + FoldsCrowsFrown, + + Max = FoldsCrowsFrown, +}; + +enum class FacelineMake : u8 { + None, // Default + CheekPorcelain, + CheekNatural, + EyeShadowBlue, + CheekBlushPorcelain, + CheekBlushNatural, + CheekPorcelainEyeShadowBlue, + CheekPorcelainEyeShadowNatural, + CheekBlushPorcelainEyeShadowEspresso, + Freckles, + LionsManeBeard, + StubbleBeard, + + Max = StubbleBeard, +}; + +enum class EyebrowType : u8 { + FlatAngledLarge, // Default + LowArchRoundedThin, + SoftAngledLarge, + MediumArchRoundedThin, + RoundedMedium, + LowArchMedium, + RoundedThin, + UpThin, + MediumArchRoundedMedium, + RoundedLarge, + UpLarge, + FlatAngledLargeInverted, + MediumArchFlat, + AngledThin, + HorizontalLarge, + HighArchFlat, + Flat, + MediumArchLarge, + LowArchThin, + RoundedThinInverted, + HighArchLarge, + Hairy, + Dotted, + None, + + Max = None, +}; + +enum class NoseType : u8 { + Normal, // Default + Rounded, + Dot, + Arrow, + Roman, + Triangle, + Button, + RoundedInverted, + Potato, + Grecian, + Snub, + Aquiline, + ArrowLeft, + RoundedLarge, + Hooked, + Fat, + Droopy, + ArrowLarge, + + Max = ArrowLarge, +}; + +enum class BeardType : u8 { + None, + Goatee, + GoateeLong, + LionsManeLong, + LionsMane, + Full, + + Min = Goatee, + Max = Full, +}; + +enum class MustacheType : u8 { + None, + Walrus, + Pencil, + Horseshoe, + Normal, + Toothbrush, + + Min = Walrus, + Max = Toothbrush, +}; + +enum class GlassType : u8 { + None, + Oval, + Wayfarer, + Rectangle, + TopRimless, + Rounded, + Oversized, + CatEye, + Square, + BottomRimless, + SemiOpaqueRounded, + SemiOpaqueCatEye, + SemiOpaqueOval, + SemiOpaqueRectangle, + SemiOpaqueAviator, + OpaqueRounded, + OpaqueCatEye, + OpaqueOval, + OpaqueRectangle, + OpaqueAviator, + + Max = OpaqueAviator, + Count = Max + 1, +}; + +enum class BeardAndMustacheFlag : u32 { + Beard = 1, + Mustache, + All = Beard | Mustache, +}; +DECLARE_ENUM_FLAG_OPERATORS(BeardAndMustacheFlag); + +enum class Source : u32 { + Database = 0, + Default = 1, + Account = 2, + Friend = 3, +}; + +enum class SourceFlag : u32 { + None = 0, + Database = 1 << 0, + Default = 1 << 1, +}; +DECLARE_ENUM_FLAG_OPERATORS(SourceFlag); + +enum class ValidationResult : u32 { + NoErrors = 0x0, + InvalidBeardColor = 0x1, + InvalidBeardType = 0x2, + InvalidBuild = 0x3, + InvalidEyeAspect = 0x4, + InvalidEyeColor = 0x5, + InvalidEyeRotate = 0x6, + InvalidEyeScale = 0x7, + InvalidEyeType = 0x8, + InvalidEyeX = 0x9, + InvalidEyeY = 0xa, + InvalidEyebrowAspect = 0xb, + InvalidEyebrowColor = 0xc, + InvalidEyebrowRotate = 0xd, + InvalidEyebrowScale = 0xe, + InvalidEyebrowType = 0xf, + InvalidEyebrowX = 0x10, + InvalidEyebrowY = 0x11, + InvalidFacelineColor = 0x12, + InvalidFacelineMake = 0x13, + InvalidFacelineWrinkle = 0x14, + InvalidFacelineType = 0x15, + InvalidColor = 0x16, + InvalidFont = 0x17, + InvalidGender = 0x18, + InvalidGlassColor = 0x19, + InvalidGlassScale = 0x1a, + InvalidGlassType = 0x1b, + InvalidGlassY = 0x1c, + InvalidHairColor = 0x1d, + InvalidHairFlip = 0x1e, + InvalidHairType = 0x1f, + InvalidHeight = 0x20, + InvalidMoleScale = 0x21, + InvalidMoleType = 0x22, + InvalidMoleX = 0x23, + InvalidMoleY = 0x24, + InvalidMouthAspect = 0x25, + InvalidMouthColor = 0x26, + InvalidMouthScale = 0x27, + InvalidMouthType = 0x28, + InvalidMouthY = 0x29, + InvalidMustacheScale = 0x2a, + InvalidMustacheType = 0x2b, + InvalidMustacheY = 0x2c, + InvalidNoseScale = 0x2e, + InvalidNoseType = 0x2f, + InvalidNoseY = 0x30, + InvalidRegionMove = 0x31, + InvalidCreateId = 0x32, + InvalidName = 0x33, + InvalidChecksum = 0x34, + InvalidType = 0x35, +}; + +struct Nickname { + std::array<char16_t, MaxNameSize> data{}; + + // Checks for null or dirty strings + bool IsValid() const { + if (data[0] == 0) { + return false; + } + + std::size_t index = 1; + while (data[index] != 0) { + index++; + } + while (index < MaxNameSize && data[index] == 0) { + index++; + } + return index == MaxNameSize; + } +}; +static_assert(sizeof(Nickname) == 0x14, "Nickname is an invalid size"); + +struct DefaultMii { + u32 face_type{}; + u32 face_color{}; + u32 face_wrinkle{}; + u32 face_makeup{}; + u32 hair_type{}; + u32 hair_color{}; + u32 hair_flip{}; + u32 eye_type{}; + u32 eye_color{}; + u32 eye_scale{}; + u32 eye_aspect{}; + u32 eye_rotate{}; + u32 eye_x{}; + u32 eye_y{}; + u32 eyebrow_type{}; + u32 eyebrow_color{}; + u32 eyebrow_scale{}; + u32 eyebrow_aspect{}; + u32 eyebrow_rotate{}; + u32 eyebrow_x{}; + u32 eyebrow_y{}; + u32 nose_type{}; + u32 nose_scale{}; + u32 nose_y{}; + u32 mouth_type{}; + u32 mouth_color{}; + u32 mouth_scale{}; + u32 mouth_aspect{}; + u32 mouth_y{}; + u32 mustache_type{}; + u32 beard_type{}; + u32 beard_color{}; + u32 mustache_scale{}; + u32 mustache_y{}; + u32 glasses_type{}; + u32 glasses_color{}; + u32 glasses_scale{}; + u32 glasses_y{}; + u32 mole_type{}; + u32 mole_scale{}; + u32 mole_x{}; + u32 mole_y{}; + u32 height{}; + u32 weight{}; + u32 gender{}; + u32 favorite_color{}; + u32 region_move{}; + u32 font_region{}; + u32 type{}; + Nickname nickname; +}; +static_assert(sizeof(DefaultMii) == 0xd8, "DefaultMii has incorrect size."); + +struct DatabaseSessionMetadata { + u32 interface_version; + u32 magic; + u64 update_counter; + + bool IsInterfaceVersionSupported(u32 version) const { + return version <= interface_version; + } +}; + +} // namespace Service::Mii diff --git a/src/core/hle/service/mii/mii_util.h b/src/core/hle/service/mii/mii_util.h new file mode 100644 index 000000000..3534fa31d --- /dev/null +++ b/src/core/hle/service/mii/mii_util.h @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <random> +#include <span> + +#include "common/common_types.h" +#include "common/swap.h" +#include "common/uuid.h" +#include "core/hle/service/mii/mii_types.h" + +namespace Service::Mii { +class MiiUtil { +public: + static u16 CalculateCrc16(const void* data, std::size_t size) { + s32 crc{}; + for (std::size_t i = 0; i < size; i++) { + crc ^= static_cast<const u8*>(data)[i] << 8; + for (std::size_t j = 0; j < 8; j++) { + crc <<= 1; + if ((crc & 0x10000) != 0) { + crc = (crc ^ 0x1021) & 0xFFFF; + } + } + } + return Common::swap16(static_cast<u16>(crc)); + } + + static u16 CalculateDeviceCrc16(const Common::UUID& uuid, std::size_t data_size) { + constexpr u16 magic{0x1021}; + s32 crc{}; + + for (std::size_t i = 0; i < uuid.uuid.size(); i++) { + for (std::size_t j = 0; j < 8; j++) { + crc <<= 1; + if ((crc & 0x10000) != 0) { + crc = crc ^ magic; + } + } + crc ^= uuid.uuid[i]; + } + + // As much as this looks wrong this is what N's does + + for (std::size_t i = 0; i < data_size * 8; i++) { + crc <<= 1; + if ((crc & 0x10000) != 0) { + crc = crc ^ magic; + } + } + + return Common::swap16(static_cast<u16>(crc)); + } + + static Common::UUID MakeCreateId() { + return Common::UUID::MakeRandomRFC4122V4(); + } + + static Common::UUID GetDeviceId() { + // This should be nn::settings::detail::GetMiiAuthorId() + return Common::UUID::MakeDefault(); + } + + template <typename T> + static T GetRandomValue(T min, T max) { + std::random_device device; + std::mt19937 gen(device()); + std::uniform_int_distribution<u64> distribution(static_cast<u64>(min), + static_cast<u64>(max)); + return static_cast<T>(distribution(gen)); + } + + template <typename T> + static T GetRandomValue(T max) { + return GetRandomValue<T>({}, max); + } + + static bool IsFontRegionValid(FontRegion font, std::span<const char16_t> text) { + // TODO: This function needs to check against the font tables + return true; + } +}; +} // namespace Service::Mii diff --git a/src/core/hle/service/mii/raw_data.h b/src/core/hle/service/mii/raw_data.h deleted file mode 100644 index c2bec68d4..000000000 --- a/src/core/hle/service/mii/raw_data.h +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include <array> - -#include "core/hle/service/mii/types.h" - -namespace Service::Mii::RawData { - -extern const std::array<Service::Mii::DefaultMii, 8> DefaultMii; -extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFaceline; -extern const std::array<Service::Mii::RandomMiiData3, 6> RandomMiiFacelineColor; -extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineWrinkle; -extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineMakeup; -extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiHairType; -extern const std::array<Service::Mii::RandomMiiData3, 9> RandomMiiHairColor; -extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyeType; -extern const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiEyeColor; -extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyebrowType; -extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiNoseType; -extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiMouthType; -extern const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiGlassType; - -} // namespace Service::Mii::RawData diff --git a/src/core/hle/service/mii/types.h b/src/core/hle/service/mii/types.h deleted file mode 100644 index c48d08d79..000000000 --- a/src/core/hle/service/mii/types.h +++ /dev/null @@ -1,553 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include <array> -#include <type_traits> - -#include "common/bit_field.h" -#include "common/common_funcs.h" -#include "common/common_types.h" -#include "common/uuid.h" - -namespace Service::Mii { - -enum class Age : u32 { - Young, - Normal, - Old, - All, -}; - -enum class BeardType : u32 { - None, - Beard1, - Beard2, - Beard3, - Beard4, - Beard5, -}; - -enum class BeardAndMustacheFlag : u32 { - Beard = 1, - Mustache, - All = Beard | Mustache, -}; -DECLARE_ENUM_FLAG_OPERATORS(BeardAndMustacheFlag); - -enum class FontRegion : u32 { - Standard, - China, - Korea, - Taiwan, -}; - -enum class Gender : u32 { - Male, - Female, - All, - Maximum = Female, -}; - -enum class HairFlip : u32 { - Left, - Right, - Maximum = Right, -}; - -enum class MustacheType : u32 { - None, - Mustache1, - Mustache2, - Mustache3, - Mustache4, - Mustache5, -}; - -enum class Race : u32 { - Black, - White, - Asian, - All, -}; - -enum class Source : u32 { - Database = 0, - Default = 1, - Account = 2, - Friend = 3, -}; - -enum class SourceFlag : u32 { - None = 0, - Database = 1 << 0, - Default = 1 << 1, -}; -DECLARE_ENUM_FLAG_OPERATORS(SourceFlag); - -// nn::mii::CharInfo -struct CharInfo { - Common::UUID uuid; - std::array<char16_t, 11> name; - u8 font_region; - u8 favorite_color; - u8 gender; - u8 height; - u8 build; - u8 type; - u8 region_move; - u8 faceline_type; - u8 faceline_color; - u8 faceline_wrinkle; - u8 faceline_make; - u8 hair_type; - u8 hair_color; - u8 hair_flip; - u8 eye_type; - u8 eye_color; - u8 eye_scale; - u8 eye_aspect; - u8 eye_rotate; - u8 eye_x; - u8 eye_y; - u8 eyebrow_type; - u8 eyebrow_color; - u8 eyebrow_scale; - u8 eyebrow_aspect; - u8 eyebrow_rotate; - u8 eyebrow_x; - u8 eyebrow_y; - u8 nose_type; - u8 nose_scale; - u8 nose_y; - u8 mouth_type; - u8 mouth_color; - u8 mouth_scale; - u8 mouth_aspect; - u8 mouth_y; - u8 beard_color; - u8 beard_type; - u8 mustache_type; - u8 mustache_scale; - u8 mustache_y; - u8 glasses_type; - u8 glasses_color; - u8 glasses_scale; - u8 glasses_y; - u8 mole_type; - u8 mole_scale; - u8 mole_x; - u8 mole_y; - u8 padding; -}; -static_assert(sizeof(CharInfo) == 0x58, "CharInfo has incorrect size."); -static_assert(std::has_unique_object_representations_v<CharInfo>, - "All bits of CharInfo must contribute to its value."); - -#pragma pack(push, 4) - -struct MiiInfoElement { - MiiInfoElement(const CharInfo& info_, Source source_) : info{info_}, source{source_} {} - - CharInfo info{}; - Source source{}; -}; -static_assert(sizeof(MiiInfoElement) == 0x5c, "MiiInfoElement has incorrect size."); - -struct MiiStoreBitFields { - union { - u32 word_0{}; - - BitField<0, 8, u32> hair_type; - BitField<8, 7, u32> height; - BitField<15, 1, u32> mole_type; - BitField<16, 7, u32> build; - BitField<23, 1, HairFlip> hair_flip; - BitField<24, 7, u32> hair_color; - BitField<31, 1, u32> type; - }; - - union { - u32 word_1{}; - - BitField<0, 7, u32> eye_color; - BitField<7, 1, Gender> gender; - BitField<8, 7, u32> eyebrow_color; - BitField<16, 7, u32> mouth_color; - BitField<24, 7, u32> beard_color; - }; - - union { - u32 word_2{}; - - BitField<0, 7, u32> glasses_color; - BitField<8, 6, u32> eye_type; - BitField<14, 2, u32> region_move; - BitField<16, 6, u32> mouth_type; - BitField<22, 2, FontRegion> font_region; - BitField<24, 5, u32> eye_y; - BitField<29, 3, u32> glasses_scale; - }; - - union { - u32 word_3{}; - - BitField<0, 5, u32> eyebrow_type; - BitField<5, 3, MustacheType> mustache_type; - BitField<8, 5, u32> nose_type; - BitField<13, 3, BeardType> beard_type; - BitField<16, 5, u32> nose_y; - BitField<21, 3, u32> mouth_aspect; - BitField<24, 5, u32> mouth_y; - BitField<29, 3, u32> eyebrow_aspect; - }; - - union { - u32 word_4{}; - - BitField<0, 5, u32> mustache_y; - BitField<5, 3, u32> eye_rotate; - BitField<8, 5, u32> glasses_y; - BitField<13, 3, u32> eye_aspect; - BitField<16, 5, u32> mole_x; - BitField<21, 3, u32> eye_scale; - BitField<24, 5, u32> mole_y; - }; - - union { - u32 word_5{}; - - BitField<0, 5, u32> glasses_type; - BitField<8, 4, u32> favorite_color; - BitField<12, 4, u32> faceline_type; - BitField<16, 4, u32> faceline_color; - BitField<20, 4, u32> faceline_wrinkle; - BitField<24, 4, u32> faceline_makeup; - BitField<28, 4, u32> eye_x; - }; - - union { - u32 word_6{}; - - BitField<0, 4, u32> eyebrow_scale; - BitField<4, 4, u32> eyebrow_rotate; - BitField<8, 4, u32> eyebrow_x; - BitField<12, 4, u32> eyebrow_y; - BitField<16, 4, u32> nose_scale; - BitField<20, 4, u32> mouth_scale; - BitField<24, 4, u32> mustache_scale; - BitField<28, 4, u32> mole_scale; - }; -}; -static_assert(sizeof(MiiStoreBitFields) == 0x1c, "MiiStoreBitFields has incorrect size."); -static_assert(std::is_trivially_copyable_v<MiiStoreBitFields>, - "MiiStoreBitFields is not trivially copyable."); - -// This is nn::mii::Ver3StoreData -// Based on citra HLE::Applets::MiiData and PretendoNetwork. -// https://github.com/citra-emu/citra/blob/master/src/core/hle/applets/mii_selector.h#L48 -// https://github.com/PretendoNetwork/mii-js/blob/master/mii.js#L299 -struct Ver3StoreData { - u8 version; - union { - u8 raw; - - BitField<0, 1, u8> allow_copying; - BitField<1, 1, u8> profanity_flag; - BitField<2, 2, u8> region_lock; - BitField<4, 2, u8> character_set; - } region_information; - u16_be mii_id; - u64_be system_id; - u32_be specialness_and_creation_date; - std::array<u8, 0x6> creator_mac; - u16_be padding; - union { - u16 raw; - - BitField<0, 1, u16> gender; - BitField<1, 4, u16> birth_month; - BitField<5, 5, u16> birth_day; - BitField<10, 4, u16> favorite_color; - BitField<14, 1, u16> favorite; - } mii_information; - std::array<char16_t, 0xA> mii_name; - u8 height; - u8 build; - union { - u8 raw; - - BitField<0, 1, u8> disable_sharing; - BitField<1, 4, u8> face_shape; - BitField<5, 3, u8> skin_color; - } appearance_bits1; - union { - u8 raw; - - BitField<0, 4, u8> wrinkles; - BitField<4, 4, u8> makeup; - } appearance_bits2; - u8 hair_style; - union { - u8 raw; - - BitField<0, 3, u8> hair_color; - BitField<3, 1, u8> flip_hair; - } appearance_bits3; - union { - u32 raw; - - BitField<0, 6, u32> eye_type; - BitField<6, 3, u32> eye_color; - BitField<9, 4, u32> eye_scale; - BitField<13, 3, u32> eye_vertical_stretch; - BitField<16, 5, u32> eye_rotation; - BitField<21, 4, u32> eye_spacing; - BitField<25, 5, u32> eye_y_position; - } appearance_bits4; - union { - u32 raw; - - BitField<0, 5, u32> eyebrow_style; - BitField<5, 3, u32> eyebrow_color; - BitField<8, 4, u32> eyebrow_scale; - BitField<12, 3, u32> eyebrow_yscale; - BitField<16, 4, u32> eyebrow_rotation; - BitField<21, 4, u32> eyebrow_spacing; - BitField<25, 5, u32> eyebrow_y_position; - } appearance_bits5; - union { - u16 raw; - - BitField<0, 5, u16> nose_type; - BitField<5, 4, u16> nose_scale; - BitField<9, 5, u16> nose_y_position; - } appearance_bits6; - union { - u16 raw; - - BitField<0, 6, u16> mouth_type; - BitField<6, 3, u16> mouth_color; - BitField<9, 4, u16> mouth_scale; - BitField<13, 3, u16> mouth_horizontal_stretch; - } appearance_bits7; - union { - u8 raw; - - BitField<0, 5, u8> mouth_y_position; - BitField<5, 3, u8> mustache_type; - } appearance_bits8; - u8 allow_copying; - union { - u16 raw; - - BitField<0, 3, u16> bear_type; - BitField<3, 3, u16> facial_hair_color; - BitField<6, 4, u16> mustache_scale; - BitField<10, 5, u16> mustache_y_position; - } appearance_bits9; - union { - u16 raw; - - BitField<0, 4, u16> glasses_type; - BitField<4, 3, u16> glasses_color; - BitField<7, 4, u16> glasses_scale; - BitField<11, 5, u16> glasses_y_position; - } appearance_bits10; - union { - u16 raw; - - BitField<0, 1, u16> mole_enabled; - BitField<1, 4, u16> mole_scale; - BitField<5, 5, u16> mole_x_position; - BitField<10, 5, u16> mole_y_position; - } appearance_bits11; - - std::array<u16_le, 0xA> author_name; - INSERT_PADDING_BYTES(0x2); - u16_be crc; -}; -static_assert(sizeof(Ver3StoreData) == 0x60, "Ver3StoreData is an invalid size"); - -struct NfpStoreDataExtension { - u8 faceline_color; - u8 hair_color; - u8 eye_color; - u8 eyebrow_color; - u8 mouth_color; - u8 beard_color; - u8 glass_color; - u8 glass_type; -}; -static_assert(sizeof(NfpStoreDataExtension) == 0x8, "NfpStoreDataExtension is an invalid size"); - -constexpr std::array<u8, 0x10> Ver3FacelineColorTable{ - 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x0, 0x1, 0x5, 0x5, -}; - -constexpr std::array<u8, 100> Ver3HairColorTable{ - 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x4, 0x3, 0x5, 0x4, 0x4, 0x6, 0x2, 0x0, - 0x6, 0x4, 0x3, 0x2, 0x2, 0x7, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, - 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x4, - 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5, - 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x7, 0x5, 0x7, 0x7, 0x7, 0x7, 0x7, 0x6, 0x7, - 0x7, 0x7, 0x7, 0x7, 0x3, 0x7, 0x7, 0x7, 0x7, 0x7, 0x0, 0x4, 0x4, 0x4, 0x4, -}; - -constexpr std::array<u8, 100> Ver3EyeColorTable{ - 0x0, 0x2, 0x2, 0x2, 0x1, 0x3, 0x2, 0x3, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x2, 0x2, 0x4, - 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, - 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x0, 0x4, 0x4, - 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, - 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x2, 0x2, - 0x3, 0x3, 0x3, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, -}; - -constexpr std::array<u8, 100> Ver3MouthlineColorTable{ - 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x4, - 0x4, 0x4, 0x0, 0x1, 0x2, 0x3, 0x4, 0x4, 0x2, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x1, 0x4, - 0x4, 0x2, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, - 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, - 0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, - 0x3, 0x3, 0x3, 0x3, 0x4, 0x0, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, 0x3, 0x3, -}; - -constexpr std::array<u8, 100> Ver3GlassColorTable{ - 0x0, 0x1, 0x1, 0x1, 0x5, 0x1, 0x1, 0x4, 0x0, 0x5, 0x1, 0x1, 0x3, 0x5, 0x1, 0x2, 0x3, - 0x4, 0x5, 0x4, 0x2, 0x2, 0x4, 0x4, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, - 0x2, 0x2, 0x2, 0x2, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, - 0x3, 0x3, 0x3, 0x3, 0x3, 0x0, 0x0, 0x0, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x0, 0x5, 0x5, - 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x1, 0x4, - 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, -}; - -constexpr std::array<u8, 20> Ver3GlassTypeTable{ - 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x1, - 0x2, 0x1, 0x3, 0x7, 0x7, 0x6, 0x7, 0x8, 0x7, 0x7, -}; - -struct MiiStoreData { - using Name = std::array<char16_t, 10>; - - MiiStoreData(); - MiiStoreData(const Name& name, const MiiStoreBitFields& bit_fields, - const Common::UUID& user_id); - - // This corresponds to the above structure MiiStoreBitFields. I did it like this because the - // BitField<> type makes this (and any thing that contains it) not trivially copyable, which is - // not suitable for our uses. - struct { - std::array<u8, 0x1C> data{}; - static_assert(sizeof(MiiStoreBitFields) == sizeof(data), "data field has incorrect size."); - - Name name{}; - Common::UUID uuid{}; - } data; - - u16 data_crc{}; - u16 device_crc{}; -}; -static_assert(sizeof(MiiStoreData) == 0x44, "MiiStoreData has incorrect size."); - -struct MiiStoreDataElement { - MiiStoreData data{}; - Source source{}; -}; -static_assert(sizeof(MiiStoreDataElement) == 0x48, "MiiStoreDataElement has incorrect size."); - -struct MiiDatabase { - u32 magic{}; // 'NFDB' - std::array<MiiStoreData, 0x64> miis{}; - INSERT_PADDING_BYTES(1); - u8 count{}; - u16 crc{}; -}; -static_assert(sizeof(MiiDatabase) == 0x1A98, "MiiDatabase has incorrect size."); - -struct RandomMiiValues { - std::array<u8, 0xbc> values{}; -}; -static_assert(sizeof(RandomMiiValues) == 0xbc, "RandomMiiValues has incorrect size."); - -struct RandomMiiData4 { - Gender gender{}; - Age age{}; - Race race{}; - u32 values_count{}; - std::array<u32, 47> values{}; -}; -static_assert(sizeof(RandomMiiData4) == 0xcc, "RandomMiiData4 has incorrect size."); - -struct RandomMiiData3 { - u32 arg_1; - u32 arg_2; - u32 values_count; - std::array<u32, 47> values{}; -}; -static_assert(sizeof(RandomMiiData3) == 0xc8, "RandomMiiData3 has incorrect size."); - -struct RandomMiiData2 { - u32 arg_1; - u32 values_count; - std::array<u32, 47> values{}; -}; -static_assert(sizeof(RandomMiiData2) == 0xc4, "RandomMiiData2 has incorrect size."); - -struct DefaultMii { - u32 face_type{}; - u32 face_color{}; - u32 face_wrinkle{}; - u32 face_makeup{}; - u32 hair_type{}; - u32 hair_color{}; - u32 hair_flip{}; - u32 eye_type{}; - u32 eye_color{}; - u32 eye_scale{}; - u32 eye_aspect{}; - u32 eye_rotate{}; - u32 eye_x{}; - u32 eye_y{}; - u32 eyebrow_type{}; - u32 eyebrow_color{}; - u32 eyebrow_scale{}; - u32 eyebrow_aspect{}; - u32 eyebrow_rotate{}; - u32 eyebrow_x{}; - u32 eyebrow_y{}; - u32 nose_type{}; - u32 nose_scale{}; - u32 nose_y{}; - u32 mouth_type{}; - u32 mouth_color{}; - u32 mouth_scale{}; - u32 mouth_aspect{}; - u32 mouth_y{}; - u32 mustache_type{}; - u32 beard_type{}; - u32 beard_color{}; - u32 mustache_scale{}; - u32 mustache_y{}; - u32 glasses_type{}; - u32 glasses_color{}; - u32 glasses_scale{}; - u32 glasses_y{}; - u32 mole_type{}; - u32 mole_scale{}; - u32 mole_x{}; - u32 mole_y{}; - u32 height{}; - u32 weight{}; - Gender gender{}; - u32 favorite_color{}; - u32 region{}; - FontRegion font_region{}; - u32 type{}; - INSERT_PADDING_WORDS(5); -}; -static_assert(sizeof(DefaultMii) == 0xd8, "MiiStoreData has incorrect size."); - -#pragma pack(pop) - -} // namespace Service::Mii diff --git a/src/core/hle/service/mii/types/char_info.cpp b/src/core/hle/service/mii/types/char_info.cpp new file mode 100644 index 000000000..c82186c73 --- /dev/null +++ b/src/core/hle/service/mii/types/char_info.cpp @@ -0,0 +1,482 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/service/mii/types/char_info.h" +#include "core/hle/service/mii/types/store_data.h" + +namespace Service::Mii { + +void CharInfo::SetFromStoreData(const StoreData& store_data) { + name = store_data.GetNickname(); + null_terminator = '\0'; + create_id = store_data.GetCreateId(); + font_region = store_data.GetFontRegion(); + favorite_color = store_data.GetFavoriteColor(); + gender = store_data.GetGender(); + height = store_data.GetHeight(); + build = store_data.GetBuild(); + type = store_data.GetType(); + region_move = store_data.GetRegionMove(); + faceline_type = store_data.GetFacelineType(); + faceline_color = store_data.GetFacelineColor(); + faceline_wrinkle = store_data.GetFacelineWrinkle(); + faceline_make = store_data.GetFacelineMake(); + hair_type = store_data.GetHairType(); + hair_color = store_data.GetHairColor(); + hair_flip = store_data.GetHairFlip(); + eye_type = store_data.GetEyeType(); + eye_color = store_data.GetEyeColor(); + eye_scale = store_data.GetEyeScale(); + eye_aspect = store_data.GetEyeAspect(); + eye_rotate = store_data.GetEyeRotate(); + eye_x = store_data.GetEyeX(); + eye_y = store_data.GetEyeY(); + eyebrow_type = store_data.GetEyebrowType(); + eyebrow_color = store_data.GetEyebrowColor(); + eyebrow_scale = store_data.GetEyebrowScale(); + eyebrow_aspect = store_data.GetEyebrowAspect(); + eyebrow_rotate = store_data.GetEyebrowRotate(); + eyebrow_x = store_data.GetEyebrowX(); + eyebrow_y = store_data.GetEyebrowY(); + nose_type = store_data.GetNoseType(); + nose_scale = store_data.GetNoseScale(); + nose_y = store_data.GetNoseY(); + mouth_type = store_data.GetMouthType(); + mouth_color = store_data.GetMouthColor(); + mouth_scale = store_data.GetMouthScale(); + mouth_aspect = store_data.GetMouthAspect(); + mouth_y = store_data.GetMouthY(); + beard_color = store_data.GetBeardColor(); + beard_type = store_data.GetBeardType(); + mustache_type = store_data.GetMustacheType(); + mustache_scale = store_data.GetMustacheScale(); + mustache_y = store_data.GetMustacheY(); + glass_type = store_data.GetGlassType(); + glass_color = store_data.GetGlassColor(); + glass_scale = store_data.GetGlassScale(); + glass_y = store_data.GetGlassY(); + mole_type = store_data.GetMoleType(); + mole_scale = store_data.GetMoleScale(); + mole_x = store_data.GetMoleX(); + mole_y = store_data.GetMoleY(); + padding = '\0'; +} + +ValidationResult CharInfo::Verify() const { + if (!create_id.IsValid()) { + return ValidationResult::InvalidCreateId; + } + if (!name.IsValid()) { + return ValidationResult::InvalidName; + } + if (font_region > FontRegion::Max) { + return ValidationResult::InvalidFont; + } + if (favorite_color > FavoriteColor::Max) { + return ValidationResult::InvalidColor; + } + if (gender > Gender::Max) { + return ValidationResult::InvalidGender; + } + if (height > MaxHeight) { + return ValidationResult::InvalidHeight; + } + if (build > MaxBuild) { + return ValidationResult::InvalidBuild; + } + if (type > MaxType) { + return ValidationResult::InvalidType; + } + if (region_move > MaxRegionMove) { + return ValidationResult::InvalidRegionMove; + } + if (faceline_type > FacelineType::Max) { + return ValidationResult::InvalidFacelineType; + } + if (faceline_color > FacelineColor::Max) { + return ValidationResult::InvalidFacelineColor; + } + if (faceline_wrinkle > FacelineWrinkle::Max) { + return ValidationResult::InvalidFacelineWrinkle; + } + if (faceline_make > FacelineMake::Max) { + return ValidationResult::InvalidFacelineMake; + } + if (hair_type > HairType::Max) { + return ValidationResult::InvalidHairType; + } + if (hair_color > CommonColor::Max) { + return ValidationResult::InvalidHairColor; + } + if (hair_flip > HairFlip::Max) { + return ValidationResult::InvalidHairFlip; + } + if (eye_type > EyeType::Max) { + return ValidationResult::InvalidEyeType; + } + if (eye_color > CommonColor::Max) { + return ValidationResult::InvalidEyeColor; + } + if (eye_scale > MaxEyeScale) { + return ValidationResult::InvalidEyeScale; + } + if (eye_aspect > MaxEyeAspect) { + return ValidationResult::InvalidEyeAspect; + } + if (eye_rotate > MaxEyeX) { + return ValidationResult::InvalidEyeRotate; + } + if (eye_x > MaxEyeX) { + return ValidationResult::InvalidEyeX; + } + if (eye_y > MaxEyeY) { + return ValidationResult::InvalidEyeY; + } + if (eyebrow_type > EyebrowType::Max) { + return ValidationResult::InvalidEyebrowType; + } + if (eyebrow_color > CommonColor::Max) { + return ValidationResult::InvalidEyebrowColor; + } + if (eyebrow_scale > MaxEyebrowScale) { + return ValidationResult::InvalidEyebrowScale; + } + if (eyebrow_aspect > MaxEyebrowAspect) { + return ValidationResult::InvalidEyebrowAspect; + } + if (eyebrow_rotate > MaxEyebrowRotate) { + return ValidationResult::InvalidEyebrowRotate; + } + if (eyebrow_x > MaxEyebrowX) { + return ValidationResult::InvalidEyebrowX; + } + if (eyebrow_y - 3 > MaxEyebrowY) { + return ValidationResult::InvalidEyebrowY; + } + if (nose_type > NoseType::Max) { + return ValidationResult::InvalidNoseType; + } + if (nose_scale > MaxNoseScale) { + return ValidationResult::InvalidNoseScale; + } + if (nose_y > MaxNoseY) { + return ValidationResult::InvalidNoseY; + } + if (mouth_type > MouthType::Max) { + return ValidationResult::InvalidMouthType; + } + if (mouth_color > CommonColor::Max) { + return ValidationResult::InvalidMouthColor; + } + if (mouth_scale > MaxMouthScale) { + return ValidationResult::InvalidMouthScale; + } + if (mouth_aspect > MaxMoutAspect) { + return ValidationResult::InvalidMouthAspect; + } + if (mouth_y > MaxMouthY) { + return ValidationResult::InvalidMoleY; + } + if (beard_color > CommonColor::Max) { + return ValidationResult::InvalidBeardColor; + } + if (beard_type > BeardType::Max) { + return ValidationResult::InvalidBeardType; + } + if (mustache_type > MustacheType::Max) { + return ValidationResult::InvalidMustacheType; + } + if (mustache_scale > MaxMustacheScale) { + return ValidationResult::InvalidMustacheScale; + } + if (mustache_y > MaxMustacheY) { + return ValidationResult::InvalidMustacheY; + } + if (glass_type > GlassType::Max) { + return ValidationResult::InvalidGlassType; + } + if (glass_color > CommonColor::Max) { + return ValidationResult::InvalidGlassColor; + } + if (glass_scale > MaxGlassScale) { + return ValidationResult::InvalidGlassScale; + } + if (glass_y > MaxGlassY) { + return ValidationResult::InvalidGlassY; + } + if (mole_type > MoleType::Max) { + return ValidationResult::InvalidMoleType; + } + if (mole_scale > MaxMoleScale) { + return ValidationResult::InvalidMoleScale; + } + if (mole_x > MaxMoleX) { + return ValidationResult::InvalidMoleX; + } + if (mole_y > MaxMoleY) { + return ValidationResult::InvalidMoleY; + } + return ValidationResult::NoErrors; +} + +Common::UUID CharInfo::GetCreateId() const { + return create_id; +} + +Nickname CharInfo::GetNickname() const { + return name; +} + +FontRegion CharInfo::GetFontRegion() const { + return font_region; +} + +FavoriteColor CharInfo::GetFavoriteColor() const { + return favorite_color; +} + +Gender CharInfo::GetGender() const { + return gender; +} + +u8 CharInfo::GetHeight() const { + return height; +} + +u8 CharInfo::GetBuild() const { + return build; +} + +u8 CharInfo::GetType() const { + return type; +} + +u8 CharInfo::GetRegionMove() const { + return region_move; +} + +FacelineType CharInfo::GetFacelineType() const { + return faceline_type; +} + +FacelineColor CharInfo::GetFacelineColor() const { + return faceline_color; +} + +FacelineWrinkle CharInfo::GetFacelineWrinkle() const { + return faceline_wrinkle; +} + +FacelineMake CharInfo::GetFacelineMake() const { + return faceline_make; +} + +HairType CharInfo::GetHairType() const { + return hair_type; +} + +CommonColor CharInfo::GetHairColor() const { + return hair_color; +} + +HairFlip CharInfo::GetHairFlip() const { + return hair_flip; +} + +EyeType CharInfo::GetEyeType() const { + return eye_type; +} + +CommonColor CharInfo::GetEyeColor() const { + return eye_color; +} + +u8 CharInfo::GetEyeScale() const { + return eye_scale; +} + +u8 CharInfo::GetEyeAspect() const { + return eye_aspect; +} + +u8 CharInfo::GetEyeRotate() const { + return eye_rotate; +} + +u8 CharInfo::GetEyeX() const { + return eye_x; +} + +u8 CharInfo::GetEyeY() const { + return eye_y; +} + +EyebrowType CharInfo::GetEyebrowType() const { + return eyebrow_type; +} + +CommonColor CharInfo::GetEyebrowColor() const { + return eyebrow_color; +} + +u8 CharInfo::GetEyebrowScale() const { + return eyebrow_scale; +} + +u8 CharInfo::GetEyebrowAspect() const { + return eyebrow_aspect; +} + +u8 CharInfo::GetEyebrowRotate() const { + return eyebrow_rotate; +} + +u8 CharInfo::GetEyebrowX() const { + return eyebrow_x; +} + +u8 CharInfo::GetEyebrowY() const { + return eyebrow_y; +} + +NoseType CharInfo::GetNoseType() const { + return nose_type; +} + +u8 CharInfo::GetNoseScale() const { + return nose_scale; +} + +u8 CharInfo::GetNoseY() const { + return nose_y; +} + +MouthType CharInfo::GetMouthType() const { + return mouth_type; +} + +CommonColor CharInfo::GetMouthColor() const { + return mouth_color; +} + +u8 CharInfo::GetMouthScale() const { + return mouth_scale; +} + +u8 CharInfo::GetMouthAspect() const { + return mouth_aspect; +} + +u8 CharInfo::GetMouthY() const { + return mouth_y; +} + +CommonColor CharInfo::GetBeardColor() const { + return beard_color; +} + +BeardType CharInfo::GetBeardType() const { + return beard_type; +} + +MustacheType CharInfo::GetMustacheType() const { + return mustache_type; +} + +u8 CharInfo::GetMustacheScale() const { + return mustache_scale; +} + +u8 CharInfo::GetMustacheY() const { + return mustache_y; +} + +GlassType CharInfo::GetGlassType() const { + return glass_type; +} + +CommonColor CharInfo::GetGlassColor() const { + return glass_color; +} + +u8 CharInfo::GetGlassScale() const { + return glass_scale; +} + +u8 CharInfo::GetGlassY() const { + return glass_y; +} + +MoleType CharInfo::GetMoleType() const { + return mole_type; +} + +u8 CharInfo::GetMoleScale() const { + return mole_scale; +} + +u8 CharInfo::GetMoleX() const { + return mole_x; +} + +u8 CharInfo::GetMoleY() const { + return mole_y; +} + +bool CharInfo::operator==(const CharInfo& info) { + bool is_identical = info.Verify() == ValidationResult::NoErrors; + is_identical &= name.data == info.GetNickname().data; + is_identical &= create_id == info.GetCreateId(); + is_identical &= font_region == info.GetFontRegion(); + is_identical &= favorite_color == info.GetFavoriteColor(); + is_identical &= gender == info.GetGender(); + is_identical &= height == info.GetHeight(); + is_identical &= build == info.GetBuild(); + is_identical &= type == info.GetType(); + is_identical &= region_move == info.GetRegionMove(); + is_identical &= faceline_type == info.GetFacelineType(); + is_identical &= faceline_color == info.GetFacelineColor(); + is_identical &= faceline_wrinkle == info.GetFacelineWrinkle(); + is_identical &= faceline_make == info.GetFacelineMake(); + is_identical &= hair_type == info.GetHairType(); + is_identical &= hair_color == info.GetHairColor(); + is_identical &= hair_flip == info.GetHairFlip(); + is_identical &= eye_type == info.GetEyeType(); + is_identical &= eye_color == info.GetEyeColor(); + is_identical &= eye_scale == info.GetEyeScale(); + is_identical &= eye_aspect == info.GetEyeAspect(); + is_identical &= eye_rotate == info.GetEyeRotate(); + is_identical &= eye_x == info.GetEyeX(); + is_identical &= eye_y == info.GetEyeY(); + is_identical &= eyebrow_type == info.GetEyebrowType(); + is_identical &= eyebrow_color == info.GetEyebrowColor(); + is_identical &= eyebrow_scale == info.GetEyebrowScale(); + is_identical &= eyebrow_aspect == info.GetEyebrowAspect(); + is_identical &= eyebrow_rotate == info.GetEyebrowRotate(); + is_identical &= eyebrow_x == info.GetEyebrowX(); + is_identical &= eyebrow_y == info.GetEyebrowY(); + is_identical &= nose_type == info.GetNoseType(); + is_identical &= nose_scale == info.GetNoseScale(); + is_identical &= nose_y == info.GetNoseY(); + is_identical &= mouth_type == info.GetMouthType(); + is_identical &= mouth_color == info.GetMouthColor(); + is_identical &= mouth_scale == info.GetMouthScale(); + is_identical &= mouth_aspect == info.GetMouthAspect(); + is_identical &= mouth_y == info.GetMouthY(); + is_identical &= beard_color == info.GetBeardColor(); + is_identical &= beard_type == info.GetBeardType(); + is_identical &= mustache_type == info.GetMustacheType(); + is_identical &= mustache_scale == info.GetMustacheScale(); + is_identical &= mustache_y == info.GetMustacheY(); + is_identical &= glass_type == info.GetGlassType(); + is_identical &= glass_color == info.GetGlassColor(); + is_identical &= glass_scale == info.GetGlassScale(); + is_identical &= glass_y == info.GetGlassY(); + is_identical &= mole_type == info.GetMoleType(); + is_identical &= mole_scale == info.GetMoleScale(); + is_identical &= mole_x == info.GetMoleX(); + is_identical &= mole_y == info.GetMoleY(); + return is_identical; +} + +} // namespace Service::Mii diff --git a/src/core/hle/service/mii/types/char_info.h b/src/core/hle/service/mii/types/char_info.h new file mode 100644 index 000000000..d0c457fd5 --- /dev/null +++ b/src/core/hle/service/mii/types/char_info.h @@ -0,0 +1,137 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/hle/service/mii/mii_types.h" + +namespace Service::Mii { +class StoreData; + +// This is nn::mii::detail::CharInfoRaw +class CharInfo { +public: + void SetFromStoreData(const StoreData& store_data_raw); + + ValidationResult Verify() const; + + Common::UUID GetCreateId() const; + Nickname GetNickname() const; + FontRegion GetFontRegion() const; + FavoriteColor GetFavoriteColor() const; + Gender GetGender() const; + u8 GetHeight() const; + u8 GetBuild() const; + u8 GetType() const; + u8 GetRegionMove() const; + FacelineType GetFacelineType() const; + FacelineColor GetFacelineColor() const; + FacelineWrinkle GetFacelineWrinkle() const; + FacelineMake GetFacelineMake() const; + HairType GetHairType() const; + CommonColor GetHairColor() const; + HairFlip GetHairFlip() const; + EyeType GetEyeType() const; + CommonColor GetEyeColor() const; + u8 GetEyeScale() const; + u8 GetEyeAspect() const; + u8 GetEyeRotate() const; + u8 GetEyeX() const; + u8 GetEyeY() const; + EyebrowType GetEyebrowType() const; + CommonColor GetEyebrowColor() const; + u8 GetEyebrowScale() const; + u8 GetEyebrowAspect() const; + u8 GetEyebrowRotate() const; + u8 GetEyebrowX() const; + u8 GetEyebrowY() const; + NoseType GetNoseType() const; + u8 GetNoseScale() const; + u8 GetNoseY() const; + MouthType GetMouthType() const; + CommonColor GetMouthColor() const; + u8 GetMouthScale() const; + u8 GetMouthAspect() const; + u8 GetMouthY() const; + CommonColor GetBeardColor() const; + BeardType GetBeardType() const; + MustacheType GetMustacheType() const; + u8 GetMustacheScale() const; + u8 GetMustacheY() const; + GlassType GetGlassType() const; + CommonColor GetGlassColor() const; + u8 GetGlassScale() const; + u8 GetGlassY() const; + MoleType GetMoleType() const; + u8 GetMoleScale() const; + u8 GetMoleX() const; + u8 GetMoleY() const; + + bool operator==(const CharInfo& info); + +private: + Common::UUID create_id{}; + Nickname name{}; + u16 null_terminator{}; + FontRegion font_region{}; + FavoriteColor favorite_color{}; + Gender gender{}; + u8 height{}; + u8 build{}; + u8 type{}; + u8 region_move{}; + FacelineType faceline_type{}; + FacelineColor faceline_color{}; + FacelineWrinkle faceline_wrinkle{}; + FacelineMake faceline_make{}; + HairType hair_type{}; + CommonColor hair_color{}; + HairFlip hair_flip{}; + EyeType eye_type{}; + CommonColor eye_color{}; + u8 eye_scale{}; + u8 eye_aspect{}; + u8 eye_rotate{}; + u8 eye_x{}; + u8 eye_y{}; + EyebrowType eyebrow_type{}; + CommonColor eyebrow_color{}; + u8 eyebrow_scale{}; + u8 eyebrow_aspect{}; + u8 eyebrow_rotate{}; + u8 eyebrow_x{}; + u8 eyebrow_y{}; + NoseType nose_type{}; + u8 nose_scale{}; + u8 nose_y{}; + MouthType mouth_type{}; + CommonColor mouth_color{}; + u8 mouth_scale{}; + u8 mouth_aspect{}; + u8 mouth_y{}; + CommonColor beard_color{}; + BeardType beard_type{}; + MustacheType mustache_type{}; + u8 mustache_scale{}; + u8 mustache_y{}; + GlassType glass_type{}; + CommonColor glass_color{}; + u8 glass_scale{}; + u8 glass_y{}; + MoleType mole_type{}; + u8 mole_scale{}; + u8 mole_x{}; + u8 mole_y{}; + u8 padding{}; +}; +static_assert(sizeof(CharInfo) == 0x58, "CharInfo has incorrect size."); +static_assert(std::has_unique_object_representations_v<CharInfo>, + "All bits of CharInfo must contribute to its value."); + +struct CharInfoElement { + CharInfo char_info{}; + Source source{}; +}; +static_assert(sizeof(CharInfoElement) == 0x5c, "CharInfoElement has incorrect size."); + +}; // namespace Service::Mii diff --git a/src/core/hle/service/mii/types/core_data.cpp b/src/core/hle/service/mii/types/core_data.cpp new file mode 100644 index 000000000..1068031e3 --- /dev/null +++ b/src/core/hle/service/mii/types/core_data.cpp @@ -0,0 +1,805 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/assert.h" +#include "core/hle/service/mii/mii_util.h" +#include "core/hle/service/mii/types/char_info.h" +#include "core/hle/service/mii/types/core_data.h" +#include "core/hle/service/mii/types/raw_data.h" + +namespace Service::Mii { + +void CoreData::SetDefault() { + data = {}; + name = GetDefaultNickname(); +} + +void CoreData::BuildRandom(Age age, Gender gender, Race race) { + if (gender == Gender::All) { + gender = MiiUtil::GetRandomValue(Gender::Max); + } + + if (age == Age::All) { + const auto random{MiiUtil::GetRandomValue<int>(10)}; + if (random >= 8) { + age = Age::Old; + } else if (random >= 4) { + age = Age::Normal; + } else { + age = Age::Young; + } + } + + if (race == Race::All) { + const auto random{MiiUtil::GetRandomValue<int>(10)}; + if (random >= 8) { + race = Race::Black; + } else if (random >= 4) { + race = Race::White; + } else { + race = Race::Asian; + } + } + + SetGender(gender); + SetFavoriteColor(MiiUtil::GetRandomValue(FavoriteColor::Max)); + SetRegionMove(0); + SetFontRegion(FontRegion::Standard); + SetType(0); + SetHeight(64); + SetBuild(64); + + u32 axis_y{}; + if (gender == Gender::Female && age == Age::Young) { + axis_y = MiiUtil::GetRandomValue<u32>(3); + } + + const std::size_t index{3 * static_cast<std::size_t>(age) + + 9 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race)}; + + const auto& faceline_type_info{RawData::RandomMiiFaceline.at(index)}; + const auto& faceline_color_info{RawData::RandomMiiFacelineColor.at( + 3 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race))}; + const auto& faceline_wrinkle_info{RawData::RandomMiiFacelineWrinkle.at(index)}; + const auto& faceline_makeup_info{RawData::RandomMiiFacelineMakeup.at(index)}; + const auto& hair_type_info{RawData::RandomMiiHairType.at(index)}; + const auto& hair_color_info{RawData::RandomMiiHairColor.at(3 * static_cast<std::size_t>(race) + + static_cast<std::size_t>(age))}; + const auto& eye_type_info{RawData::RandomMiiEyeType.at(index)}; + const auto& eye_color_info{RawData::RandomMiiEyeColor.at(static_cast<std::size_t>(race))}; + const auto& eyebrow_type_info{RawData::RandomMiiEyebrowType.at(index)}; + const auto& nose_type_info{RawData::RandomMiiNoseType.at(index)}; + const auto& mouth_type_info{RawData::RandomMiiMouthType.at(index)}; + const auto& glasses_type_info{RawData::RandomMiiGlassType.at(static_cast<std::size_t>(age))}; + + data.faceline_type.Assign( + faceline_type_info + .values[MiiUtil::GetRandomValue<std::size_t>(faceline_type_info.values_count)]); + data.faceline_color.Assign( + faceline_color_info + .values[MiiUtil::GetRandomValue<std::size_t>(faceline_color_info.values_count)]); + data.faceline_wrinkle.Assign( + faceline_wrinkle_info + .values[MiiUtil::GetRandomValue<std::size_t>(faceline_wrinkle_info.values_count)]); + data.faceline_makeup.Assign( + faceline_makeup_info + .values[MiiUtil::GetRandomValue<std::size_t>(faceline_makeup_info.values_count)]); + + data.hair_type.Assign( + hair_type_info.values[MiiUtil::GetRandomValue<std::size_t>(hair_type_info.values_count)]); + SetHairColor(RawData::GetHairColorFromVer3( + hair_color_info + .values[MiiUtil::GetRandomValue<std::size_t>(hair_color_info.values_count)])); + SetHairFlip(MiiUtil::GetRandomValue(HairFlip::Max)); + + data.eye_type.Assign( + eye_type_info.values[MiiUtil::GetRandomValue<std::size_t>(eye_type_info.values_count)]); + + const auto eye_rotate_1{gender != Gender::Male ? 4 : 2}; + const auto eye_rotate_2{gender != Gender::Male ? 3 : 4}; + const auto eye_rotate_offset{32 - RawData::EyeRotateLookup[eye_rotate_1] + eye_rotate_2}; + const auto eye_rotate{32 - RawData::EyeRotateLookup[data.eye_type]}; + + SetEyeColor(RawData::GetEyeColorFromVer3( + eye_color_info.values[MiiUtil::GetRandomValue<std::size_t>(eye_color_info.values_count)])); + SetEyeScale(4); + SetEyeAspect(3); + SetEyeRotate(static_cast<u8>(eye_rotate_offset - eye_rotate)); + SetEyeX(2); + SetEyeY(static_cast<u8>(axis_y + 12)); + + data.eyebrow_type.Assign( + eyebrow_type_info + .values[MiiUtil::GetRandomValue<std::size_t>(eyebrow_type_info.values_count)]); + + const auto eyebrow_rotate_1{race == Race::Asian ? 6 : 0}; + const auto eyebrow_y{race == Race::Asian ? 9 : 10}; + const auto eyebrow_rotate_offset{32 - RawData::EyebrowRotateLookup[eyebrow_rotate_1] + 6}; + const auto eyebrow_rotate{ + 32 - RawData::EyebrowRotateLookup[static_cast<std::size_t>(data.eyebrow_type.Value())]}; + + SetEyebrowColor(GetHairColor()); + SetEyebrowScale(4); + SetEyebrowAspect(3); + SetEyebrowRotate(static_cast<u8>(eyebrow_rotate_offset - eyebrow_rotate)); + SetEyebrowX(2); + SetEyebrowY(static_cast<u8>(axis_y + eyebrow_y)); + + data.nose_type.Assign( + nose_type_info.values[MiiUtil::GetRandomValue<std::size_t>(nose_type_info.values_count)]); + SetNoseScale(gender == Gender::Female ? 3 : 4); + SetNoseY(static_cast<u8>(axis_y + 9)); + + const auto mouth_color{gender == Gender::Female ? MiiUtil::GetRandomValue<int>(4) : 0}; + + data.mouth_type.Assign( + mouth_type_info.values[MiiUtil::GetRandomValue<std::size_t>(mouth_type_info.values_count)]); + SetMouthColor(RawData::GetMouthColorFromVer3(mouth_color)); + SetMouthScale(4); + SetMouthAspect(3); + SetMouthY(static_cast<u8>(axis_y + 13)); + + SetBeardColor(GetHairColor()); + SetMustacheScale(4); + + if (gender == Gender::Male && age != Age::Young && MiiUtil::GetRandomValue<int>(10) < 2) { + const auto mustache_and_beard_flag{MiiUtil::GetRandomValue(BeardAndMustacheFlag::All)}; + + auto beard_type{BeardType::None}; + auto mustache_type{MustacheType::None}; + + if ((mustache_and_beard_flag & BeardAndMustacheFlag::Beard) == + BeardAndMustacheFlag::Beard) { + beard_type = MiiUtil::GetRandomValue(BeardType::Min, BeardType::Max); + } + + if ((mustache_and_beard_flag & BeardAndMustacheFlag::Mustache) == + BeardAndMustacheFlag::Mustache) { + mustache_type = MiiUtil::GetRandomValue(MustacheType::Min, MustacheType::Max); + } + + SetMustacheType(mustache_type); + SetBeardType(beard_type); + SetMustacheY(10); + } else { + SetMustacheType(MustacheType::None); + SetBeardType(BeardType::None); + SetMustacheY(static_cast<u8>(axis_y + 10)); + } + + const auto glasses_type_start{MiiUtil::GetRandomValue<std::size_t>(100)}; + u8 glasses_type{}; + while (glasses_type_start < glasses_type_info.values[glasses_type]) { + if (++glasses_type >= glasses_type_info.values_count) { + glasses_type = 0; + break; + } + } + + SetGlassType(static_cast<GlassType>(glasses_type)); + SetGlassColor(RawData::GetGlassColorFromVer3(0)); + SetGlassScale(4); + SetGlassY(static_cast<u8>(axis_y + 10)); + + SetMoleType(MoleType::None); + SetMoleScale(4); + SetMoleX(2); + SetMoleY(20); +} + +void CoreData::BuildFromCharInfo(const CharInfo& char_info) { + name = char_info.GetNickname(); + SetFontRegion(char_info.GetFontRegion()); + SetFavoriteColor(char_info.GetFavoriteColor()); + SetGender(char_info.GetGender()); + SetHeight(char_info.GetHeight()); + SetBuild(char_info.GetBuild()); + SetType(char_info.GetType()); + SetRegionMove(char_info.GetRegionMove()); + SetFacelineType(char_info.GetFacelineType()); + SetFacelineColor(char_info.GetFacelineColor()); + SetFacelineWrinkle(char_info.GetFacelineWrinkle()); + SetFacelineMake(char_info.GetFacelineMake()); + SetHairType(char_info.GetHairType()); + SetHairColor(char_info.GetHairColor()); + SetHairFlip(char_info.GetHairFlip()); + SetEyeType(char_info.GetEyeType()); + SetEyeColor(char_info.GetEyeColor()); + SetEyeScale(char_info.GetEyeScale()); + SetEyeAspect(char_info.GetEyeAspect()); + SetEyeRotate(char_info.GetEyeRotate()); + SetEyeX(char_info.GetEyeX()); + SetEyeY(char_info.GetEyeY()); + SetEyebrowType(char_info.GetEyebrowType()); + SetEyebrowColor(char_info.GetEyebrowColor()); + SetEyebrowScale(char_info.GetEyebrowScale()); + SetEyebrowAspect(char_info.GetEyebrowAspect()); + SetEyebrowRotate(char_info.GetEyebrowRotate()); + SetEyebrowX(char_info.GetEyebrowX()); + SetEyebrowY(char_info.GetEyebrowY() - 3); + SetNoseType(char_info.GetNoseType()); + SetNoseScale(char_info.GetNoseScale()); + SetNoseY(char_info.GetNoseY()); + SetMouthType(char_info.GetMouthType()); + SetMouthColor(char_info.GetMouthColor()); + SetMouthScale(char_info.GetMouthScale()); + SetMouthAspect(char_info.GetMouthAspect()); + SetMouthY(char_info.GetMouthY()); + SetBeardColor(char_info.GetBeardColor()); + SetBeardType(char_info.GetBeardType()); + SetMustacheType(char_info.GetMustacheType()); + SetMustacheScale(char_info.GetMustacheScale()); + SetMustacheY(char_info.GetMustacheY()); + SetGlassType(char_info.GetGlassType()); + SetGlassColor(char_info.GetGlassColor()); + SetGlassScale(char_info.GetGlassScale()); + SetGlassY(char_info.GetGlassY()); + SetMoleType(char_info.GetMoleType()); + SetMoleScale(char_info.GetMoleScale()); + SetMoleX(char_info.GetMoleX()); + SetMoleY(char_info.GetMoleY()); +} + +ValidationResult CoreData::IsValid() const { + if (!name.IsValid()) { + return ValidationResult::InvalidName; + } + if (GetFontRegion() > FontRegion::Max) { + return ValidationResult::InvalidFont; + } + if (GetFavoriteColor() > FavoriteColor::Max) { + return ValidationResult::InvalidColor; + } + if (GetGender() > Gender::Max) { + return ValidationResult::InvalidGender; + } + if (GetHeight() > MaxHeight) { + return ValidationResult::InvalidHeight; + } + if (GetBuild() > MaxBuild) { + return ValidationResult::InvalidBuild; + } + if (GetType() > MaxType) { + return ValidationResult::InvalidType; + } + if (GetRegionMove() > MaxRegionMove) { + return ValidationResult::InvalidRegionMove; + } + if (GetFacelineType() > FacelineType::Max) { + return ValidationResult::InvalidFacelineType; + } + if (GetFacelineColor() > FacelineColor::Max) { + return ValidationResult::InvalidFacelineColor; + } + if (GetFacelineWrinkle() > FacelineWrinkle::Max) { + return ValidationResult::InvalidFacelineWrinkle; + } + if (GetFacelineMake() > FacelineMake::Max) { + return ValidationResult::InvalidFacelineMake; + } + if (GetHairType() > HairType::Max) { + return ValidationResult::InvalidHairType; + } + if (GetHairColor() > CommonColor::Max) { + return ValidationResult::InvalidHairColor; + } + if (GetHairFlip() > HairFlip::Max) { + return ValidationResult::InvalidHairFlip; + } + if (GetEyeType() > EyeType::Max) { + return ValidationResult::InvalidEyeType; + } + if (GetEyeColor() > CommonColor::Max) { + return ValidationResult::InvalidEyeColor; + } + if (GetEyeScale() > MaxEyeScale) { + return ValidationResult::InvalidEyeScale; + } + if (GetEyeAspect() > MaxEyeAspect) { + return ValidationResult::InvalidEyeAspect; + } + if (GetEyeRotate() > MaxEyeRotate) { + return ValidationResult::InvalidEyeRotate; + } + if (GetEyeX() > MaxEyeX) { + return ValidationResult::InvalidEyeX; + } + if (GetEyeY() > MaxEyeY) { + return ValidationResult::InvalidEyeY; + } + if (GetEyebrowType() > EyebrowType::Max) { + return ValidationResult::InvalidEyebrowType; + } + if (GetEyebrowColor() > CommonColor::Max) { + return ValidationResult::InvalidEyebrowColor; + } + if (GetEyebrowScale() > MaxEyebrowScale) { + return ValidationResult::InvalidEyebrowScale; + } + if (GetEyebrowAspect() > MaxEyebrowAspect) { + return ValidationResult::InvalidEyebrowAspect; + } + if (GetEyebrowRotate() > MaxEyebrowRotate) { + return ValidationResult::InvalidEyebrowRotate; + } + if (GetEyebrowX() > MaxEyebrowX) { + return ValidationResult::InvalidEyebrowX; + } + if (GetEyebrowY() > MaxEyebrowY) { + return ValidationResult::InvalidEyebrowY; + } + if (GetNoseType() > NoseType::Max) { + return ValidationResult::InvalidNoseType; + } + if (GetNoseScale() > MaxNoseScale) { + return ValidationResult::InvalidNoseScale; + } + if (GetNoseY() > MaxNoseY) { + return ValidationResult::InvalidNoseY; + } + if (GetMouthType() > MouthType::Max) { + return ValidationResult::InvalidMouthType; + } + if (GetMouthColor() > CommonColor::Max) { + return ValidationResult::InvalidMouthColor; + } + if (GetMouthScale() > MaxMouthScale) { + return ValidationResult::InvalidMouthScale; + } + if (GetMouthAspect() > MaxMoutAspect) { + return ValidationResult::InvalidMouthAspect; + } + if (GetMouthY() > MaxMouthY) { + return ValidationResult::InvalidMouthY; + } + if (GetBeardColor() > CommonColor::Max) { + return ValidationResult::InvalidBeardColor; + } + if (GetBeardType() > BeardType::Max) { + return ValidationResult::InvalidBeardType; + } + if (GetMustacheType() > MustacheType::Max) { + return ValidationResult::InvalidMustacheType; + } + if (GetMustacheScale() > MaxMustacheScale) { + return ValidationResult::InvalidMustacheScale; + } + if (GetMustacheY() > MaxMustacheY) { + return ValidationResult::InvalidMustacheY; + } + if (GetGlassType() > GlassType::Max) { + return ValidationResult::InvalidGlassType; + } + if (GetGlassColor() > CommonColor::Max) { + return ValidationResult::InvalidGlassColor; + } + if (GetGlassScale() > MaxGlassScale) { + return ValidationResult::InvalidGlassScale; + } + if (GetGlassY() > MaxGlassY) { + return ValidationResult::InvalidGlassY; + } + if (GetMoleType() > MoleType::Max) { + return ValidationResult::InvalidMoleType; + } + if (GetMoleScale() > MaxMoleScale) { + return ValidationResult::InvalidMoleScale; + } + if (GetMoleX() > MaxMoleX) { + return ValidationResult::InvalidMoleX; + } + if (GetMoleY() > MaxMoleY) { + return ValidationResult::InvalidMoleY; + } + return ValidationResult::NoErrors; +} + +void CoreData::SetFontRegion(FontRegion value) { + data.font_region.Assign(static_cast<u32>(value)); +} + +void CoreData::SetFavoriteColor(FavoriteColor value) { + data.favorite_color.Assign(static_cast<u32>(value)); +} + +void CoreData::SetGender(Gender value) { + data.gender.Assign(static_cast<u32>(value)); +} + +void CoreData::SetHeight(u8 value) { + data.height.Assign(value); +} + +void CoreData::SetBuild(u8 value) { + data.build.Assign(value); +} + +void CoreData::SetType(u8 value) { + data.type.Assign(value); +} + +void CoreData::SetRegionMove(u8 value) { + data.region_move.Assign(value); +} + +void CoreData::SetFacelineType(FacelineType value) { + data.faceline_type.Assign(static_cast<u32>(value)); +} + +void CoreData::SetFacelineColor(FacelineColor value) { + data.faceline_color.Assign(static_cast<u32>(value)); +} + +void CoreData::SetFacelineWrinkle(FacelineWrinkle value) { + data.faceline_wrinkle.Assign(static_cast<u32>(value)); +} + +void CoreData::SetFacelineMake(FacelineMake value) { + data.faceline_makeup.Assign(static_cast<u32>(value)); +} + +void CoreData::SetHairType(HairType value) { + data.hair_type.Assign(static_cast<u32>(value)); +} + +void CoreData::SetHairColor(CommonColor value) { + data.hair_color.Assign(static_cast<u32>(value)); +} + +void CoreData::SetHairFlip(HairFlip value) { + data.hair_flip.Assign(static_cast<u32>(value)); +} + +void CoreData::SetEyeType(EyeType value) { + data.eye_type.Assign(static_cast<u32>(value)); +} + +void CoreData::SetEyeColor(CommonColor value) { + data.eye_color.Assign(static_cast<u32>(value)); +} + +void CoreData::SetEyeScale(u8 value) { + data.eye_scale.Assign(value); +} + +void CoreData::SetEyeAspect(u8 value) { + data.eye_aspect.Assign(value); +} + +void CoreData::SetEyeRotate(u8 value) { + data.eye_rotate.Assign(value); +} + +void CoreData::SetEyeX(u8 value) { + data.eye_x.Assign(value); +} + +void CoreData::SetEyeY(u8 value) { + data.eye_y.Assign(value); +} + +void CoreData::SetEyebrowType(EyebrowType value) { + data.eyebrow_type.Assign(static_cast<u32>(value)); +} + +void CoreData::SetEyebrowColor(CommonColor value) { + data.eyebrow_color.Assign(static_cast<u32>(value)); +} + +void CoreData::SetEyebrowScale(u8 value) { + data.eyebrow_scale.Assign(value); +} + +void CoreData::SetEyebrowAspect(u8 value) { + data.eyebrow_aspect.Assign(value); +} + +void CoreData::SetEyebrowRotate(u8 value) { + data.eyebrow_rotate.Assign(value); +} + +void CoreData::SetEyebrowX(u8 value) { + data.eyebrow_x.Assign(value); +} + +void CoreData::SetEyebrowY(u8 value) { + data.eyebrow_y.Assign(value); +} + +void CoreData::SetNoseType(NoseType value) { + data.nose_type.Assign(static_cast<u32>(value)); +} + +void CoreData::SetNoseScale(u8 value) { + data.nose_scale.Assign(value); +} + +void CoreData::SetNoseY(u8 value) { + data.nose_y.Assign(value); +} + +void CoreData::SetMouthType(MouthType value) { + data.mouth_type.Assign(static_cast<u32>(value)); +} + +void CoreData::SetMouthColor(CommonColor value) { + data.mouth_color.Assign(static_cast<u32>(value)); +} + +void CoreData::SetMouthScale(u8 value) { + data.mouth_scale.Assign(value); +} + +void CoreData::SetMouthAspect(u8 value) { + data.mouth_aspect.Assign(value); +} + +void CoreData::SetMouthY(u8 value) { + data.mouth_y.Assign(value); +} + +void CoreData::SetBeardColor(CommonColor value) { + data.beard_color.Assign(static_cast<u32>(value)); +} + +void CoreData::SetBeardType(BeardType value) { + data.beard_type.Assign(static_cast<u32>(value)); +} + +void CoreData::SetMustacheType(MustacheType value) { + data.mustache_type.Assign(static_cast<u32>(value)); +} + +void CoreData::SetMustacheScale(u8 value) { + data.mustache_scale.Assign(value); +} + +void CoreData::SetMustacheY(u8 value) { + data.mustache_y.Assign(value); +} + +void CoreData::SetGlassType(GlassType value) { + data.glasses_type.Assign(static_cast<u32>(value)); +} + +void CoreData::SetGlassColor(CommonColor value) { + data.glasses_color.Assign(static_cast<u32>(value)); +} + +void CoreData::SetGlassScale(u8 value) { + data.glasses_scale.Assign(value); +} + +void CoreData::SetGlassY(u8 value) { + data.glasses_y.Assign(value); +} + +void CoreData::SetMoleType(MoleType value) { + data.mole_type.Assign(static_cast<u32>(value)); +} + +void CoreData::SetMoleScale(u8 value) { + data.mole_scale.Assign(value); +} + +void CoreData::SetMoleX(u8 value) { + data.mole_x.Assign(value); +} + +void CoreData::SetMoleY(u8 value) { + data.mole_y.Assign(value); +} + +void CoreData::SetNickname(Nickname nickname) { + name = nickname; +} + +FontRegion CoreData::GetFontRegion() const { + return static_cast<FontRegion>(data.font_region.Value()); +} + +FavoriteColor CoreData::GetFavoriteColor() const { + return static_cast<FavoriteColor>(data.favorite_color.Value()); +} + +Gender CoreData::GetGender() const { + return static_cast<Gender>(data.gender.Value()); +} + +u8 CoreData::GetHeight() const { + return static_cast<u8>(data.height.Value()); +} + +u8 CoreData::GetBuild() const { + return static_cast<u8>(data.build.Value()); +} + +u8 CoreData::GetType() const { + return static_cast<u8>(data.type.Value()); +} + +u8 CoreData::GetRegionMove() const { + return static_cast<u8>(data.region_move.Value()); +} + +FacelineType CoreData::GetFacelineType() const { + return static_cast<FacelineType>(data.faceline_type.Value()); +} + +FacelineColor CoreData::GetFacelineColor() const { + return static_cast<FacelineColor>(data.faceline_color.Value()); +} + +FacelineWrinkle CoreData::GetFacelineWrinkle() const { + return static_cast<FacelineWrinkle>(data.faceline_wrinkle.Value()); +} + +FacelineMake CoreData::GetFacelineMake() const { + return static_cast<FacelineMake>(data.faceline_makeup.Value()); +} + +HairType CoreData::GetHairType() const { + return static_cast<HairType>(data.hair_type.Value()); +} + +CommonColor CoreData::GetHairColor() const { + return static_cast<CommonColor>(data.hair_color.Value()); +} + +HairFlip CoreData::GetHairFlip() const { + return static_cast<HairFlip>(data.hair_flip.Value()); +} + +EyeType CoreData::GetEyeType() const { + return static_cast<EyeType>(data.eye_type.Value()); +} + +CommonColor CoreData::GetEyeColor() const { + return static_cast<CommonColor>(data.eye_color.Value()); +} + +u8 CoreData::GetEyeScale() const { + return static_cast<u8>(data.eye_scale.Value()); +} + +u8 CoreData::GetEyeAspect() const { + return static_cast<u8>(data.eye_aspect.Value()); +} + +u8 CoreData::GetEyeRotate() const { + return static_cast<u8>(data.eye_rotate.Value()); +} + +u8 CoreData::GetEyeX() const { + return static_cast<u8>(data.eye_x.Value()); +} + +u8 CoreData::GetEyeY() const { + return static_cast<u8>(data.eye_y.Value()); +} + +EyebrowType CoreData::GetEyebrowType() const { + return static_cast<EyebrowType>(data.eyebrow_type.Value()); +} + +CommonColor CoreData::GetEyebrowColor() const { + return static_cast<CommonColor>(data.eyebrow_color.Value()); +} + +u8 CoreData::GetEyebrowScale() const { + return static_cast<u8>(data.eyebrow_scale.Value()); +} + +u8 CoreData::GetEyebrowAspect() const { + return static_cast<u8>(data.eyebrow_aspect.Value()); +} + +u8 CoreData::GetEyebrowRotate() const { + return static_cast<u8>(data.eyebrow_rotate.Value()); +} + +u8 CoreData::GetEyebrowX() const { + return static_cast<u8>(data.eyebrow_x.Value()); +} + +u8 CoreData::GetEyebrowY() const { + return static_cast<u8>(data.eyebrow_y.Value()); +} + +NoseType CoreData::GetNoseType() const { + return static_cast<NoseType>(data.nose_type.Value()); +} + +u8 CoreData::GetNoseScale() const { + return static_cast<u8>(data.nose_scale.Value()); +} + +u8 CoreData::GetNoseY() const { + return static_cast<u8>(data.nose_y.Value()); +} + +MouthType CoreData::GetMouthType() const { + return static_cast<MouthType>(data.mouth_type.Value()); +} + +CommonColor CoreData::GetMouthColor() const { + return static_cast<CommonColor>(data.mouth_color.Value()); +} + +u8 CoreData::GetMouthScale() const { + return static_cast<u8>(data.mouth_scale.Value()); +} + +u8 CoreData::GetMouthAspect() const { + return static_cast<u8>(data.mouth_aspect.Value()); +} + +u8 CoreData::GetMouthY() const { + return static_cast<u8>(data.mouth_y.Value()); +} + +CommonColor CoreData::GetBeardColor() const { + return static_cast<CommonColor>(data.beard_color.Value()); +} + +BeardType CoreData::GetBeardType() const { + return static_cast<BeardType>(data.beard_type.Value()); +} + +MustacheType CoreData::GetMustacheType() const { + return static_cast<MustacheType>(data.mustache_type.Value()); +} + +u8 CoreData::GetMustacheScale() const { + return static_cast<u8>(data.mustache_scale.Value()); +} + +u8 CoreData::GetMustacheY() const { + return static_cast<u8>(data.mustache_y.Value()); +} + +GlassType CoreData::GetGlassType() const { + return static_cast<GlassType>(data.glasses_type.Value()); +} + +CommonColor CoreData::GetGlassColor() const { + return static_cast<CommonColor>(data.glasses_color.Value()); +} + +u8 CoreData::GetGlassScale() const { + return static_cast<u8>(data.glasses_scale.Value()); +} + +u8 CoreData::GetGlassY() const { + return static_cast<u8>(data.glasses_y.Value()); +} + +MoleType CoreData::GetMoleType() const { + return static_cast<MoleType>(data.mole_type.Value()); +} + +u8 CoreData::GetMoleScale() const { + return static_cast<u8>(data.mole_scale.Value()); +} + +u8 CoreData::GetMoleX() const { + return static_cast<u8>(data.mole_x.Value()); +} + +u8 CoreData::GetMoleY() const { + return static_cast<u8>(data.mole_y.Value()); +} + +Nickname CoreData::GetNickname() const { + return name; +} + +Nickname CoreData::GetDefaultNickname() const { + return {u'n', u'o', u' ', u'n', u'a', u'm', u'e'}; +} + +Nickname CoreData::GetInvalidNickname() const { + return {u'?', u'?', u'?'}; +} + +} // namespace Service::Mii diff --git a/src/core/hle/service/mii/types/core_data.h b/src/core/hle/service/mii/types/core_data.h new file mode 100644 index 000000000..8897e4f3b --- /dev/null +++ b/src/core/hle/service/mii/types/core_data.h @@ -0,0 +1,219 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/hle/service/mii/mii_types.h" + +namespace Service::Mii { +class CharInfo; + +struct StoreDataBitFields { + union { + u32 word_0{}; + + BitField<0, 8, u32> hair_type; + BitField<8, 7, u32> height; + BitField<15, 1, u32> mole_type; + BitField<16, 7, u32> build; + BitField<23, 1, u32> hair_flip; + BitField<24, 7, u32> hair_color; + BitField<31, 1, u32> type; + }; + + union { + u32 word_1{}; + + BitField<0, 7, u32> eye_color; + BitField<7, 1, u32> gender; + BitField<8, 7, u32> eyebrow_color; + BitField<16, 7, u32> mouth_color; + BitField<24, 7, u32> beard_color; + }; + + union { + u32 word_2{}; + + BitField<0, 7, u32> glasses_color; + BitField<8, 6, u32> eye_type; + BitField<14, 2, u32> region_move; + BitField<16, 6, u32> mouth_type; + BitField<22, 2, u32> font_region; + BitField<24, 5, u32> eye_y; + BitField<29, 3, u32> glasses_scale; + }; + + union { + u32 word_3{}; + + BitField<0, 5, u32> eyebrow_type; + BitField<5, 3, u32> mustache_type; + BitField<8, 5, u32> nose_type; + BitField<13, 3, u32> beard_type; + BitField<16, 5, u32> nose_y; + BitField<21, 3, u32> mouth_aspect; + BitField<24, 5, u32> mouth_y; + BitField<29, 3, u32> eyebrow_aspect; + }; + + union { + u32 word_4{}; + + BitField<0, 5, u32> mustache_y; + BitField<5, 3, u32> eye_rotate; + BitField<8, 5, u32> glasses_y; + BitField<13, 3, u32> eye_aspect; + BitField<16, 5, u32> mole_x; + BitField<21, 3, u32> eye_scale; + BitField<24, 5, u32> mole_y; + }; + + union { + u32 word_5{}; + + BitField<0, 5, u32> glasses_type; + BitField<8, 4, u32> favorite_color; + BitField<12, 4, u32> faceline_type; + BitField<16, 4, u32> faceline_color; + BitField<20, 4, u32> faceline_wrinkle; + BitField<24, 4, u32> faceline_makeup; + BitField<28, 4, u32> eye_x; + }; + + union { + u32 word_6{}; + + BitField<0, 4, u32> eyebrow_scale; + BitField<4, 4, u32> eyebrow_rotate; + BitField<8, 4, u32> eyebrow_x; + BitField<12, 4, u32> eyebrow_y; + BitField<16, 4, u32> nose_scale; + BitField<20, 4, u32> mouth_scale; + BitField<24, 4, u32> mustache_scale; + BitField<28, 4, u32> mole_scale; + }; +}; +static_assert(sizeof(StoreDataBitFields) == 0x1c, "StoreDataBitFields has incorrect size."); +static_assert(std::is_trivially_copyable_v<StoreDataBitFields>, + "StoreDataBitFields is not trivially copyable."); + +class CoreData { +public: + void SetDefault(); + void BuildRandom(Age age, Gender gender, Race race); + void BuildFromCharInfo(const CharInfo& char_info); + + ValidationResult IsValid() const; + + void SetFontRegion(FontRegion value); + void SetFavoriteColor(FavoriteColor value); + void SetGender(Gender value); + void SetHeight(u8 value); + void SetBuild(u8 value); + void SetType(u8 value); + void SetRegionMove(u8 value); + void SetFacelineType(FacelineType value); + void SetFacelineColor(FacelineColor value); + void SetFacelineWrinkle(FacelineWrinkle value); + void SetFacelineMake(FacelineMake value); + void SetHairType(HairType value); + void SetHairColor(CommonColor value); + void SetHairFlip(HairFlip value); + void SetEyeType(EyeType value); + void SetEyeColor(CommonColor value); + void SetEyeScale(u8 value); + void SetEyeAspect(u8 value); + void SetEyeRotate(u8 value); + void SetEyeX(u8 value); + void SetEyeY(u8 value); + void SetEyebrowType(EyebrowType value); + void SetEyebrowColor(CommonColor value); + void SetEyebrowScale(u8 value); + void SetEyebrowAspect(u8 value); + void SetEyebrowRotate(u8 value); + void SetEyebrowX(u8 value); + void SetEyebrowY(u8 value); + void SetNoseType(NoseType value); + void SetNoseScale(u8 value); + void SetNoseY(u8 value); + void SetMouthType(MouthType value); + void SetMouthColor(CommonColor value); + void SetMouthScale(u8 value); + void SetMouthAspect(u8 value); + void SetMouthY(u8 value); + void SetBeardColor(CommonColor value); + void SetBeardType(BeardType value); + void SetMustacheType(MustacheType value); + void SetMustacheScale(u8 value); + void SetMustacheY(u8 value); + void SetGlassType(GlassType value); + void SetGlassColor(CommonColor value); + void SetGlassScale(u8 value); + void SetGlassY(u8 value); + void SetMoleType(MoleType value); + void SetMoleScale(u8 value); + void SetMoleX(u8 value); + void SetMoleY(u8 value); + void SetNickname(Nickname nickname); + + FontRegion GetFontRegion() const; + FavoriteColor GetFavoriteColor() const; + Gender GetGender() const; + u8 GetHeight() const; + u8 GetBuild() const; + u8 GetType() const; + u8 GetRegionMove() const; + FacelineType GetFacelineType() const; + FacelineColor GetFacelineColor() const; + FacelineWrinkle GetFacelineWrinkle() const; + FacelineMake GetFacelineMake() const; + HairType GetHairType() const; + CommonColor GetHairColor() const; + HairFlip GetHairFlip() const; + EyeType GetEyeType() const; + CommonColor GetEyeColor() const; + u8 GetEyeScale() const; + u8 GetEyeAspect() const; + u8 GetEyeRotate() const; + u8 GetEyeX() const; + u8 GetEyeY() const; + EyebrowType GetEyebrowType() const; + CommonColor GetEyebrowColor() const; + u8 GetEyebrowScale() const; + u8 GetEyebrowAspect() const; + u8 GetEyebrowRotate() const; + u8 GetEyebrowX() const; + u8 GetEyebrowY() const; + NoseType GetNoseType() const; + u8 GetNoseScale() const; + u8 GetNoseY() const; + MouthType GetMouthType() const; + CommonColor GetMouthColor() const; + u8 GetMouthScale() const; + u8 GetMouthAspect() const; + u8 GetMouthY() const; + CommonColor GetBeardColor() const; + BeardType GetBeardType() const; + MustacheType GetMustacheType() const; + u8 GetMustacheScale() const; + u8 GetMustacheY() const; + GlassType GetGlassType() const; + CommonColor GetGlassColor() const; + u8 GetGlassScale() const; + u8 GetGlassY() const; + MoleType GetMoleType() const; + u8 GetMoleScale() const; + u8 GetMoleX() const; + u8 GetMoleY() const; + Nickname GetNickname() const; + Nickname GetDefaultNickname() const; + Nickname GetInvalidNickname() const; + +private: + StoreDataBitFields data{}; + Nickname name{}; +}; +static_assert(sizeof(CoreData) == 0x30, "CoreData has incorrect size."); +static_assert(std::is_trivially_copyable_v<CoreData>, "CoreData type must be trivially copyable."); + +}; // namespace Service::Mii diff --git a/src/core/hle/service/mii/raw_data.cpp b/src/core/hle/service/mii/types/raw_data.cpp index 1442280c8..0e1a07fd7 100644 --- a/src/core/hle/service/mii/raw_data.cpp +++ b/src/core/hle/service/mii/types/raw_data.cpp @@ -1,11 +1,88 @@ // SPDX-FileCopyrightText: Ryujinx Team and Contributors // SPDX-License-Identifier: MIT -#include "core/hle/service/mii/raw_data.h" +#include "core/hle/service/mii/types/raw_data.h" namespace Service::Mii::RawData { -const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ +constexpr std::array<u8, static_cast<u8>(FacelineColor::Count)> FromVer3FacelineColorTable{ + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x0, 0x1, 0x5, 0x5, +}; + +constexpr std::array<u8, static_cast<u8>(CommonColor::Count)> FromVer3HairColorTable{ + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x4, 0x3, 0x5, 0x4, 0x4, 0x6, 0x2, 0x0, + 0x6, 0x4, 0x3, 0x2, 0x2, 0x7, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x4, + 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5, + 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x7, 0x5, 0x7, 0x7, 0x7, 0x7, 0x7, 0x6, 0x7, + 0x7, 0x7, 0x7, 0x7, 0x3, 0x7, 0x7, 0x7, 0x7, 0x7, 0x0, 0x4, 0x4, 0x4, 0x4, +}; + +constexpr std::array<u8, static_cast<u8>(CommonColor::Count)> FromVer3EyeColorTable{ + 0x0, 0x2, 0x2, 0x2, 0x1, 0x3, 0x2, 0x3, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x2, 0x2, 0x4, + 0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x0, 0x4, 0x4, + 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, + 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x2, 0x2, + 0x3, 0x3, 0x3, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, +}; + +constexpr std::array<u8, static_cast<u8>(CommonColor::Count)> FromVer3MouthlineColorTable{ + 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x4, + 0x4, 0x4, 0x0, 0x1, 0x2, 0x3, 0x4, 0x4, 0x2, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x1, 0x4, + 0x4, 0x2, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, + 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, + 0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, + 0x3, 0x3, 0x3, 0x3, 0x4, 0x0, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, 0x3, 0x3, +}; + +constexpr std::array<u8, static_cast<u8>(CommonColor::Count)> FromVer3GlassColorTable{ + 0x0, 0x1, 0x1, 0x1, 0x5, 0x1, 0x1, 0x4, 0x0, 0x5, 0x1, 0x1, 0x3, 0x5, 0x1, 0x2, 0x3, + 0x4, 0x5, 0x4, 0x2, 0x2, 0x4, 0x4, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x2, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, + 0x3, 0x3, 0x3, 0x3, 0x3, 0x0, 0x0, 0x0, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x0, 0x5, 0x5, + 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x1, 0x4, + 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, +}; + +constexpr std::array<u8, static_cast<u8>(GlassType::Count)> FromVer3GlassTypeTable{ + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x1, + 0x2, 0x1, 0x3, 0x7, 0x7, 0x6, 0x7, 0x8, 0x7, 0x7, +}; + +constexpr std::array<u8, 8> Ver3FacelineColorTable{ + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, +}; + +constexpr std::array<u8, 8> Ver3HairColorTable{ + 0x8, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, +}; + +constexpr std::array<u8, 6> Ver3EyeColorTable{ + 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, +}; + +constexpr std::array<u8, 5> Ver3MouthColorTable{ + 0x13, 0x14, 0x15, 0x16, 0x17, +}; + +constexpr std::array<u8, 7> Ver3GlassColorTable{ + 0x8, 0xe, 0xf, 0x10, 0x11, 0x12, 0x0, +}; + +const std::array<u8, 62> EyeRotateLookup{ + 0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x04, + 0x04, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04, + 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04, +}; + +const std::array<u8, 24> EyebrowRotateLookup{ + 0x06, 0x06, 0x05, 0x07, 0x06, 0x07, 0x06, 0x07, 0x04, 0x07, 0x06, 0x08, + 0x05, 0x05, 0x06, 0x06, 0x07, 0x07, 0x06, 0x06, 0x05, 0x06, 0x07, 0x05, +}; + +const std::array<Service::Mii::DefaultMii, 2> BaseMii{ Service::Mii::DefaultMii{ .face_type = 0, .face_color = 0, @@ -51,11 +128,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ .mole_y = 20, .height = 64, .weight = 64, - .gender = Gender::Male, + .gender = 0, .favorite_color = 0, - .region = 0, - .font_region = FontRegion::Standard, + .region_move = 0, + .font_region = 0, .type = 0, + .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'}, }, Service::Mii::DefaultMii{ .face_type = 0, @@ -102,12 +180,16 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ .mole_y = 20, .height = 64, .weight = 64, - .gender = Gender::Female, + .gender = 1, .favorite_color = 0, - .region = 0, - .font_region = FontRegion::Standard, + .region_move = 0, + .font_region = 0, .type = 0, + .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'}, }, +}; + +const std::array<Service::Mii::DefaultMii, 6> DefaultMii{ Service::Mii::DefaultMii{ .face_type = 0, .face_color = 4, @@ -153,11 +235,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ .mole_y = 20, .height = 64, .weight = 64, - .gender = Gender::Male, + .gender = 0, .favorite_color = 4, - .region = 0, - .font_region = FontRegion::Standard, + .region_move = 0, + .font_region = 0, .type = 0, + .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'}, }, Service::Mii::DefaultMii{ .face_type = 0, @@ -204,11 +287,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ .mole_y = 20, .height = 64, .weight = 64, - .gender = Gender::Male, + .gender = 0, .favorite_color = 5, - .region = 0, - .font_region = FontRegion::Standard, + .region_move = 0, + .font_region = 0, .type = 0, + .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'}, }, Service::Mii::DefaultMii{ .face_type = 0, @@ -255,11 +339,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ .mole_y = 20, .height = 64, .weight = 64, - .gender = Gender::Male, + .gender = 0, .favorite_color = 0, - .region = 0, - .font_region = FontRegion::Standard, + .region_move = 0, + .font_region = 0, .type = 0, + .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'}, }, Service::Mii::DefaultMii{ .face_type = 0, @@ -306,11 +391,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ .mole_y = 20, .height = 64, .weight = 64, - .gender = Gender::Female, + .gender = 1, .favorite_color = 2, - .region = 0, - .font_region = FontRegion::Standard, + .region_move = 0, + .font_region = 0, .type = 0, + .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'}, }, Service::Mii::DefaultMii{ .face_type = 0, @@ -357,11 +443,12 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ .mole_y = 20, .height = 64, .weight = 64, - .gender = Gender::Female, + .gender = 1, .favorite_color = 6, - .region = 0, - .font_region = FontRegion::Standard, + .region_move = 0, + .font_region = 0, .type = 0, + .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'}, }, Service::Mii::DefaultMii{ .face_type = 0, @@ -408,176 +495,177 @@ const std::array<Service::Mii::DefaultMii, 8> DefaultMii{ .mole_y = 20, .height = 64, .weight = 64, - .gender = Gender::Female, + .gender = 1, .favorite_color = 7, - .region = 0, - .font_region = FontRegion::Standard, + .region_move = 0, + .font_region = 0, .type = 0, + .nickname = {u'n', u'o', u' ', u'n', u'a', u'm', u'e'}, }, }; -const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFaceline{ - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::Black, +const std::array<RandomMiiData4, 18> RandomMiiFaceline{ + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Black), .values_count = 10, .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Black), .values_count = 10, .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Black), .values_count = 10, .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::White), .values_count = 12, .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::White), .values_count = 13, .values = {0, 1, 2, 2, 3, 4, 5, 6, 6, 7, 7, 10, 11}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::White), .values_count = 12, .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Asian), .values_count = 12, .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Asian), .values_count = 13, .values = {0, 1, 2, 2, 3, 4, 5, 6, 6, 7, 7, 10, 11}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Asian), .values_count = 12, .values = {0, 0, 1, 2, 2, 3, 4, 5, 6, 7, 10, 11}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Black), .values_count = 10, .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Black), .values_count = 10, .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Black), .values_count = 10, .values = {0, 0, 1, 1, 2, 3, 4, 5, 9, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::White), .values_count = 12, .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::White), .values_count = 12, .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::White), .values_count = 12, .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Asian), .values_count = 12, .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Asian), .values_count = 12, .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Asian), .values_count = 12, .values = {0, 0, 0, 1, 1, 1, 2, 3, 4, 5, 8, 10}, }, }; -const std::array<Service::Mii::RandomMiiData3, 6> RandomMiiFacelineColor{ - Service::Mii::RandomMiiData3{ +const std::array<RandomMiiData3, 6> RandomMiiFacelineColor{ + RandomMiiData3{ .arg_1 = 0, .arg_2 = 0, .values_count = 10, .values = {2, 2, 4, 4, 4, 4, 5, 5, 5, 5}, }, - Service::Mii::RandomMiiData3{ + RandomMiiData3{ .arg_1 = 0, .arg_2 = 1, .values_count = 10, .values = {0, 0, 0, 0, 1, 1, 2, 3, 3, 3}, }, - Service::Mii::RandomMiiData3{ + RandomMiiData3{ .arg_1 = 0, .arg_2 = 2, .values_count = 10, .values = {0, 0, 1, 1, 1, 1, 1, 1, 1, 2}, }, - Service::Mii::RandomMiiData3{ + RandomMiiData3{ .arg_1 = 1, .arg_2 = 0, .values_count = 10, .values = {2, 2, 4, 4, 4, 4, 5, 5, 5, 5}, }, - Service::Mii::RandomMiiData3{ + RandomMiiData3{ .arg_1 = 1, .arg_2 = 1, .values_count = 10, .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 3}, }, - Service::Mii::RandomMiiData3{ + RandomMiiData3{ .arg_1 = 1, .arg_2 = 2, .values_count = 10, @@ -585,407 +673,407 @@ const std::array<Service::Mii::RandomMiiData3, 6> RandomMiiFacelineColor{ }, }; -const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineWrinkle{ - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::Black, +const std::array<RandomMiiData4, 18> RandomMiiFacelineWrinkle{ + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Black), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Black), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Black), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::White), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::White), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::White), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Asian), .values_count = 20, .values = {9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Asian), .values_count = 20, .values = {9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Asian), .values_count = 20, .values = {9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Black), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Black), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Black), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::White), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 4, 8, 8}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::White), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 4, 8, 8}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::White), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 4, 4}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Asian), .values_count = 20, .values = {9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Asian), .values_count = 20, .values = {9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Asian), .values_count = 20, .values = {9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11}, }, }; -const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineMakeup{ - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::Black, +const std::array<RandomMiiData4, 18> RandomMiiFacelineMakeup{ + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Black), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Black), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Black), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::White), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::White), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::White), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Asian), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Asian), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Asian), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Black), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Black), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 9, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Black), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 9, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::White), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 3, 4, 5, 5, 6, 7, 8, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::White), .values_count = 20, .values = {0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::White), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 6, 7, 8, 9, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Asian), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Asian), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Asian), .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, }, }; -const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiHairType{ - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::Black, +const std::array<RandomMiiData4, 18> RandomMiiHairType{ + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Black), .values_count = 30, .values = {13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 43, 44, 45, 47, 48, 49, 50, 51, 52, 54, 56, 57, 64, 66, 75, 76, 86, 89}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Black), .values_count = 31, .values = {13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 43, 44, 45, 47, 48, 49, 50, 51, 52, 54, 56, 57, 64, 66, 73, 75, 81, 86, 87}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Black), .values_count = 31, .values = {13, 23, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 43, 44, 45, 47, 48, 49, 50, 51, 52, 54, 56, 57, 64, 66, 73, 75, 81, 86, 87}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::White), .values_count = 38, .values = {13, 23, 30, 31, 32, 33, 34, 36, 37, 38, 40, 42, 43, 44, 45, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 67, 68, 70, 75, 76, 86, 89}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::White), .values_count = 39, .values = {13, 23, 30, 31, 32, 33, 34, 36, 37, 38, 39, 40, 43, 44, 45, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 67, 68, 70, 73, 75, 81, 86, 87}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::White), .values_count = 39, .values = {13, 23, 30, 31, 32, 33, 34, 36, 37, 38, 39, 40, 43, 44, 45, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 58, 59, 60, 64, 65, 66, 67, 68, 70, 73, 75, 81, 86, 87}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Asian), .values_count = 18, .values = {13, 23, 30, 36, 37, 41, 45, 47, 51, 53, 54, 55, 58, 59, 65, 67, 86, 88}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Asian), .values_count = 19, .values = {13, 23, 30, 36, 37, 39, 41, 45, 47, 51, 53, 54, 55, 58, 59, 65, 67, 86, 88}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Asian), .values_count = 19, .values = {13, 23, 30, 36, 37, 39, 41, 45, 47, 51, 53, 54, 55, 58, 59, 65, 67, 86, 88}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Black), .values_count = 39, .values = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 28, 46, 50, 61, 62, 63, 64, 69, 76, 77, 79, 80, 83, 85}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Black), .values_count = 42, .values = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 28, 46, 50, 61, 62, 63, 64, 69, 72, 74, 77, 78, 82, 83, 84, 85, 87}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Black), .values_count = 42, .values = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 28, 46, 50, 61, 62, 63, 64, 69, 72, 74, 77, 78, 82, 83, 84, 85, 87}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::White), .values_count = 44, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 42, 50, 58, 60, 62, 63, 64, 69, 71, 76, 79, 80, 81, 82, 83, 86}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::White), .values_count = 44, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 50, 58, 60, 62, 63, 64, 69, 71, 72, 74, 79, 81, 82, 83, 84, 85}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::White), .values_count = 44, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 50, 58, 60, 62, 63, 64, 69, 71, 72, 74, 79, 81, 82, 83, 84, 85}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Asian), .values_count = 24, .values = {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, 16, 17, 18, 20, 21, 24, 25, 58, 62, 69, 76, 83}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Asian), .values_count = 27, .values = {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, 16, 17, 18, 20, 21, 24, 25, 58, 62, 69, 74, 76, 81, 83, 85}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Asian), .values_count = 27, .values = {0, 1, 2, 3, 4, 5, 6, 10, 11, 12, 13, 14, 16, 17, 18, 20, 21, 24, 25, 58, 62, 69, 74, 76, 81, 83, 85}, @@ -993,55 +1081,55 @@ const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiHairType{ }; const std::array<RandomMiiData3, 9> RandomMiiHairColor{ - Service::Mii::RandomMiiData3{ + RandomMiiData3{ .arg_1 = 0, .arg_2 = 0, .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, }, - Service::Mii::RandomMiiData3{ + RandomMiiData3{ .arg_1 = 0, .arg_2 = 1, .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, }, - Service::Mii::RandomMiiData3{ + RandomMiiData3{ .arg_1 = 0, .arg_2 = 2, .values_count = 20, .values = {0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, }, - Service::Mii::RandomMiiData3{ + RandomMiiData3{ .arg_1 = 1, .arg_2 = 0, .values_count = 20, .values = {2, 3, 3, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7}, }, - Service::Mii::RandomMiiData3{ + RandomMiiData3{ .arg_1 = 1, .arg_2 = 1, .values_count = 20, .values = {2, 3, 3, 3, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7}, }, - Service::Mii::RandomMiiData3{ + RandomMiiData3{ .arg_1 = 1, .arg_2 = 2, .values_count = 20, .values = {2, 3, 3, 4, 4, 4, 4, 4, 4, 5, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7}, }, - Service::Mii::RandomMiiData3{ + RandomMiiData3{ .arg_1 = 2, .arg_2 = 0, .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1}, }, - Service::Mii::RandomMiiData3{ + RandomMiiData3{ .arg_1 = 2, .arg_2 = 1, .values_count = 20, .values = {0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 3, 3, 3, 3}, }, - Service::Mii::RandomMiiData3{ + RandomMiiData3{ .arg_1 = 2, .arg_2 = 2, .values_count = 20, @@ -1049,598 +1137,642 @@ const std::array<RandomMiiData3, 9> RandomMiiHairColor{ }, }; -const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyeType{ - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::Black, +const std::array<RandomMiiData4, 18> RandomMiiEyeType{ + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Black), .values_count = 26, .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 27, 29, 32, 34, 36, 38, 39, 41, 43, 47, 49, 51, 53, 57}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Black), .values_count = 26, .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 27, 29, 32, 34, 36, 38, 39, 41, 43, 47, 49, 51, 53, 57}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Black), .values_count = 27, .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 15, 16, 18, 26, 27, 29, 32, 34, 36, 38, 39, 41, 43, 47, 48, 49, 53, 57}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::White), .values_count = 35, .values = {2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 17, 18, 21, 22, 27, 29, 31, 32, 34, 36, 37, 38, 39, 41, 43, 44, 47, 49, 51, 53, 55, 56, 57}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::White), .values_count = 35, .values = {2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 17, 18, 21, 22, 27, 29, 31, 32, 34, 36, 37, 38, 39, 41, 43, 44, 47, 49, 51, 53, 55, 56, 57}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::White), .values_count = 35, .values = {2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 15, 16, 18, 21, 22, 26, 27, 29, 31, 32, 34, 36, 37, 38, 39, 41, 43, 44, 47, 48, 49, 50, 53, 56, 57}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Asian), .values_count = 30, .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 21, 22, 31, 32, 34, 36, 37, 39, 41, 44, 49, 51, 53, 55, 56, 57}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Asian), .values_count = 30, .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 21, 22, 31, 32, 34, 36, 37, 39, 41, 44, 49, 51, 53, 55, 56, 57}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Asian), .values_count = 30, .values = {2, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 18, 21, 22, 26, 31, 32, 34, 36, 37, 39, 41, 44, 48, 49, 50, 51, 53, 57}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Black), .values_count = 39, .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 59}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Black), .values_count = 39, .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 59}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Black), .values_count = 40, .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 26, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 59}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::White), .values_count = 46, .values = {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 23, 24, 25, 27, 28, 29, 30, 32, 33, 34, 35, 37, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 58, 59}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::White), .values_count = 46, .values = {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 21, 23, 24, 25, 27, 28, 29, 30, 32, 33, 34, 35, 37, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 58, 59}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::White), .values_count = 46, .values = {0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 20, 21, 23, 24, 25, 26, 27, 28, 29, 30, 32, 33, 34, 35, 37, 38, 39, 40, 41, 42, 45, 46, 47, 48, 53, 54, 57, 58, 59}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Asian), .values_count = 34, .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Asian), .values_count = 34, .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Asian), .values_count = 35, .values = {0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 15, 16, 18, 19, 23, 24, 25, 26, 27, 28, 29, 32, 33, 34, 35, 38, 39, 40, 41, 42, 45, 46, 47}, }, }; -const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiEyeColor{ - Service::Mii::RandomMiiData2{ +const std::array<RandomMiiData2, 3> RandomMiiEyeColor{ + RandomMiiData2{ .arg_1 = 0, .values_count = 10, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, }, - Service::Mii::RandomMiiData2{ + RandomMiiData2{ .arg_1 = 1, .values_count = 10, .values = {0, 1, 1, 2, 3, 3, 4, 4, 4, 5}, }, - Service::Mii::RandomMiiData2{ + RandomMiiData2{ .arg_1 = 2, .values_count = 10, .values = {0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, }, }; -const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyebrowType{ - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::Black, +const std::array<RandomMiiData4, 18> RandomMiiEyebrowType{ + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Black), .values_count = 18, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Black), .values_count = 18, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Black), .values_count = 18, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17, 18, 20}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::White), .values_count = 23, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::White), .values_count = 23, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::White), .values_count = 23, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Asian), .values_count = 21, .values = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Asian), .values_count = 21, .values = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Asian), .values_count = 21, .values = {0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Black), .values_count = 9, .values = {0, 1, 3, 7, 8, 9, 10, 11, 13}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Black), .values_count = 9, .values = {0, 1, 3, 7, 8, 9, 10, 11, 13}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Black), .values_count = 9, .values = {0, 1, 3, 7, 8, 9, 10, 11, 13}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::White), .values_count = 11, .values = {0, 1, 3, 7, 8, 9, 10, 11, 13, 15, 19}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::White), .values_count = 11, .values = {0, 1, 3, 7, 8, 9, 10, 11, 13, 15, 19}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::White), .values_count = 11, .values = {0, 1, 3, 7, 8, 9, 10, 11, 13, 15, 19}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Asian), .values_count = 9, .values = {0, 3, 7, 8, 9, 10, 11, 13, 15}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Asian), .values_count = 9, .values = {0, 3, 7, 8, 9, 10, 11, 13, 15}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Asian), .values_count = 9, .values = {0, 3, 7, 8, 9, 10, 11, 13, 15}, }, }; -const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiNoseType{ - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::Black, +const std::array<RandomMiiData4, 18> RandomMiiNoseType{ + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Black), .values_count = 11, .values = {0, 1, 2, 3, 4, 5, 7, 8, 10, 13, 14}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Black), .values_count = 11, .values = {0, 1, 2, 3, 4, 5, 7, 8, 10, 13, 14}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Black), .values_count = 11, .values = {0, 1, 2, 3, 4, 5, 7, 8, 10, 13, 14}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::White), .values_count = 18, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::White), .values_count = 18, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::White), .values_count = 15, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 16}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Asian), .values_count = 18, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Asian), .values_count = 18, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Asian), .values_count = 15, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 16}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Black), .values_count = 8, .values = {0, 1, 3, 4, 8, 10, 13, 14}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Black), .values_count = 8, .values = {0, 1, 3, 4, 8, 10, 13, 14}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Black), .values_count = 8, .values = {0, 1, 3, 4, 8, 10, 13, 14}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::White), .values_count = 12, .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::White), .values_count = 11, .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 15}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::White), .values_count = 10, .values = {0, 1, 3, 4, 6, 8, 10, 11, 13, 14}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Asian), .values_count = 12, .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 14, 15}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Asian), .values_count = 11, .values = {0, 1, 3, 4, 6, 8, 9, 10, 11, 13, 15}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Asian), .values_count = 10, .values = {0, 1, 3, 4, 6, 8, 10, 11, 13, 14}, }, }; -const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiMouthType{ - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::Black, +const std::array<RandomMiiData4, 18> RandomMiiMouthType{ + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Black), .values_count = 25, .values = {0, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 17, 18, 19, 21, 22, 23, 25, 26, 28, 30, 32, 33, 34, 35}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Black), .values_count = 27, .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 21, 22, 23, 25, 26, 28, 30, 32, 33, 34, 35}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Black), .values_count = 28, .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 21, 22, 23, 25, 26, 28, 30, 31, 32, 33, 34, 35}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::White), .values_count = 24, .values = {0, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::White), .values_count = 26, .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::White), .values_count = 26, .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Young, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Asian), .values_count = 24, .values = {0, 2, 3, 6, 7, 8, 9, 10, 12, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Normal, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Asian), .values_count = 26, .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Male, - .age = Age::Old, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Male), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Asian), .values_count = 26, .values = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 30, 31, 33, 34, 35}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Black), .values_count = 25, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14, 15, 17, 18, 19, 21, 22, 23, 25, 26, 30, 33, 34, 35}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Black), .values_count = 26, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, 15, 17, 18, 19, 21, 22, 23, 25, 26, 30, 33, 34, 35}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::Black, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Black), .values_count = 26, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, 15, 17, 18, 19, 21, 22, 23, 25, 26, 30, 33, 34, 35}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::White), .values_count = 25, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14, 15, 17, 18, 19, 21, 22, 23, 24, 26, 27, 29, 33, 35}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::White), .values_count = 26, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, 15, 17, 18, 19, 21, 22, 23, 24, 26, 27, 29, 33, 35}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::White, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::White), .values_count = 25, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, 15, 17, 18, 19, 21, 22, 23, 24, 25, 29, 33, 35}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Young, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Young), + .race = static_cast<u32>(Race::Asian), .values_count = 24, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 14, 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 33}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Normal, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Normal), + .race = static_cast<u32>(Race::Asian), .values_count = 25, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 33}, }, - Service::Mii::RandomMiiData4{ - .gender = Gender::Female, - .age = Age::Old, - .race = Race::Asian, + RandomMiiData4{ + .gender = static_cast<u32>(Gender::Female), + .age = static_cast<u32>(Age::Old), + .race = static_cast<u32>(Race::Asian), .values_count = 25, .values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 29, 33}, }, }; -const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiGlassType{ - Service::Mii::RandomMiiData2{ +const std::array<RandomMiiData2, 3> RandomMiiGlassType{ + RandomMiiData2{ .arg_1 = 0, - .values_count = 9, - .values = {90, 94, 96, 100, 0, 0, 0, 0, 0}, + .values_count = 4, + .values = {90, 94, 96, 100}, }, - Service::Mii::RandomMiiData2{ + RandomMiiData2{ .arg_1 = 1, - .values_count = 9, - .values = {83, 86, 90, 93, 94, 96, 98, 100, 0}, + .values_count = 8, + .values = {83, 86, 90, 93, 94, 96, 98, 100}, }, - Service::Mii::RandomMiiData2{ + RandomMiiData2{ .arg_1 = 2, - .values_count = 9, - .values = {78, 83, 0, 93, 0, 0, 98, 100, 0}, + .values_count = 8, + .values = {78, 83, 0, 93, 0, 0, 98, 100}, }, }; +u8 FromVer3GetFacelineColor(u8 color) { + return FromVer3FacelineColorTable[color]; +} + +u8 FromVer3GetHairColor(u8 color) { + return FromVer3HairColorTable[color]; +} + +u8 FromVer3GetEyeColor(u8 color) { + return FromVer3EyeColorTable[color]; +} + +u8 FromVer3GetMouthlineColor(u8 color) { + return FromVer3MouthlineColorTable[color]; +} + +u8 FromVer3GetGlassColor(u8 color) { + return FromVer3GlassColorTable[color]; +} + +u8 FromVer3GetGlassType(u8 type) { + return FromVer3GlassTypeTable[type]; +} + +FacelineColor GetFacelineColorFromVer3(u32 color) { + return static_cast<FacelineColor>(Ver3FacelineColorTable[color]); +} + +CommonColor GetHairColorFromVer3(u32 color) { + return static_cast<CommonColor>(Ver3HairColorTable[color]); +} + +CommonColor GetEyeColorFromVer3(u32 color) { + return static_cast<CommonColor>(Ver3EyeColorTable[color]); +} + +CommonColor GetMouthColorFromVer3(u32 color) { + return static_cast<CommonColor>(Ver3MouthColorTable[color]); +} + +CommonColor GetGlassColorFromVer3(u32 color) { + return static_cast<CommonColor>(Ver3GlassColorTable[color]); +} + } // namespace Service::Mii::RawData diff --git a/src/core/hle/service/mii/types/raw_data.h b/src/core/hle/service/mii/types/raw_data.h new file mode 100644 index 000000000..9a4cfa738 --- /dev/null +++ b/src/core/hle/service/mii/types/raw_data.h @@ -0,0 +1,73 @@ +// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include <array> + +#include "core/hle/service/mii/mii_types.h" + +namespace Service::Mii::RawData { + +struct RandomMiiValues { + std::array<u8, 188> values{}; +}; +static_assert(sizeof(RandomMiiValues) == 0xbc, "RandomMiiValues has incorrect size."); + +struct RandomMiiData4 { + u32 gender{}; + u32 age{}; + u32 race{}; + u32 values_count{}; + std::array<u32, 47> values{}; +}; +static_assert(sizeof(RandomMiiData4) == 0xcc, "RandomMiiData4 has incorrect size."); + +struct RandomMiiData3 { + u32 arg_1; + u32 arg_2; + u32 values_count; + std::array<u32, 47> values{}; +}; +static_assert(sizeof(RandomMiiData3) == 0xc8, "RandomMiiData3 has incorrect size."); + +struct RandomMiiData2 { + u32 arg_1; + u32 values_count; + std::array<u32, 47> values{}; +}; +static_assert(sizeof(RandomMiiData2) == 0xc4, "RandomMiiData2 has incorrect size."); + +extern const std::array<Service::Mii::DefaultMii, 2> BaseMii; +extern const std::array<Service::Mii::DefaultMii, 6> DefaultMii; + +extern const std::array<u8, 62> EyeRotateLookup; +extern const std::array<u8, 24> EyebrowRotateLookup; + +extern const std::array<RandomMiiData4, 18> RandomMiiFaceline; +extern const std::array<RandomMiiData3, 6> RandomMiiFacelineColor; +extern const std::array<RandomMiiData4, 18> RandomMiiFacelineWrinkle; +extern const std::array<RandomMiiData4, 18> RandomMiiFacelineMakeup; +extern const std::array<RandomMiiData4, 18> RandomMiiHairType; +extern const std::array<RandomMiiData3, 9> RandomMiiHairColor; +extern const std::array<RandomMiiData4, 18> RandomMiiEyeType; +extern const std::array<RandomMiiData2, 3> RandomMiiEyeColor; +extern const std::array<RandomMiiData4, 18> RandomMiiEyebrowType; +extern const std::array<RandomMiiData4, 18> RandomMiiNoseType; +extern const std::array<RandomMiiData4, 18> RandomMiiMouthType; +extern const std::array<RandomMiiData2, 3> RandomMiiGlassType; + +u8 FromVer3GetFacelineColor(u8 color); +u8 FromVer3GetHairColor(u8 color); +u8 FromVer3GetEyeColor(u8 color); +u8 FromVer3GetMouthlineColor(u8 color); +u8 FromVer3GetGlassColor(u8 color); +u8 FromVer3GetGlassType(u8 type); + +FacelineColor GetFacelineColorFromVer3(u32 color); +CommonColor GetHairColorFromVer3(u32 color); +CommonColor GetEyeColorFromVer3(u32 color); +CommonColor GetMouthColorFromVer3(u32 color); +CommonColor GetGlassColorFromVer3(u32 color); + +} // namespace Service::Mii::RawData diff --git a/src/core/hle/service/mii/types/store_data.cpp b/src/core/hle/service/mii/types/store_data.cpp new file mode 100644 index 000000000..127221fdb --- /dev/null +++ b/src/core/hle/service/mii/types/store_data.cpp @@ -0,0 +1,676 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/service/mii/mii_result.h" +#include "core/hle/service/mii/mii_util.h" +#include "core/hle/service/mii/types/raw_data.h" +#include "core/hle/service/mii/types/store_data.h" + +namespace Service::Mii { + +void StoreData::BuildDefault(u32 mii_index) { + const auto& default_mii = RawData::DefaultMii[mii_index]; + core_data.SetDefault(); + + core_data.SetFacelineType(static_cast<FacelineType>(default_mii.face_type)); + core_data.SetFacelineColor( + RawData::GetFacelineColorFromVer3(static_cast<u8>(default_mii.face_color))); + core_data.SetFacelineWrinkle(static_cast<FacelineWrinkle>(default_mii.face_wrinkle)); + core_data.SetFacelineMake(static_cast<FacelineMake>(default_mii.face_makeup)); + + core_data.SetHairType(static_cast<HairType>(default_mii.hair_type)); + core_data.SetHairColor(RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.hair_color))); + core_data.SetHairFlip(static_cast<HairFlip>(default_mii.hair_flip)); + core_data.SetEyeType(static_cast<EyeType>(default_mii.eye_type)); + core_data.SetEyeColor(RawData::GetEyeColorFromVer3(static_cast<u8>(default_mii.eye_color))); + core_data.SetEyeScale(static_cast<u8>(default_mii.eye_scale)); + core_data.SetEyeAspect(static_cast<u8>(default_mii.eye_aspect)); + core_data.SetEyeRotate(static_cast<u8>(default_mii.eye_rotate)); + core_data.SetEyeX(static_cast<u8>(default_mii.eye_x)); + core_data.SetEyeY(static_cast<u8>(default_mii.eye_y)); + + core_data.SetEyebrowType(static_cast<EyebrowType>(default_mii.eyebrow_type)); + core_data.SetEyebrowColor( + RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.eyebrow_color))); + core_data.SetEyebrowScale(static_cast<u8>(default_mii.eyebrow_scale)); + core_data.SetEyebrowAspect(static_cast<u8>(default_mii.eyebrow_aspect)); + core_data.SetEyebrowRotate(static_cast<u8>(default_mii.eyebrow_rotate)); + core_data.SetEyebrowX(static_cast<u8>(default_mii.eyebrow_x)); + core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y - 3)); + + core_data.SetNoseType(static_cast<NoseType>(default_mii.nose_type)); + core_data.SetNoseScale(static_cast<u8>(default_mii.nose_scale)); + core_data.SetNoseY(static_cast<u8>(default_mii.nose_y)); + + core_data.SetMouthType(static_cast<MouthType>(default_mii.mouth_type)); + core_data.SetMouthColor( + RawData::GetMouthColorFromVer3(static_cast<u8>(default_mii.mouth_color))); + core_data.SetMouthScale(static_cast<u8>(default_mii.mouth_scale)); + core_data.SetMouthAspect(static_cast<u8>(default_mii.mouth_aspect)); + core_data.SetMouthY(static_cast<u8>(default_mii.mouth_y)); + + core_data.SetMustacheType(static_cast<MustacheType>(default_mii.mustache_type)); + core_data.SetBeardType(static_cast<BeardType>(default_mii.beard_type)); + core_data.SetBeardColor( + RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.beard_color))); + core_data.SetMustacheScale(static_cast<u8>(default_mii.mustache_scale)); + core_data.SetMustacheY(static_cast<u8>(default_mii.mustache_y)); + + core_data.SetGlassType(static_cast<GlassType>(default_mii.glasses_type)); + core_data.SetGlassColor( + RawData::GetGlassColorFromVer3(static_cast<u8>(default_mii.glasses_color))); + core_data.SetGlassScale(static_cast<u8>(default_mii.glasses_scale)); + core_data.SetGlassY(static_cast<u8>(default_mii.glasses_y)); + + core_data.SetMoleType(static_cast<MoleType>(default_mii.mole_type)); + core_data.SetMoleScale(static_cast<u8>(default_mii.mole_scale)); + core_data.SetMoleX(static_cast<u8>(default_mii.mole_x)); + core_data.SetMoleY(static_cast<u8>(default_mii.mole_y)); + + core_data.SetHeight(static_cast<u8>(default_mii.height)); + core_data.SetBuild(static_cast<u8>(default_mii.weight)); + core_data.SetGender(static_cast<Gender>(default_mii.gender)); + core_data.SetFavoriteColor(static_cast<FavoriteColor>(default_mii.favorite_color)); + core_data.SetRegionMove(static_cast<u8>(default_mii.region_move)); + core_data.SetFontRegion(static_cast<FontRegion>(default_mii.font_region)); + core_data.SetType(static_cast<u8>(default_mii.type)); + core_data.SetNickname(default_mii.nickname); + + create_id = MiiUtil::MakeCreateId(); + SetChecksum(); +} + +void StoreData::BuildBase(Gender gender) { + const auto& default_mii = RawData::BaseMii[gender == Gender::Female ? 1 : 0]; + core_data.SetDefault(); + + core_data.SetFacelineType(static_cast<FacelineType>(default_mii.face_type)); + core_data.SetFacelineColor( + RawData::GetFacelineColorFromVer3(static_cast<u8>(default_mii.face_color))); + core_data.SetFacelineWrinkle(static_cast<FacelineWrinkle>(default_mii.face_wrinkle)); + core_data.SetFacelineMake(static_cast<FacelineMake>(default_mii.face_makeup)); + + core_data.SetHairType(static_cast<HairType>(default_mii.hair_type)); + core_data.SetHairColor(RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.hair_color))); + core_data.SetHairFlip(static_cast<HairFlip>(default_mii.hair_flip)); + core_data.SetEyeType(static_cast<EyeType>(default_mii.eye_type)); + core_data.SetEyeColor(RawData::GetEyeColorFromVer3(static_cast<u8>(default_mii.eye_color))); + core_data.SetEyeScale(static_cast<u8>(default_mii.eye_scale)); + core_data.SetEyeAspect(static_cast<u8>(default_mii.eye_aspect)); + core_data.SetEyeRotate(static_cast<u8>(default_mii.eye_rotate)); + core_data.SetEyeX(static_cast<u8>(default_mii.eye_x)); + core_data.SetEyeY(static_cast<u8>(default_mii.eye_y)); + + core_data.SetEyebrowType(static_cast<EyebrowType>(default_mii.eyebrow_type)); + core_data.SetEyebrowColor( + RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.eyebrow_color))); + core_data.SetEyebrowScale(static_cast<u8>(default_mii.eyebrow_scale)); + core_data.SetEyebrowAspect(static_cast<u8>(default_mii.eyebrow_aspect)); + core_data.SetEyebrowRotate(static_cast<u8>(default_mii.eyebrow_rotate)); + core_data.SetEyebrowX(static_cast<u8>(default_mii.eyebrow_x)); + core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y - 3)); + + core_data.SetNoseType(static_cast<NoseType>(default_mii.nose_type)); + core_data.SetNoseScale(static_cast<u8>(default_mii.nose_scale)); + core_data.SetNoseY(static_cast<u8>(default_mii.nose_y)); + + core_data.SetMouthType(static_cast<MouthType>(default_mii.mouth_type)); + core_data.SetMouthColor( + RawData::GetMouthColorFromVer3(static_cast<u8>(default_mii.mouth_color))); + core_data.SetMouthScale(static_cast<u8>(default_mii.mouth_scale)); + core_data.SetMouthAspect(static_cast<u8>(default_mii.mouth_aspect)); + core_data.SetMouthY(static_cast<u8>(default_mii.mouth_y)); + + core_data.SetMustacheType(static_cast<MustacheType>(default_mii.mustache_type)); + core_data.SetBeardType(static_cast<BeardType>(default_mii.beard_type)); + core_data.SetBeardColor( + RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.beard_color))); + core_data.SetMustacheScale(static_cast<u8>(default_mii.mustache_scale)); + core_data.SetMustacheY(static_cast<u8>(default_mii.mustache_y)); + + core_data.SetGlassType(static_cast<GlassType>(default_mii.glasses_type)); + core_data.SetGlassColor( + RawData::GetGlassColorFromVer3(static_cast<u8>(default_mii.glasses_color))); + core_data.SetGlassScale(static_cast<u8>(default_mii.glasses_scale)); + core_data.SetGlassY(static_cast<u8>(default_mii.glasses_y)); + + core_data.SetMoleType(static_cast<MoleType>(default_mii.mole_type)); + core_data.SetMoleScale(static_cast<u8>(default_mii.mole_scale)); + core_data.SetMoleX(static_cast<u8>(default_mii.mole_x)); + core_data.SetMoleY(static_cast<u8>(default_mii.mole_y)); + + core_data.SetHeight(static_cast<u8>(default_mii.height)); + core_data.SetBuild(static_cast<u8>(default_mii.weight)); + core_data.SetGender(static_cast<Gender>(default_mii.gender)); + core_data.SetFavoriteColor(static_cast<FavoriteColor>(default_mii.favorite_color)); + core_data.SetRegionMove(static_cast<u8>(default_mii.region_move)); + core_data.SetFontRegion(static_cast<FontRegion>(default_mii.font_region)); + core_data.SetType(static_cast<u8>(default_mii.type)); + core_data.SetNickname(default_mii.nickname); + + create_id = MiiUtil::MakeCreateId(); + SetChecksum(); +} + +void StoreData::BuildRandom(Age age, Gender gender, Race race) { + core_data.BuildRandom(age, gender, race); + create_id = MiiUtil::MakeCreateId(); + SetChecksum(); +} + +void StoreData::BuildWithCharInfo(const CharInfo& char_info) { + core_data.BuildFromCharInfo(char_info); + create_id = MiiUtil::MakeCreateId(); + SetChecksum(); +} + +void StoreData::BuildWithCoreData(const CoreData& in_core_data) { + core_data = in_core_data; + create_id = MiiUtil::MakeCreateId(); + SetChecksum(); +} + +Result StoreData::Restore() { + // TODO: Implement this + return ResultNotUpdated; +} + +ValidationResult StoreData::IsValid() const { + if (core_data.IsValid() != ValidationResult::NoErrors) { + return core_data.IsValid(); + } + if (data_crc != MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData) + sizeof(Common::UUID))) { + return ValidationResult::InvalidChecksum; + } + const auto device_id = MiiUtil::GetDeviceId(); + if (device_crc != MiiUtil::CalculateDeviceCrc16(device_id, sizeof(StoreData))) { + return ValidationResult::InvalidChecksum; + } + return ValidationResult::NoErrors; +} + +bool StoreData::IsSpecial() const { + return GetType() == 1; +} + +void StoreData::SetFontRegion(FontRegion value) { + core_data.SetFontRegion(value); +} + +void StoreData::SetFavoriteColor(FavoriteColor value) { + core_data.SetFavoriteColor(value); +} + +void StoreData::SetGender(Gender value) { + core_data.SetGender(value); +} + +void StoreData::SetHeight(u8 value) { + core_data.SetHeight(value); +} + +void StoreData::SetBuild(u8 value) { + core_data.SetBuild(value); +} + +void StoreData::SetType(u8 value) { + core_data.SetType(value); +} + +void StoreData::SetRegionMove(u8 value) { + core_data.SetRegionMove(value); +} + +void StoreData::SetFacelineType(FacelineType value) { + core_data.SetFacelineType(value); +} + +void StoreData::SetFacelineColor(FacelineColor value) { + core_data.SetFacelineColor(value); +} + +void StoreData::SetFacelineWrinkle(FacelineWrinkle value) { + core_data.SetFacelineWrinkle(value); +} + +void StoreData::SetFacelineMake(FacelineMake value) { + core_data.SetFacelineMake(value); +} + +void StoreData::SetHairType(HairType value) { + core_data.SetHairType(value); +} + +void StoreData::SetHairColor(CommonColor value) { + core_data.SetHairColor(value); +} + +void StoreData::SetHairFlip(HairFlip value) { + core_data.SetHairFlip(value); +} + +void StoreData::SetEyeType(EyeType value) { + core_data.SetEyeType(value); +} + +void StoreData::SetEyeColor(CommonColor value) { + core_data.SetEyeColor(value); +} + +void StoreData::SetEyeScale(u8 value) { + core_data.SetEyeScale(value); +} + +void StoreData::SetEyeAspect(u8 value) { + core_data.SetEyeAspect(value); +} + +void StoreData::SetEyeRotate(u8 value) { + core_data.SetEyeRotate(value); +} + +void StoreData::SetEyeX(u8 value) { + core_data.SetEyeX(value); +} + +void StoreData::SetEyeY(u8 value) { + core_data.SetEyeY(value); +} + +void StoreData::SetEyebrowType(EyebrowType value) { + core_data.SetEyebrowType(value); +} + +void StoreData::SetEyebrowColor(CommonColor value) { + core_data.SetEyebrowColor(value); +} + +void StoreData::SetEyebrowScale(u8 value) { + core_data.SetEyebrowScale(value); +} + +void StoreData::SetEyebrowAspect(u8 value) { + core_data.SetEyebrowAspect(value); +} + +void StoreData::SetEyebrowRotate(u8 value) { + core_data.SetEyebrowRotate(value); +} + +void StoreData::SetEyebrowX(u8 value) { + core_data.SetEyebrowX(value); +} + +void StoreData::SetEyebrowY(u8 value) { + core_data.SetEyebrowY(value); +} + +void StoreData::SetNoseType(NoseType value) { + core_data.SetNoseType(value); +} + +void StoreData::SetNoseScale(u8 value) { + core_data.SetNoseScale(value); +} + +void StoreData::SetNoseY(u8 value) { + core_data.SetNoseY(value); +} + +void StoreData::SetMouthType(MouthType value) { + core_data.SetMouthType(value); +} + +void StoreData::SetMouthColor(CommonColor value) { + core_data.SetMouthColor(value); +} + +void StoreData::SetMouthScale(u8 value) { + core_data.SetMouthScale(value); +} + +void StoreData::SetMouthAspect(u8 value) { + core_data.SetMouthAspect(value); +} + +void StoreData::SetMouthY(u8 value) { + core_data.SetMouthY(value); +} + +void StoreData::SetBeardColor(CommonColor value) { + core_data.SetBeardColor(value); +} + +void StoreData::SetBeardType(BeardType value) { + core_data.SetBeardType(value); +} + +void StoreData::SetMustacheType(MustacheType value) { + core_data.SetMustacheType(value); +} + +void StoreData::SetMustacheScale(u8 value) { + core_data.SetMustacheScale(value); +} + +void StoreData::SetMustacheY(u8 value) { + core_data.SetMustacheY(value); +} + +void StoreData::SetGlassType(GlassType value) { + core_data.SetGlassType(value); +} + +void StoreData::SetGlassColor(CommonColor value) { + core_data.SetGlassColor(value); +} + +void StoreData::SetGlassScale(u8 value) { + core_data.SetGlassScale(value); +} + +void StoreData::SetGlassY(u8 value) { + core_data.SetGlassY(value); +} + +void StoreData::SetMoleType(MoleType value) { + core_data.SetMoleType(value); +} + +void StoreData::SetMoleScale(u8 value) { + core_data.SetMoleScale(value); +} + +void StoreData::SetMoleX(u8 value) { + core_data.SetMoleX(value); +} + +void StoreData::SetMoleY(u8 value) { + core_data.SetMoleY(value); +} + +void StoreData::SetNickname(Nickname value) { + core_data.SetNickname(value); +} + +void StoreData::SetInvalidName() { + const auto& invalid_name = core_data.GetInvalidNickname(); + core_data.SetNickname(invalid_name); + SetChecksum(); +} + +void StoreData::SetChecksum() { + SetDataChecksum(); + SetDeviceChecksum(); +} + +void StoreData::SetDataChecksum() { + data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData) + sizeof(Common::UUID)); +} + +void StoreData::SetDeviceChecksum() { + const auto device_id = MiiUtil::GetDeviceId(); + device_crc = MiiUtil::CalculateDeviceCrc16(device_id, sizeof(StoreData)); +} + +Common::UUID StoreData::GetCreateId() const { + return create_id; +} + +FontRegion StoreData::GetFontRegion() const { + return static_cast<FontRegion>(core_data.GetFontRegion()); +} + +FavoriteColor StoreData::GetFavoriteColor() const { + return core_data.GetFavoriteColor(); +} + +Gender StoreData::GetGender() const { + return core_data.GetGender(); +} + +u8 StoreData::GetHeight() const { + return core_data.GetHeight(); +} + +u8 StoreData::GetBuild() const { + return core_data.GetBuild(); +} + +u8 StoreData::GetType() const { + return core_data.GetType(); +} + +u8 StoreData::GetRegionMove() const { + return core_data.GetRegionMove(); +} + +FacelineType StoreData::GetFacelineType() const { + return core_data.GetFacelineType(); +} + +FacelineColor StoreData::GetFacelineColor() const { + return core_data.GetFacelineColor(); +} + +FacelineWrinkle StoreData::GetFacelineWrinkle() const { + return core_data.GetFacelineWrinkle(); +} + +FacelineMake StoreData::GetFacelineMake() const { + return core_data.GetFacelineMake(); +} + +HairType StoreData::GetHairType() const { + return core_data.GetHairType(); +} + +CommonColor StoreData::GetHairColor() const { + return core_data.GetHairColor(); +} + +HairFlip StoreData::GetHairFlip() const { + return core_data.GetHairFlip(); +} + +EyeType StoreData::GetEyeType() const { + return core_data.GetEyeType(); +} + +CommonColor StoreData::GetEyeColor() const { + return core_data.GetEyeColor(); +} + +u8 StoreData::GetEyeScale() const { + return core_data.GetEyeScale(); +} + +u8 StoreData::GetEyeAspect() const { + return core_data.GetEyeAspect(); +} + +u8 StoreData::GetEyeRotate() const { + return core_data.GetEyeRotate(); +} + +u8 StoreData::GetEyeX() const { + return core_data.GetEyeX(); +} + +u8 StoreData::GetEyeY() const { + return core_data.GetEyeY(); +} + +EyebrowType StoreData::GetEyebrowType() const { + return core_data.GetEyebrowType(); +} + +CommonColor StoreData::GetEyebrowColor() const { + return core_data.GetEyebrowColor(); +} + +u8 StoreData::GetEyebrowScale() const { + return core_data.GetEyebrowScale(); +} + +u8 StoreData::GetEyebrowAspect() const { + return core_data.GetEyebrowAspect(); +} + +u8 StoreData::GetEyebrowRotate() const { + return core_data.GetEyebrowRotate(); +} + +u8 StoreData::GetEyebrowX() const { + return core_data.GetEyebrowX(); +} + +u8 StoreData::GetEyebrowY() const { + return core_data.GetEyebrowY(); +} + +NoseType StoreData::GetNoseType() const { + return core_data.GetNoseType(); +} + +u8 StoreData::GetNoseScale() const { + return core_data.GetNoseScale(); +} + +u8 StoreData::GetNoseY() const { + return core_data.GetNoseY(); +} + +MouthType StoreData::GetMouthType() const { + return core_data.GetMouthType(); +} + +CommonColor StoreData::GetMouthColor() const { + return core_data.GetMouthColor(); +} + +u8 StoreData::GetMouthScale() const { + return core_data.GetMouthScale(); +} + +u8 StoreData::GetMouthAspect() const { + return core_data.GetMouthAspect(); +} + +u8 StoreData::GetMouthY() const { + return core_data.GetMouthY(); +} + +CommonColor StoreData::GetBeardColor() const { + return core_data.GetBeardColor(); +} + +BeardType StoreData::GetBeardType() const { + return core_data.GetBeardType(); +} + +MustacheType StoreData::GetMustacheType() const { + return core_data.GetMustacheType(); +} + +u8 StoreData::GetMustacheScale() const { + return core_data.GetMustacheScale(); +} + +u8 StoreData::GetMustacheY() const { + return core_data.GetMustacheY(); +} + +GlassType StoreData::GetGlassType() const { + return core_data.GetGlassType(); +} + +CommonColor StoreData::GetGlassColor() const { + return core_data.GetGlassColor(); +} + +u8 StoreData::GetGlassScale() const { + return core_data.GetGlassScale(); +} + +u8 StoreData::GetGlassY() const { + return core_data.GetGlassY(); +} + +MoleType StoreData::GetMoleType() const { + return core_data.GetMoleType(); +} + +u8 StoreData::GetMoleScale() const { + return core_data.GetMoleScale(); +} + +u8 StoreData::GetMoleX() const { + return core_data.GetMoleX(); +} + +u8 StoreData::GetMoleY() const { + return core_data.GetMoleY(); +} + +Nickname StoreData::GetNickname() const { + return core_data.GetNickname(); +} + +bool StoreData::operator==(const StoreData& data) { + bool is_identical = data.core_data.IsValid() == ValidationResult::NoErrors; + is_identical &= core_data.GetNickname().data == data.core_data.GetNickname().data; + is_identical &= GetCreateId() == data.GetCreateId(); + is_identical &= GetFontRegion() == data.GetFontRegion(); + is_identical &= GetFavoriteColor() == data.GetFavoriteColor(); + is_identical &= GetGender() == data.GetGender(); + is_identical &= GetHeight() == data.GetHeight(); + is_identical &= GetBuild() == data.GetBuild(); + is_identical &= GetType() == data.GetType(); + is_identical &= GetRegionMove() == data.GetRegionMove(); + is_identical &= GetFacelineType() == data.GetFacelineType(); + is_identical &= GetFacelineColor() == data.GetFacelineColor(); + is_identical &= GetFacelineWrinkle() == data.GetFacelineWrinkle(); + is_identical &= GetFacelineMake() == data.GetFacelineMake(); + is_identical &= GetHairType() == data.GetHairType(); + is_identical &= GetHairColor() == data.GetHairColor(); + is_identical &= GetHairFlip() == data.GetHairFlip(); + is_identical &= GetEyeType() == data.GetEyeType(); + is_identical &= GetEyeColor() == data.GetEyeColor(); + is_identical &= GetEyeScale() == data.GetEyeScale(); + is_identical &= GetEyeAspect() == data.GetEyeAspect(); + is_identical &= GetEyeRotate() == data.GetEyeRotate(); + is_identical &= GetEyeX() == data.GetEyeX(); + is_identical &= GetEyeY() == data.GetEyeY(); + is_identical &= GetEyebrowType() == data.GetEyebrowType(); + is_identical &= GetEyebrowColor() == data.GetEyebrowColor(); + is_identical &= GetEyebrowScale() == data.GetEyebrowScale(); + is_identical &= GetEyebrowAspect() == data.GetEyebrowAspect(); + is_identical &= GetEyebrowRotate() == data.GetEyebrowRotate(); + is_identical &= GetEyebrowX() == data.GetEyebrowX(); + is_identical &= GetEyebrowY() == data.GetEyebrowY(); + is_identical &= GetNoseType() == data.GetNoseType(); + is_identical &= GetNoseScale() == data.GetNoseScale(); + is_identical &= GetNoseY() == data.GetNoseY(); + is_identical &= GetMouthType() == data.GetMouthType(); + is_identical &= GetMouthColor() == data.GetMouthColor(); + is_identical &= GetMouthScale() == data.GetMouthScale(); + is_identical &= GetMouthAspect() == data.GetMouthAspect(); + is_identical &= GetMouthY() == data.GetMouthY(); + is_identical &= GetBeardColor() == data.GetBeardColor(); + is_identical &= GetBeardType() == data.GetBeardType(); + is_identical &= GetMustacheType() == data.GetMustacheType(); + is_identical &= GetMustacheScale() == data.GetMustacheScale(); + is_identical &= GetMustacheY() == data.GetMustacheY(); + is_identical &= GetGlassType() == data.GetGlassType(); + is_identical &= GetGlassColor() == data.GetGlassColor(); + is_identical &= GetGlassScale() == data.GetGlassScale(); + is_identical &= GetGlassY() == data.GetGlassY(); + is_identical &= GetMoleType() == data.GetMoleType(); + is_identical &= GetMoleScale() == data.GetMoleScale(); + is_identical &= GetMoleX() == data.GetMoleX(); + is_identical &= data.GetMoleY() == data.GetMoleY(); + return is_identical; +} + +} // namespace Service::Mii diff --git a/src/core/hle/service/mii/types/store_data.h b/src/core/hle/service/mii/types/store_data.h new file mode 100644 index 000000000..ed5dfb949 --- /dev/null +++ b/src/core/hle/service/mii/types/store_data.h @@ -0,0 +1,150 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/hle/result.h" +#include "core/hle/service/mii/mii_types.h" +#include "core/hle/service/mii/types/core_data.h" + +namespace Service::Mii { + +class StoreData { +public: + void BuildDefault(u32 mii_index); + void BuildBase(Gender gender); + void BuildRandom(Age age, Gender gender, Race race); + void BuildWithCharInfo(const CharInfo& char_info); + void BuildWithCoreData(const CoreData& in_core_data); + Result Restore(); + + ValidationResult IsValid() const; + + bool IsSpecial() const; + + void SetFontRegion(FontRegion value); + void SetFavoriteColor(FavoriteColor value); + void SetGender(Gender value); + void SetHeight(u8 value); + void SetBuild(u8 value); + void SetType(u8 value); + void SetRegionMove(u8 value); + void SetFacelineType(FacelineType value); + void SetFacelineColor(FacelineColor value); + void SetFacelineWrinkle(FacelineWrinkle value); + void SetFacelineMake(FacelineMake value); + void SetHairType(HairType value); + void SetHairColor(CommonColor value); + void SetHairFlip(HairFlip value); + void SetEyeType(EyeType value); + void SetEyeColor(CommonColor value); + void SetEyeScale(u8 value); + void SetEyeAspect(u8 value); + void SetEyeRotate(u8 value); + void SetEyeX(u8 value); + void SetEyeY(u8 value); + void SetEyebrowType(EyebrowType value); + void SetEyebrowColor(CommonColor value); + void SetEyebrowScale(u8 value); + void SetEyebrowAspect(u8 value); + void SetEyebrowRotate(u8 value); + void SetEyebrowX(u8 value); + void SetEyebrowY(u8 value); + void SetNoseType(NoseType value); + void SetNoseScale(u8 value); + void SetNoseY(u8 value); + void SetMouthType(MouthType value); + void SetMouthColor(CommonColor value); + void SetMouthScale(u8 value); + void SetMouthAspect(u8 value); + void SetMouthY(u8 value); + void SetBeardColor(CommonColor value); + void SetBeardType(BeardType value); + void SetMustacheType(MustacheType value); + void SetMustacheScale(u8 value); + void SetMustacheY(u8 value); + void SetGlassType(GlassType value); + void SetGlassColor(CommonColor value); + void SetGlassScale(u8 value); + void SetGlassY(u8 value); + void SetMoleType(MoleType value); + void SetMoleScale(u8 value); + void SetMoleX(u8 value); + void SetMoleY(u8 value); + void SetNickname(Nickname nickname); + void SetInvalidName(); + void SetChecksum(); + void SetDataChecksum(); + void SetDeviceChecksum(); + + Common::UUID GetCreateId() const; + FontRegion GetFontRegion() const; + FavoriteColor GetFavoriteColor() const; + Gender GetGender() const; + u8 GetHeight() const; + u8 GetBuild() const; + u8 GetType() const; + u8 GetRegionMove() const; + FacelineType GetFacelineType() const; + FacelineColor GetFacelineColor() const; + FacelineWrinkle GetFacelineWrinkle() const; + FacelineMake GetFacelineMake() const; + HairType GetHairType() const; + CommonColor GetHairColor() const; + HairFlip GetHairFlip() const; + EyeType GetEyeType() const; + CommonColor GetEyeColor() const; + u8 GetEyeScale() const; + u8 GetEyeAspect() const; + u8 GetEyeRotate() const; + u8 GetEyeX() const; + u8 GetEyeY() const; + EyebrowType GetEyebrowType() const; + CommonColor GetEyebrowColor() const; + u8 GetEyebrowScale() const; + u8 GetEyebrowAspect() const; + u8 GetEyebrowRotate() const; + u8 GetEyebrowX() const; + u8 GetEyebrowY() const; + NoseType GetNoseType() const; + u8 GetNoseScale() const; + u8 GetNoseY() const; + MouthType GetMouthType() const; + CommonColor GetMouthColor() const; + u8 GetMouthScale() const; + u8 GetMouthAspect() const; + u8 GetMouthY() const; + CommonColor GetBeardColor() const; + BeardType GetBeardType() const; + MustacheType GetMustacheType() const; + u8 GetMustacheScale() const; + u8 GetMustacheY() const; + GlassType GetGlassType() const; + CommonColor GetGlassColor() const; + u8 GetGlassScale() const; + u8 GetGlassY() const; + MoleType GetMoleType() const; + u8 GetMoleScale() const; + u8 GetMoleX() const; + u8 GetMoleY() const; + Nickname GetNickname() const; + + bool operator==(const StoreData& data); + +private: + CoreData core_data{}; + Common::UUID create_id{}; + u16 data_crc{}; + u16 device_crc{}; +}; +static_assert(sizeof(StoreData) == 0x44, "StoreData has incorrect size."); +static_assert(std::is_trivially_copyable_v<StoreData>, + "StoreData type must be trivially copyable."); + +struct StoreDataElement { + StoreData store_data{}; + Source source{}; +}; +static_assert(sizeof(StoreDataElement) == 0x48, "StoreDataElement has incorrect size."); + +}; // namespace Service::Mii diff --git a/src/core/hle/service/mii/types/ver3_store_data.cpp b/src/core/hle/service/mii/types/ver3_store_data.cpp new file mode 100644 index 000000000..a019cc9f7 --- /dev/null +++ b/src/core/hle/service/mii/types/ver3_store_data.cpp @@ -0,0 +1,241 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "core/hle/service/mii/mii_util.h" +#include "core/hle/service/mii/types/raw_data.h" +#include "core/hle/service/mii/types/store_data.h" +#include "core/hle/service/mii/types/ver3_store_data.h" + +namespace Service::Mii { + +void NfpStoreDataExtension::SetFromStoreData(const StoreData& store_data) { + faceline_color = static_cast<u8>(store_data.GetFacelineColor()) & 0xf; + hair_color = static_cast<u8>(store_data.GetHairColor()) & 0x7f; + eye_color = static_cast<u8>(store_data.GetEyeColor()) & 0x7f; + eyebrow_color = static_cast<u8>(store_data.GetEyebrowColor()) & 0x7f; + mouth_color = static_cast<u8>(store_data.GetMouthColor()) & 0x7f; + beard_color = static_cast<u8>(store_data.GetBeardColor()) & 0x7f; + glass_color = static_cast<u8>(store_data.GetGlassColor()) & 0x7f; + glass_type = static_cast<u8>(store_data.GetGlassType()) & 0x1f; +} + +void Ver3StoreData::BuildToStoreData(StoreData& out_store_data) const { + out_store_data.BuildBase(Gender::Male); + + out_store_data.SetGender(static_cast<Gender>(mii_information.gender.Value())); + out_store_data.SetFavoriteColor( + static_cast<FavoriteColor>(mii_information.favorite_color.Value())); + out_store_data.SetHeight(height); + out_store_data.SetBuild(build); + + out_store_data.SetNickname(mii_name); + out_store_data.SetFontRegion( + static_cast<FontRegion>(static_cast<u8>(region_information.font_region.Value()))); + + out_store_data.SetFacelineType( + static_cast<FacelineType>(appearance_bits1.faceline_type.Value())); + out_store_data.SetFacelineColor( + RawData::GetFacelineColorFromVer3(appearance_bits1.faceline_color.Value())); + out_store_data.SetFacelineWrinkle( + static_cast<FacelineWrinkle>(appearance_bits2.faceline_wrinkle.Value())); + out_store_data.SetFacelineMake( + static_cast<FacelineMake>(appearance_bits2.faceline_make.Value())); + + out_store_data.SetHairType(static_cast<HairType>(hair_type)); + out_store_data.SetHairColor(RawData::GetHairColorFromVer3(appearance_bits3.hair_color.Value())); + out_store_data.SetHairFlip(static_cast<HairFlip>(appearance_bits3.hair_flip.Value())); + + out_store_data.SetEyeType(static_cast<EyeType>(appearance_bits4.eye_type.Value())); + out_store_data.SetEyeColor(RawData::GetEyeColorFromVer3(appearance_bits4.eye_color.Value())); + out_store_data.SetEyeScale(static_cast<u8>(appearance_bits4.eye_scale.Value())); + out_store_data.SetEyeAspect(static_cast<u8>(appearance_bits4.eye_aspect.Value())); + out_store_data.SetEyeRotate(static_cast<u8>(appearance_bits4.eye_rotate.Value())); + out_store_data.SetEyeX(static_cast<u8>(appearance_bits4.eye_x.Value())); + out_store_data.SetEyeY(static_cast<u8>(appearance_bits4.eye_y.Value())); + + out_store_data.SetEyebrowType(static_cast<EyebrowType>(appearance_bits5.eyebrow_type.Value())); + out_store_data.SetEyebrowColor( + RawData::GetHairColorFromVer3(appearance_bits5.eyebrow_color.Value())); + out_store_data.SetEyebrowScale(static_cast<u8>(appearance_bits5.eyebrow_scale.Value())); + out_store_data.SetEyebrowAspect(static_cast<u8>(appearance_bits5.eyebrow_aspect.Value())); + out_store_data.SetEyebrowRotate(static_cast<u8>(appearance_bits5.eyebrow_rotate.Value())); + out_store_data.SetEyebrowX(static_cast<u8>(appearance_bits5.eyebrow_x.Value())); + out_store_data.SetEyebrowY(static_cast<u8>(appearance_bits5.eyebrow_y.Value() - 3)); + + out_store_data.SetNoseType(static_cast<NoseType>(appearance_bits6.nose_type.Value())); + out_store_data.SetNoseScale(static_cast<u8>(appearance_bits6.nose_scale.Value())); + out_store_data.SetNoseY(static_cast<u8>(appearance_bits6.nose_y.Value())); + + out_store_data.SetMouthType(static_cast<MouthType>(appearance_bits7.mouth_type.Value())); + out_store_data.SetMouthColor( + RawData::GetMouthColorFromVer3(appearance_bits7.mouth_color.Value())); + out_store_data.SetMouthScale(static_cast<u8>(appearance_bits7.mouth_scale.Value())); + out_store_data.SetMouthAspect(static_cast<u8>(appearance_bits7.mouth_aspect.Value())); + out_store_data.SetMouthY(static_cast<u8>(appearance_bits8.mouth_y.Value())); + + out_store_data.SetMustacheType( + static_cast<MustacheType>(appearance_bits8.mustache_type.Value())); + out_store_data.SetMustacheScale(static_cast<u8>(appearance_bits9.mustache_scale.Value())); + out_store_data.SetMustacheY(static_cast<u8>(appearance_bits9.mustache_y.Value())); + + out_store_data.SetBeardType(static_cast<BeardType>(appearance_bits9.beard_type.Value())); + out_store_data.SetBeardColor( + RawData::GetHairColorFromVer3(appearance_bits9.beard_color.Value())); + + // Glass type is compatible as it is. It doesn't need a table + out_store_data.SetGlassType(static_cast<GlassType>(appearance_bits10.glass_type.Value())); + out_store_data.SetGlassColor( + RawData::GetGlassColorFromVer3(appearance_bits10.glass_color.Value())); + out_store_data.SetGlassScale(static_cast<u8>(appearance_bits10.glass_scale.Value())); + out_store_data.SetGlassY(static_cast<u8>(appearance_bits10.glass_y.Value())); + + out_store_data.SetMoleType(static_cast<MoleType>(appearance_bits11.mole_type.Value())); + out_store_data.SetMoleScale(static_cast<u8>(appearance_bits11.mole_scale.Value())); + out_store_data.SetMoleX(static_cast<u8>(appearance_bits11.mole_x.Value())); + out_store_data.SetMoleY(static_cast<u8>(appearance_bits11.mole_y.Value())); + + out_store_data.SetChecksum(); +} + +void Ver3StoreData::BuildFromStoreData(const StoreData& store_data) { + version = 1; + mii_information.gender.Assign(static_cast<u8>(store_data.GetGender())); + mii_information.favorite_color.Assign(static_cast<u8>(store_data.GetFavoriteColor())); + height = store_data.GetHeight(); + build = store_data.GetBuild(); + + mii_name = store_data.GetNickname(); + region_information.font_region.Assign(static_cast<u8>(store_data.GetFontRegion())); + + appearance_bits1.faceline_type.Assign(static_cast<u8>(store_data.GetFacelineType())); + appearance_bits2.faceline_wrinkle.Assign(static_cast<u8>(store_data.GetFacelineWrinkle())); + appearance_bits2.faceline_make.Assign(static_cast<u8>(store_data.GetFacelineMake())); + + hair_type = static_cast<u8>(store_data.GetHairType()); + appearance_bits3.hair_flip.Assign(static_cast<u8>(store_data.GetHairFlip())); + + appearance_bits4.eye_type.Assign(static_cast<u8>(store_data.GetEyeType())); + appearance_bits4.eye_scale.Assign(store_data.GetEyeScale()); + appearance_bits4.eye_aspect.Assign(store_data.GetEyebrowAspect()); + appearance_bits4.eye_rotate.Assign(store_data.GetEyeRotate()); + appearance_bits4.eye_x.Assign(store_data.GetEyeX()); + appearance_bits4.eye_y.Assign(store_data.GetEyeY()); + + appearance_bits5.eyebrow_type.Assign(static_cast<u8>(store_data.GetEyebrowType())); + appearance_bits5.eyebrow_scale.Assign(store_data.GetEyebrowScale()); + appearance_bits5.eyebrow_aspect.Assign(store_data.GetEyebrowAspect()); + appearance_bits5.eyebrow_rotate.Assign(store_data.GetEyebrowRotate()); + appearance_bits5.eyebrow_x.Assign(store_data.GetEyebrowX()); + appearance_bits5.eyebrow_y.Assign(store_data.GetEyebrowY()); + + appearance_bits6.nose_type.Assign(static_cast<u8>(store_data.GetNoseType())); + appearance_bits6.nose_scale.Assign(store_data.GetNoseScale()); + appearance_bits6.nose_y.Assign(store_data.GetNoseY()); + + appearance_bits7.mouth_type.Assign(static_cast<u8>(store_data.GetMouthType())); + appearance_bits7.mouth_scale.Assign(store_data.GetMouthScale()); + appearance_bits7.mouth_aspect.Assign(store_data.GetMouthAspect()); + appearance_bits8.mouth_y.Assign(store_data.GetMouthY()); + + appearance_bits8.mustache_type.Assign(static_cast<u8>(store_data.GetMustacheType())); + appearance_bits9.mustache_scale.Assign(store_data.GetMustacheScale()); + appearance_bits9.mustache_y.Assign(store_data.GetMustacheY()); + + appearance_bits9.beard_type.Assign(static_cast<u8>(store_data.GetBeardType())); + + appearance_bits10.glass_scale.Assign(store_data.GetGlassScale()); + appearance_bits10.glass_y.Assign(store_data.GetGlassY()); + + appearance_bits11.mole_type.Assign(static_cast<u8>(store_data.GetMoleType())); + appearance_bits11.mole_scale.Assign(store_data.GetMoleScale()); + appearance_bits11.mole_x.Assign(store_data.GetMoleX()); + appearance_bits11.mole_y.Assign(store_data.GetMoleY()); + + // These types are converted to V3 from a table + appearance_bits1.faceline_color.Assign( + RawData::FromVer3GetFacelineColor(static_cast<u8>(store_data.GetFacelineColor()))); + appearance_bits3.hair_color.Assign( + RawData::FromVer3GetHairColor(static_cast<u8>(store_data.GetHairColor()))); + appearance_bits4.eye_color.Assign( + RawData::FromVer3GetEyeColor(static_cast<u8>(store_data.GetEyeColor()))); + appearance_bits5.eyebrow_color.Assign( + RawData::FromVer3GetHairColor(static_cast<u8>(store_data.GetEyebrowColor()))); + appearance_bits7.mouth_color.Assign( + RawData::FromVer3GetMouthlineColor(static_cast<u8>(store_data.GetMouthColor()))); + appearance_bits9.beard_color.Assign( + RawData::FromVer3GetHairColor(static_cast<u8>(store_data.GetBeardColor()))); + appearance_bits10.glass_color.Assign( + RawData::FromVer3GetGlassColor(static_cast<u8>(store_data.GetGlassColor()))); + appearance_bits10.glass_type.Assign( + RawData::FromVer3GetGlassType(static_cast<u8>(store_data.GetGlassType()))); + + crc = MiiUtil::CalculateCrc16(&version, sizeof(Ver3StoreData) - sizeof(u16)); +} + +u32 Ver3StoreData::IsValid() const { + bool is_valid = version == 0 || version == 3; + + is_valid = is_valid && (mii_name.data[0] != '\0'); + + is_valid = is_valid && (mii_information.birth_month < 13); + is_valid = is_valid && (mii_information.birth_day < 32); + is_valid = is_valid && (mii_information.favorite_color <= static_cast<u8>(FavoriteColor::Max)); + is_valid = is_valid && (height <= MaxHeight); + is_valid = is_valid && (build <= MaxBuild); + + is_valid = is_valid && (appearance_bits1.faceline_type <= static_cast<u8>(FacelineType::Max)); + is_valid = is_valid && (appearance_bits1.faceline_color <= MaxVer3CommonColor - 2); + is_valid = + is_valid && (appearance_bits2.faceline_wrinkle <= static_cast<u8>(FacelineWrinkle::Max)); + is_valid = is_valid && (appearance_bits2.faceline_make <= static_cast<u8>(FacelineMake::Max)); + + is_valid = is_valid && (hair_type <= static_cast<u8>(HairType::Max)); + is_valid = is_valid && (appearance_bits3.hair_color <= MaxVer3CommonColor); + + is_valid = is_valid && (appearance_bits4.eye_type <= static_cast<u8>(EyeType::Max)); + is_valid = is_valid && (appearance_bits4.eye_color <= MaxVer3CommonColor - 2); + is_valid = is_valid && (appearance_bits4.eye_scale <= MaxEyeScale); + is_valid = is_valid && (appearance_bits4.eye_aspect <= MaxEyeAspect); + is_valid = is_valid && (appearance_bits4.eye_rotate <= MaxEyeRotate); + is_valid = is_valid && (appearance_bits4.eye_x <= MaxEyeX); + is_valid = is_valid && (appearance_bits4.eye_y <= MaxEyeY); + + is_valid = is_valid && (appearance_bits5.eyebrow_type <= static_cast<u8>(EyebrowType::Max)); + is_valid = is_valid && (appearance_bits5.eyebrow_color <= MaxVer3CommonColor); + is_valid = is_valid && (appearance_bits5.eyebrow_scale <= MaxEyebrowScale); + is_valid = is_valid && (appearance_bits5.eyebrow_aspect <= MaxEyebrowAspect); + is_valid = is_valid && (appearance_bits5.eyebrow_rotate <= MaxEyebrowRotate); + is_valid = is_valid && (appearance_bits5.eyebrow_x <= MaxEyebrowX); + is_valid = is_valid && (appearance_bits5.eyebrow_y <= MaxEyebrowY); + + is_valid = is_valid && (appearance_bits6.nose_type <= static_cast<u8>(NoseType::Max)); + is_valid = is_valid && (appearance_bits6.nose_scale <= MaxNoseScale); + is_valid = is_valid && (appearance_bits6.nose_y <= MaxNoseY); + + is_valid = is_valid && (appearance_bits7.mouth_type <= static_cast<u8>(MouthType::Max)); + is_valid = is_valid && (appearance_bits7.mouth_color <= MaxVer3CommonColor - 3); + is_valid = is_valid && (appearance_bits7.mouth_scale <= MaxMouthScale); + is_valid = is_valid && (appearance_bits7.mouth_aspect <= MaxMoutAspect); + is_valid = is_valid && (appearance_bits8.mouth_y <= MaxMouthY); + + is_valid = is_valid && (appearance_bits8.mustache_type <= static_cast<u8>(MustacheType::Max)); + is_valid = is_valid && (appearance_bits9.mustache_scale < MaxMustacheScale); + is_valid = is_valid && (appearance_bits9.mustache_y <= MaxMustacheY); + + is_valid = is_valid && (appearance_bits9.beard_type <= static_cast<u8>(BeardType::Max)); + is_valid = is_valid && (appearance_bits9.beard_color <= MaxVer3CommonColor); + + is_valid = is_valid && (appearance_bits10.glass_type <= MaxVer3GlassType); + is_valid = is_valid && (appearance_bits10.glass_color <= MaxVer3CommonColor - 2); + is_valid = is_valid && (appearance_bits10.glass_scale <= MaxGlassScale); + is_valid = is_valid && (appearance_bits10.glass_y <= MaxGlassY); + + is_valid = is_valid && (appearance_bits11.mole_type <= static_cast<u8>(MoleType::Max)); + is_valid = is_valid && (appearance_bits11.mole_scale <= MaxMoleScale); + is_valid = is_valid && (appearance_bits11.mole_x <= MaxMoleX); + is_valid = is_valid && (appearance_bits11.mole_y <= MaxMoleY); + + return is_valid; +} + +} // namespace Service::Mii diff --git a/src/core/hle/service/mii/types/ver3_store_data.h b/src/core/hle/service/mii/types/ver3_store_data.h new file mode 100644 index 000000000..47907bf7d --- /dev/null +++ b/src/core/hle/service/mii/types/ver3_store_data.h @@ -0,0 +1,160 @@ +// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/hle/service/mii/mii_types.h" + +namespace Service::Mii { +class StoreData; + +// This is nn::mii::Ver3StoreData +// Based on citra HLE::Applets::MiiData and PretendoNetwork. +// https://github.com/citra-emu/citra/blob/master/src/core/hle/applets/mii_selector.h#L48 +// https://github.com/PretendoNetwork/mii-js/blob/master/mii.js#L299 + +struct NfpStoreDataExtension { + void SetFromStoreData(const StoreData& store_data); + + u8 faceline_color; + u8 hair_color; + u8 eye_color; + u8 eyebrow_color; + u8 mouth_color; + u8 beard_color; + u8 glass_color; + u8 glass_type; +}; +static_assert(sizeof(NfpStoreDataExtension) == 0x8, "NfpStoreDataExtension is an invalid size"); + +#pragma pack(push, 4) +class Ver3StoreData { +public: + void BuildToStoreData(StoreData& out_store_data) const; + void BuildFromStoreData(const StoreData& store_data); + + u32 IsValid() const; + + u8 version; + union { + u8 raw; + + BitField<0, 1, u8> allow_copying; + BitField<1, 1, u8> profanity_flag; + BitField<2, 2, u8> region_lock; + BitField<4, 2, u8> font_region; + } region_information; + u16_be mii_id; + u64_be system_id; + u32_be specialness_and_creation_date; + std::array<u8, 6> creator_mac; + u16_be padding; + union { + u16 raw; + + BitField<0, 1, u16> gender; + BitField<1, 4, u16> birth_month; + BitField<5, 5, u16> birth_day; + BitField<10, 4, u16> favorite_color; + BitField<14, 1, u16> favorite; + } mii_information; + Nickname mii_name; + u8 height; + u8 build; + union { + u8 raw; + + BitField<0, 1, u8> disable_sharing; + BitField<1, 4, u8> faceline_type; + BitField<5, 3, u8> faceline_color; + } appearance_bits1; + union { + u8 raw; + + BitField<0, 4, u8> faceline_wrinkle; + BitField<4, 4, u8> faceline_make; + } appearance_bits2; + u8 hair_type; + union { + u8 raw; + + BitField<0, 3, u8> hair_color; + BitField<3, 1, u8> hair_flip; + } appearance_bits3; + union { + u32 raw; + + BitField<0, 6, u32> eye_type; + BitField<6, 3, u32> eye_color; + BitField<9, 4, u32> eye_scale; + BitField<13, 3, u32> eye_aspect; + BitField<16, 5, u32> eye_rotate; + BitField<21, 4, u32> eye_x; + BitField<25, 5, u32> eye_y; + } appearance_bits4; + union { + u32 raw; + + BitField<0, 5, u32> eyebrow_type; + BitField<5, 3, u32> eyebrow_color; + BitField<8, 4, u32> eyebrow_scale; + BitField<12, 3, u32> eyebrow_aspect; + BitField<16, 4, u32> eyebrow_rotate; + BitField<21, 4, u32> eyebrow_x; + BitField<25, 5, u32> eyebrow_y; + } appearance_bits5; + union { + u16 raw; + + BitField<0, 5, u16> nose_type; + BitField<5, 4, u16> nose_scale; + BitField<9, 5, u16> nose_y; + } appearance_bits6; + union { + u16 raw; + + BitField<0, 6, u16> mouth_type; + BitField<6, 3, u16> mouth_color; + BitField<9, 4, u16> mouth_scale; + BitField<13, 3, u16> mouth_aspect; + } appearance_bits7; + union { + u8 raw; + + BitField<0, 5, u8> mouth_y; + BitField<5, 3, u8> mustache_type; + } appearance_bits8; + u8 allow_copying; + union { + u16 raw; + + BitField<0, 3, u16> beard_type; + BitField<3, 3, u16> beard_color; + BitField<6, 4, u16> mustache_scale; + BitField<10, 5, u16> mustache_y; + } appearance_bits9; + union { + u16 raw; + + BitField<0, 4, u16> glass_type; + BitField<4, 3, u16> glass_color; + BitField<7, 4, u16> glass_scale; + BitField<11, 5, u16> glass_y; + } appearance_bits10; + union { + u16 raw; + + BitField<0, 1, u16> mole_type; + BitField<1, 4, u16> mole_scale; + BitField<5, 5, u16> mole_x; + BitField<10, 5, u16> mole_y; + } appearance_bits11; + + Nickname author_name; + INSERT_PADDING_BYTES(0x2); + u16_be crc; +}; +static_assert(sizeof(Ver3StoreData) == 0x60, "Ver3StoreData is an invalid size"); +#pragma pack(pop) + +}; // namespace Service::Mii diff --git a/src/core/hle/service/nfc/common/device.cpp b/src/core/hle/service/nfc/common/device.cpp index 49446bc42..05951d8cb 100644 --- a/src/core/hle/service/nfc/common/device.cpp +++ b/src/core/hle/service/nfc/common/device.cpp @@ -28,7 +28,6 @@ #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" @@ -440,6 +439,7 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target device_state = DeviceState::TagMounted; mount_target = mount_target_; + return ResultSuccess; } @@ -681,12 +681,16 @@ Result NfcDevice::GetRegisterInfo(NFP::RegisterInfo& register_info) const { return ResultRegistrationIsNotInitialized; } - Service::Mii::MiiManager manager; + Mii::CharInfo char_info{}; + Mii::StoreData store_data{}; + tag_data.owner_mii.BuildToStoreData(store_data); + char_info.SetFromStoreData(store_data); + const auto& settings = tag_data.settings; // TODO: Validate this data register_info = { - .mii_char_info = manager.ConvertV3ToCharInfo(tag_data.owner_mii), + .mii_char_info = char_info, .creation_date = settings.init_date.GetWriteDate(), .amiibo_name = GetAmiiboName(settings), .font_region = settings.settings.font_region, @@ -713,12 +717,13 @@ Result NfcDevice::GetRegisterInfoPrivate(NFP::RegisterInfoPrivate& register_info return ResultRegistrationIsNotInitialized; } - Service::Mii::MiiManager manager; + Mii::StoreData store_data{}; const auto& settings = tag_data.settings; + tag_data.owner_mii.BuildToStoreData(store_data); // TODO: Validate and complete this data register_info = { - .mii_store_data = {}, + .mii_store_data = store_data, .creation_date = settings.init_date.GetWriteDate(), .amiibo_name = GetAmiiboName(settings), .font_region = settings.settings.font_region, @@ -825,8 +830,11 @@ Result NfcDevice::SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& registe return ResultWrongDeviceState; } - Service::Mii::MiiManager manager; - const auto mii = manager.BuildDefault(0); + Service::Mii::StoreData store_data{}; + Service::Mii::NfpStoreDataExtension extension{}; + store_data.BuildBase(Mii::Gender::Male); + extension.SetFromStoreData(store_data); + auto& settings = tag_data.settings; if (tag_data.settings.settings.amiibo_initialized == 0) { @@ -835,8 +843,8 @@ Result NfcDevice::SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& registe } SetAmiiboName(settings, register_info.amiibo_name); - tag_data.owner_mii = manager.BuildFromStoreData(mii); - tag_data.mii_extension = manager.SetFromStoreData(mii); + tag_data.owner_mii.BuildFromStoreData(store_data); + tag_data.mii_extension = extension; tag_data.unknown = 0; tag_data.unknown2 = {}; settings.country_code_id = 0; @@ -868,17 +876,19 @@ Result NfcDevice::RestoreAmiibo() { } Result NfcDevice::Format() { - auto result1 = DeleteApplicationArea(); - auto result2 = DeleteRegisterInfo(); + Result result = ResultSuccess; - if (result1.IsError()) { - return result1; + if (device_state == DeviceState::TagFound) { + result = Mount(NFP::ModelType::Amiibo, NFP::MountTarget::All); } - if (result2.IsError()) { - return result2; + if (result.IsError()) { + return result; } + DeleteApplicationArea(); + DeleteRegisterInfo(); + return Flush(); } @@ -1364,7 +1374,7 @@ NFP::AmiiboName NfcDevice::GetAmiiboName(const NFP::AmiiboSettings& settings) co // 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()); + memcpy(amiibo_name.data(), amiibo_name_utf8.data(), amiibo_name_utf8.size() - 1); return amiibo_name; } @@ -1453,7 +1463,7 @@ void NfcDevice::UpdateRegisterInfoCrc() { void NfcDevice::BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data, const NFP::EncryptedNTAG215File& encrypted_file) const { - Service::Mii::MiiManager manager; + Service::Mii::StoreData store_data{}; auto& settings = stubbed_tag_data.settings; stubbed_tag_data = NFP::AmiiboCrypto::NfcDataToEncodedData(encrypted_file); @@ -1467,7 +1477,8 @@ void NfcDevice::BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data, 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)); + store_data.BuildBase(Mii::Gender::Male); + stubbed_tag_data.owner_mii.BuildFromStoreData(store_data); // Admin info settings.settings.amiibo_initialized.Assign(1); diff --git a/src/core/hle/service/nfp/nfp_types.h b/src/core/hle/service/nfp/nfp_types.h index aed12a7f8..f96d21220 100644 --- a/src/core/hle/service/nfp/nfp_types.h +++ b/src/core/hle/service/nfp/nfp_types.h @@ -6,7 +6,9 @@ #include <array> #include "common/swap.h" -#include "core/hle/service/mii/types.h" +#include "core/hle/service/mii/types/char_info.h" +#include "core/hle/service/mii/types/store_data.h" +#include "core/hle/service/mii/types/ver3_store_data.h" #include "core/hle/service/nfc/nfc_types.h" namespace Service::NFP { @@ -322,7 +324,7 @@ static_assert(sizeof(RegisterInfo) == 0x100, "RegisterInfo is an invalid size"); // This is nn::nfp::RegisterInfoPrivate struct RegisterInfoPrivate { - Service::Mii::MiiStoreData mii_store_data; + Service::Mii::StoreData mii_store_data; WriteDate creation_date; AmiiboName amiibo_name; u8 font_region; diff --git a/src/core/hle/service/ngc/ngc.cpp b/src/core/hle/service/ngc/ngc.cpp new file mode 100644 index 000000000..c26019ec0 --- /dev/null +++ b/src/core/hle/service/ngc/ngc.cpp @@ -0,0 +1,150 @@ +// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/string_util.h" +#include "core/core.h" +#include "core/hle/service/ipc_helpers.h" +#include "core/hle/service/ngc/ngc.h" +#include "core/hle/service/server_manager.h" +#include "core/hle/service/service.h" + +namespace Service::NGC { + +class NgctServiceImpl final : public ServiceFramework<NgctServiceImpl> { +public: + explicit NgctServiceImpl(Core::System& system_) : ServiceFramework{system_, "ngct:u"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &NgctServiceImpl::Match, "Match"}, + {1, &NgctServiceImpl::Filter, "Filter"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + +private: + void Match(HLERequestContext& ctx) { + const auto buffer = ctx.ReadBuffer(); + const auto text = Common::StringFromFixedZeroTerminatedBuffer( + reinterpret_cast<const char*>(buffer.data()), buffer.size()); + + LOG_WARNING(Service_NGC, "(STUBBED) called, text={}", text); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + // Return false since we don't censor anything + rb.Push(false); + } + + void Filter(HLERequestContext& ctx) { + const auto buffer = ctx.ReadBuffer(); + const auto text = Common::StringFromFixedZeroTerminatedBuffer( + reinterpret_cast<const char*>(buffer.data()), buffer.size()); + + LOG_WARNING(Service_NGC, "(STUBBED) called, text={}", text); + + // Return the same string since we don't censor anything + ctx.WriteBuffer(buffer); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } +}; + +class NgcServiceImpl final : public ServiceFramework<NgcServiceImpl> { +public: + explicit NgcServiceImpl(Core::System& system_) : ServiceFramework(system_, "ngc:u") { + // clang-format off + static const FunctionInfo functions[] = { + {0, &NgcServiceImpl::GetContentVersion, "GetContentVersion"}, + {1, &NgcServiceImpl::Check, "Check"}, + {2, &NgcServiceImpl::Mask, "Mask"}, + {3, &NgcServiceImpl::Reload, "Reload"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + +private: + static constexpr u32 NgcContentVersion = 1; + + // This is nn::ngc::detail::ProfanityFilterOption + struct ProfanityFilterOption { + INSERT_PADDING_BYTES_NOINIT(0x20); + }; + static_assert(sizeof(ProfanityFilterOption) == 0x20, + "ProfanityFilterOption has incorrect size"); + + void GetContentVersion(HLERequestContext& ctx) { + LOG_INFO(Service_NGC, "(STUBBED) called"); + + // This calls nn::ngc::ProfanityFilter::GetContentVersion + const u32 version = NgcContentVersion; + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(version); + } + + void Check(HLERequestContext& ctx) { + LOG_INFO(Service_NGC, "(STUBBED) called"); + + struct InputParameters { + u32 flags; + ProfanityFilterOption option; + }; + + IPC::RequestParser rp{ctx}; + [[maybe_unused]] const auto params = rp.PopRaw<InputParameters>(); + [[maybe_unused]] const auto input = ctx.ReadBuffer(0); + + // This calls nn::ngc::ProfanityFilter::CheckProfanityWords + const u32 out_flags = 0; + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(out_flags); + } + + void Mask(HLERequestContext& ctx) { + LOG_INFO(Service_NGC, "(STUBBED) called"); + + struct InputParameters { + u32 flags; + ProfanityFilterOption option; + }; + + IPC::RequestParser rp{ctx}; + [[maybe_unused]] const auto params = rp.PopRaw<InputParameters>(); + const auto input = ctx.ReadBuffer(0); + + // This calls nn::ngc::ProfanityFilter::MaskProfanityWordsInText + const u32 out_flags = 0; + ctx.WriteBuffer(input); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(out_flags); + } + + void Reload(HLERequestContext& ctx) { + LOG_INFO(Service_NGC, "(STUBBED) called"); + + // This reloads the database. + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); + } +}; + +void LoopProcess(Core::System& system) { + auto server_manager = std::make_unique<ServerManager>(system); + + server_manager->RegisterNamedService("ngct:u", std::make_shared<NgctServiceImpl>(system)); + server_manager->RegisterNamedService("ngc:u", std::make_shared<NgcServiceImpl>(system)); + ServerManager::RunServer(std::move(server_manager)); +} + +} // namespace Service::NGC diff --git a/src/core/hle/service/ngct/ngct.h b/src/core/hle/service/ngc/ngc.h index 27c34dad4..823b1aa81 100644 --- a/src/core/hle/service/ngct/ngct.h +++ b/src/core/hle/service/ngc/ngc.h @@ -7,8 +7,8 @@ namespace Core { class System; } -namespace Service::NGCT { +namespace Service::NGC { void LoopProcess(Core::System& system); -} // namespace Service::NGCT +} // namespace Service::NGC diff --git a/src/core/hle/service/ngct/ngct.cpp b/src/core/hle/service/ngct/ngct.cpp deleted file mode 100644 index 493c80ed2..000000000 --- a/src/core/hle/service/ngct/ngct.cpp +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "common/string_util.h" -#include "core/core.h" -#include "core/hle/service/ipc_helpers.h" -#include "core/hle/service/ngct/ngct.h" -#include "core/hle/service/server_manager.h" -#include "core/hle/service/service.h" - -namespace Service::NGCT { - -class IService final : public ServiceFramework<IService> { -public: - explicit IService(Core::System& system_) : ServiceFramework{system_, "ngct:u"} { - // clang-format off - static const FunctionInfo functions[] = { - {0, &IService::Match, "Match"}, - {1, &IService::Filter, "Filter"}, - }; - // clang-format on - - RegisterHandlers(functions); - } - -private: - void Match(HLERequestContext& ctx) { - const auto buffer = ctx.ReadBuffer(); - const auto text = Common::StringFromFixedZeroTerminatedBuffer( - reinterpret_cast<const char*>(buffer.data()), buffer.size()); - - LOG_WARNING(Service_NGCT, "(STUBBED) called, text={}", text); - - IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); - // Return false since we don't censor anything - rb.Push(false); - } - - void Filter(HLERequestContext& ctx) { - const auto buffer = ctx.ReadBuffer(); - const auto text = Common::StringFromFixedZeroTerminatedBuffer( - reinterpret_cast<const char*>(buffer.data()), buffer.size()); - - LOG_WARNING(Service_NGCT, "(STUBBED) called, text={}", text); - - // Return the same string since we don't censor anything - ctx.WriteBuffer(buffer); - - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultSuccess); - } -}; - -void LoopProcess(Core::System& system) { - auto server_manager = std::make_unique<ServerManager>(system); - - server_manager->RegisterNamedService("ngct:u", std::make_shared<IService>(system)); - ServerManager::RunServer(std::move(server_manager)); -} - -} // namespace Service::NGCT diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp index 376067a95..6e0baf0be 100644 --- a/src/core/hle/service/ns/ns.cpp +++ b/src/core/hle/service/ns/ns.cpp @@ -392,24 +392,25 @@ void IApplicationManagerInterface::GetApplicationDesiredLanguage(HLERequestConte IPC::RequestParser rp{ctx}; const auto supported_languages = rp.Pop<u32>(); - const auto res = GetApplicationDesiredLanguage(supported_languages); - if (res.Succeeded()) { + u8 desired_language{}; + const auto res = GetApplicationDesiredLanguage(&desired_language, supported_languages); + if (res == ResultSuccess) { IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push<u32>(*res); + rb.Push<u32>(desired_language); } else { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(res.Code()); + rb.Push(res); } } -ResultVal<u8> IApplicationManagerInterface::GetApplicationDesiredLanguage( - const u32 supported_languages) { +Result IApplicationManagerInterface::GetApplicationDesiredLanguage(u8* out_desired_language, + const u32 supported_languages) { LOG_DEBUG(Service_NS, "called with supported_languages={:08X}", supported_languages); // Get language code from settings const auto language_code = - Set::GetLanguageCodeFromIndex(Settings::values.language_index.GetValue()); + Set::GetLanguageCodeFromIndex(static_cast<s32>(Settings::values.language_index.GetValue())); // Convert to application language, get priority list const auto application_language = ConvertToApplicationLanguage(language_code); @@ -430,7 +431,8 @@ ResultVal<u8> IApplicationManagerInterface::GetApplicationDesiredLanguage( for (const auto lang : *priority_list) { const auto supported_flag = GetSupportedLanguageFlag(lang); if (supported_languages == 0 || (supported_languages & supported_flag) == supported_flag) { - return static_cast<u8>(lang); + *out_desired_language = static_cast<u8>(lang); + return ResultSuccess; } } @@ -444,19 +446,20 @@ void IApplicationManagerInterface::ConvertApplicationLanguageToLanguageCode( IPC::RequestParser rp{ctx}; const auto application_language = rp.Pop<u8>(); - const auto res = ConvertApplicationLanguageToLanguageCode(application_language); - if (res.Succeeded()) { + u64 language_code{}; + const auto res = ConvertApplicationLanguageToLanguageCode(&language_code, application_language); + if (res == ResultSuccess) { IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); - rb.Push(*res); + rb.Push(language_code); } else { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(res.Code()); + rb.Push(res); } } -ResultVal<u64> IApplicationManagerInterface::ConvertApplicationLanguageToLanguageCode( - u8 application_language) { +Result IApplicationManagerInterface::ConvertApplicationLanguageToLanguageCode( + u64* out_language_code, u8 application_language) { const auto language_code = ConvertToLanguageCode(static_cast<ApplicationLanguage>(application_language)); if (language_code == std::nullopt) { @@ -464,7 +467,8 @@ ResultVal<u64> IApplicationManagerInterface::ConvertApplicationLanguageToLanguag return Service::NS::ResultApplicationLanguageNotFound; } - return static_cast<u64>(*language_code); + *out_language_code = static_cast<u64>(*language_code); + return ResultSuccess; } IApplicationVersionInterface::IApplicationVersionInterface(Core::System& system_) @@ -618,12 +622,13 @@ void IReadOnlyApplicationControlDataInterface::GetApplicationControlData(HLERequ static_assert(sizeof(RequestParameters) == 0x10, "RequestParameters has incorrect size."); IPC::RequestParser rp{ctx}; + std::vector<u8> nacp_data{}; const auto parameters{rp.PopRaw<RequestParameters>()}; - const auto nacp_data{system.GetARPManager().GetControlProperty(parameters.application_id)}; - const auto result = nacp_data ? ResultSuccess : ResultUnknown; + const auto result = + system.GetARPManager().GetControlProperty(&nacp_data, parameters.application_id); - if (nacp_data) { - ctx.WriteBuffer(nacp_data->data(), nacp_data->size()); + if (result == ResultSuccess) { + ctx.WriteBuffer(nacp_data.data(), nacp_data.size()); } IPC::ResponseBuilder rb{ctx, 2}; diff --git a/src/core/hle/service/ns/ns.h b/src/core/hle/service/ns/ns.h index 203388e1f..175dad780 100644 --- a/src/core/hle/service/ns/ns.h +++ b/src/core/hle/service/ns/ns.h @@ -28,8 +28,9 @@ public: explicit IApplicationManagerInterface(Core::System& system_); ~IApplicationManagerInterface() override; - ResultVal<u8> GetApplicationDesiredLanguage(u32 supported_languages); - ResultVal<u64> ConvertApplicationLanguageToLanguageCode(u8 application_language); + Result GetApplicationDesiredLanguage(u8* out_desired_language, u32 supported_languages); + Result ConvertApplicationLanguageToLanguageCode(u64* out_language_code, + u8 application_language); private: void GetApplicationControlData(HLERequestContext& ctx); diff --git a/src/core/hle/service/nvdrv/core/nvmap.cpp b/src/core/hle/service/nvdrv/core/nvmap.cpp index a51ca5444..0ca05257e 100644 --- a/src/core/hle/service/nvdrv/core/nvmap.cpp +++ b/src/core/hle/service/nvdrv/core/nvmap.cpp @@ -160,8 +160,8 @@ u32 NvMap::PinHandle(NvMap::Handle::Id handle) { u32 address{}; auto& smmu_allocator = host1x.Allocator(); auto& smmu_memory_manager = host1x.MemoryManager(); - while (!(address = - smmu_allocator.Allocate(static_cast<u32>(handle_description->aligned_size)))) { + while ((address = smmu_allocator.Allocate( + static_cast<u32>(handle_description->aligned_size))) == 0) { // Free handles until the allocation succeeds std::scoped_lock queueLock(unmap_queue_lock); if (auto freeHandleDesc{unmap_queue.front()}) { diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp index 07e570a9f..7d7bb8687 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp @@ -204,9 +204,11 @@ void nvhost_as_gpu::FreeMappingLocked(u64 offset) { if (!mapping->fixed) { auto& allocator{mapping->big_page ? *vm.big_page_allocator : *vm.small_page_allocator}; u32 page_size_bits{mapping->big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS}; + u32 page_size{mapping->big_page ? vm.big_page_size : VM::YUZU_PAGESIZE}; + u64 aligned_size{Common::AlignUp(mapping->size, page_size)}; allocator.Free(static_cast<u32>(mapping->offset >> page_size_bits), - static_cast<u32>(mapping->size >> page_size_bits)); + static_cast<u32>(aligned_size >> page_size_bits)); } // Sparse mappings shouldn't be fully unmapped, just returned to their sparse state diff --git a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp index b16f9933f..dc6917d5d 100644 --- a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp +++ b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp @@ -449,6 +449,7 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input, case NativeWindowScalingMode::ScaleToWindow: case NativeWindowScalingMode::ScaleCrop: case NativeWindowScalingMode::NoScaleCrop: + case NativeWindowScalingMode::PreserveAspectRatio: break; default: LOG_ERROR(Service_Nvnflinger, "unknown scaling mode {}", scaling_mode); diff --git a/src/core/hle/service/nvnflinger/nvnflinger.cpp b/src/core/hle/service/nvnflinger/nvnflinger.cpp index 5f55cd31e..21f31f7a0 100644 --- a/src/core/hle/service/nvnflinger/nvnflinger.cpp +++ b/src/core/hle/service/nvnflinger/nvnflinger.cpp @@ -183,7 +183,7 @@ std::optional<u32> Nvnflinger::FindBufferQueueId(u64 display_id, u64 layer_id) { return layer->GetBinderId(); } -ResultVal<Kernel::KReadableEvent*> Nvnflinger::FindVsyncEvent(u64 display_id) { +Result Nvnflinger::FindVsyncEvent(Kernel::KReadableEvent** out_vsync_event, u64 display_id) { const auto lock_guard = Lock(); auto* const display = FindDisplay(display_id); @@ -191,7 +191,7 @@ ResultVal<Kernel::KReadableEvent*> Nvnflinger::FindVsyncEvent(u64 display_id) { return VI::ResultNotFound; } - return display->GetVSyncEvent(); + return display->GetVSyncEvent(out_vsync_event); } VI::Display* Nvnflinger::FindDisplay(u64 display_id) { diff --git a/src/core/hle/service/nvnflinger/nvnflinger.h b/src/core/hle/service/nvnflinger/nvnflinger.h index ef236303a..f478c2bc6 100644 --- a/src/core/hle/service/nvnflinger/nvnflinger.h +++ b/src/core/hle/service/nvnflinger/nvnflinger.h @@ -82,7 +82,7 @@ public: /// /// If an invalid display ID is provided, then VI::ResultNotFound is returned. /// If the vsync event has already been retrieved, then VI::ResultPermissionDenied is returned. - [[nodiscard]] ResultVal<Kernel::KReadableEvent*> FindVsyncEvent(u64 display_id); + [[nodiscard]] Result FindVsyncEvent(Kernel::KReadableEvent** out_vsync_event, u64 display_id); /// Performs a composition request to the emulated nvidia GPU and triggers the vsync events when /// finished. diff --git a/src/core/hle/service/nvnflinger/window.h b/src/core/hle/service/nvnflinger/window.h index 61cca5b01..36d6cde3d 100644 --- a/src/core/hle/service/nvnflinger/window.h +++ b/src/core/hle/service/nvnflinger/window.h @@ -41,6 +41,7 @@ enum class NativeWindowScalingMode : s32 { ScaleToWindow = 1, ScaleCrop = 2, NoScaleCrop = 3, + PreserveAspectRatio = 4, }; /// Transform parameter for QueueBuffer diff --git a/src/core/hle/service/olsc/olsc.cpp b/src/core/hle/service/olsc/olsc.cpp index 14ba67b4c..889f27c31 100644 --- a/src/core/hle/service/olsc/olsc.cpp +++ b/src/core/hle/service/olsc/olsc.cpp @@ -8,15 +8,16 @@ namespace Service::OLSC { -class OLSC final : public ServiceFramework<OLSC> { +class IOlscServiceForApplication final : public ServiceFramework<IOlscServiceForApplication> { public: - explicit OLSC(Core::System& system_) : ServiceFramework{system_, "olsc:u"} { + explicit IOlscServiceForApplication(Core::System& system_) + : ServiceFramework{system_, "olsc:u"} { // clang-format off static const FunctionInfo functions[] = { - {0, &OLSC::Initialize, "Initialize"}, + {0, &IOlscServiceForApplication::Initialize, "Initialize"}, {10, nullptr, "VerifySaveDataBackupLicenseAsync"}, - {13, &OLSC::GetSaveDataBackupSetting, "GetSaveDataBackupSetting"}, - {14, &OLSC::SetSaveDataBackupSettingEnabled, "SetSaveDataBackupSettingEnabled"}, + {13, &IOlscServiceForApplication::GetSaveDataBackupSetting, "GetSaveDataBackupSetting"}, + {14, &IOlscServiceForApplication::SetSaveDataBackupSettingEnabled, "SetSaveDataBackupSettingEnabled"}, {15, nullptr, "SetCustomData"}, {16, nullptr, "DeleteSaveDataBackupSetting"}, {18, nullptr, "GetSaveDataBackupInfoCache"}, @@ -72,10 +73,155 @@ private: bool initialized{}; }; +class INativeHandleHolder final : public ServiceFramework<INativeHandleHolder> { +public: + explicit INativeHandleHolder(Core::System& system_) + : ServiceFramework{system_, "INativeHandleHolder"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "GetNativeHandle"}, + }; + // clang-format on + + RegisterHandlers(functions); + } +}; + +class ITransferTaskListController final : public ServiceFramework<ITransferTaskListController> { +public: + explicit ITransferTaskListController(Core::System& system_) + : ServiceFramework{system_, "ITransferTaskListController"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "Unknown0"}, + {1, nullptr, "Unknown1"}, + {2, nullptr, "Unknown2"}, + {3, nullptr, "Unknown3"}, + {4, nullptr, "Unknown4"}, + {5, &ITransferTaskListController::GetNativeHandleHolder , "GetNativeHandleHolder"}, + {6, nullptr, "Unknown6"}, + {7, nullptr, "Unknown7"}, + {8, nullptr, "GetRemoteStorageController"}, + {9, &ITransferTaskListController::GetNativeHandleHolder, "GetNativeHandleHolder2"}, + {10, nullptr, "Unknown10"}, + {11, nullptr, "Unknown11"}, + {12, nullptr, "Unknown12"}, + {13, nullptr, "Unknown13"}, + {14, nullptr, "Unknown14"}, + {15, nullptr, "Unknown15"}, + {16, nullptr, "Unknown16"}, + {17, nullptr, "Unknown17"}, + {18, nullptr, "Unknown18"}, + {19, nullptr, "Unknown19"}, + {20, nullptr, "Unknown20"}, + {21, nullptr, "Unknown21"}, + {22, nullptr, "Unknown22"}, + {23, nullptr, "Unknown23"}, + {24, nullptr, "Unknown24"}, + {25, nullptr, "Unknown25"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + +private: + void GetNativeHandleHolder(HLERequestContext& ctx) { + LOG_INFO(Service_OLSC, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface<INativeHandleHolder>(system); + } +}; + +class IOlscServiceForSystemService final : public ServiceFramework<IOlscServiceForSystemService> { +public: + explicit IOlscServiceForSystemService(Core::System& system_) + : ServiceFramework{system_, "olsc:s"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &IOlscServiceForSystemService::OpenTransferTaskListController, "OpenTransferTaskListController"}, + {1, nullptr, "OpenRemoteStorageController"}, + {2, nullptr, "OpenDaemonController"}, + {10, nullptr, "Unknown10"}, + {11, nullptr, "Unknown11"}, + {12, nullptr, "Unknown12"}, + {13, nullptr, "Unknown13"}, + {100, nullptr, "ListLastTransferTaskErrorInfo"}, + {101, nullptr, "GetLastErrorInfoCount"}, + {102, nullptr, "RemoveLastErrorInfoOld"}, + {103, nullptr, "GetLastErrorInfo"}, + {104, nullptr, "GetLastErrorEventHolder"}, + {105, nullptr, "GetLastTransferTaskErrorInfo"}, + {200, nullptr, "GetDataTransferPolicyInfo"}, + {201, nullptr, "RemoveDataTransferPolicyInfo"}, + {202, nullptr, "UpdateDataTransferPolicyOld"}, + {203, nullptr, "UpdateDataTransferPolicy"}, + {204, nullptr, "CleanupDataTransferPolicyInfo"}, + {205, nullptr, "RequestDataTransferPolicy"}, + {300, nullptr, "GetAutoTransferSeriesInfo"}, + {301, nullptr, "UpdateAutoTransferSeriesInfo"}, + {400, nullptr, "CleanupSaveDataArchiveInfoType1"}, + {900, nullptr, "CleanupTransferTask"}, + {902, nullptr, "CleanupSeriesInfoType0"}, + {903, nullptr, "CleanupSaveDataArchiveInfoType0"}, + {904, nullptr, "CleanupApplicationAutoTransferSetting"}, + {905, nullptr, "CleanupErrorHistory"}, + {906, nullptr, "SetLastError"}, + {907, nullptr, "AddSaveDataArchiveInfoType0"}, + {908, nullptr, "RemoveSeriesInfoType0"}, + {909, nullptr, "GetSeriesInfoType0"}, + {910, nullptr, "RemoveLastErrorInfo"}, + {911, nullptr, "CleanupSeriesInfoType1"}, + {912, nullptr, "RemoveSeriesInfoType1"}, + {913, nullptr, "GetSeriesInfoType1"}, + {1000, nullptr, "UpdateIssueOld"}, + {1010, nullptr, "Unknown1010"}, + {1011, nullptr, "ListIssueInfoOld"}, + {1012, nullptr, "GetIssueOld"}, + {1013, nullptr, "GetIssue2Old"}, + {1014, nullptr, "GetIssue3Old"}, + {1020, nullptr, "RepairIssueOld"}, + {1021, nullptr, "RepairIssueWithUserIdOld"}, + {1022, nullptr, "RepairIssue2Old"}, + {1023, nullptr, "RepairIssue3Old"}, + {1024, nullptr, "Unknown1024"}, + {1100, nullptr, "UpdateIssue"}, + {1110, nullptr, "Unknown1110"}, + {1111, nullptr, "ListIssueInfo"}, + {1112, nullptr, "GetIssue"}, + {1113, nullptr, "GetIssue2"}, + {1114, nullptr, "GetIssue3"}, + {1120, nullptr, "RepairIssue"}, + {1121, nullptr, "RepairIssueWithUserId"}, + {1122, nullptr, "RepairIssue2"}, + {1123, nullptr, "RepairIssue3"}, + {1124, nullptr, "Unknown1124"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + +private: + void OpenTransferTaskListController(HLERequestContext& ctx) { + LOG_INFO(Service_OLSC, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(ResultSuccess); + rb.PushIpcInterface<ITransferTaskListController>(system); + } +}; + void LoopProcess(Core::System& system) { auto server_manager = std::make_unique<ServerManager>(system); - server_manager->RegisterNamedService("olsc:u", std::make_shared<OLSC>(system)); + server_manager->RegisterNamedService("olsc:u", + std::make_shared<IOlscServiceForApplication>(system)); + server_manager->RegisterNamedService("olsc:s", + std::make_shared<IOlscServiceForSystemService>(system)); + ServerManager::RunServer(std::move(server_manager)); } diff --git a/src/core/hle/service/pctl/pctl_module.cpp b/src/core/hle/service/pctl/pctl_module.cpp index f966c5c8b..5db1703d1 100644 --- a/src/core/hle/service/pctl/pctl_module.cpp +++ b/src/core/hle/service/pctl/pctl_module.cpp @@ -6,6 +6,7 @@ #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" #include "core/hle/service/ipc_helpers.h" +#include "core/hle/service/kernel_helpers.h" #include "core/hle/service/pctl/pctl.h" #include "core/hle/service/pctl/pctl_module.h" #include "core/hle/service/server_manager.h" @@ -24,7 +25,8 @@ constexpr Result ResultNoRestrictionEnabled{ErrorModule::PCTL, 181}; class IParentalControlService final : public ServiceFramework<IParentalControlService> { public: explicit IParentalControlService(Core::System& system_, Capability capability_) - : ServiceFramework{system_, "IParentalControlService"}, capability{capability_} { + : ServiceFramework{system_, "IParentalControlService"}, capability{capability_}, + service_context{system_, "IParentalControlService"} { // clang-format off static const FunctionInfo functions[] = { {1, &IParentalControlService::Initialize, "Initialize"}, @@ -33,7 +35,7 @@ public: {1003, nullptr, "ConfirmResumeApplicationPermission"}, {1004, nullptr, "ConfirmSnsPostPermission"}, {1005, nullptr, "ConfirmSystemSettingsPermission"}, - {1006, nullptr, "IsRestrictionTemporaryUnlocked"}, + {1006, &IParentalControlService::IsRestrictionTemporaryUnlocked, "IsRestrictionTemporaryUnlocked"}, {1007, nullptr, "RevertRestrictionTemporaryUnlocked"}, {1008, nullptr, "EnterRestrictedSystemSettings"}, {1009, nullptr, "LeaveRestrictedSystemSettings"}, @@ -47,14 +49,14 @@ public: {1017, &IParentalControlService::EndFreeCommunication, "EndFreeCommunication"}, {1018, &IParentalControlService::IsFreeCommunicationAvailable, "IsFreeCommunicationAvailable"}, {1031, &IParentalControlService::IsRestrictionEnabled, "IsRestrictionEnabled"}, - {1032, nullptr, "GetSafetyLevel"}, + {1032, &IParentalControlService::GetSafetyLevel, "GetSafetyLevel"}, {1033, nullptr, "SetSafetyLevel"}, {1034, nullptr, "GetSafetyLevelSettings"}, - {1035, nullptr, "GetCurrentSettings"}, + {1035, &IParentalControlService::GetCurrentSettings, "GetCurrentSettings"}, {1036, nullptr, "SetCustomSafetyLevelSettings"}, {1037, nullptr, "GetDefaultRatingOrganization"}, {1038, nullptr, "SetDefaultRatingOrganization"}, - {1039, nullptr, "GetFreeCommunicationApplicationListCount"}, + {1039, &IParentalControlService::GetFreeCommunicationApplicationListCount, "GetFreeCommunicationApplicationListCount"}, {1042, nullptr, "AddToFreeCommunicationApplicationList"}, {1043, nullptr, "DeleteSettings"}, {1044, nullptr, "GetFreeCommunicationApplicationList"}, @@ -76,7 +78,7 @@ public: {1206, nullptr, "GetPinCodeLength"}, {1207, nullptr, "GetPinCodeChangedEvent"}, {1208, nullptr, "GetPinCode"}, - {1403, nullptr, "IsPairingActive"}, + {1403, &IParentalControlService::IsPairingActive, "IsPairingActive"}, {1406, nullptr, "GetSettingsLastUpdated"}, {1411, nullptr, "GetPairingAccountInfo"}, {1421, nullptr, "GetAccountNickname"}, @@ -84,18 +86,18 @@ public: {1425, nullptr, "RequestPostEvents"}, {1426, nullptr, "GetPostEventInterval"}, {1427, nullptr, "SetPostEventInterval"}, - {1432, nullptr, "GetSynchronizationEvent"}, + {1432, &IParentalControlService::GetSynchronizationEvent, "GetSynchronizationEvent"}, {1451, nullptr, "StartPlayTimer"}, {1452, nullptr, "StopPlayTimer"}, {1453, nullptr, "IsPlayTimerEnabled"}, {1454, nullptr, "GetPlayTimerRemainingTime"}, {1455, nullptr, "IsRestrictedByPlayTimer"}, - {1456, nullptr, "GetPlayTimerSettings"}, - {1457, nullptr, "GetPlayTimerEventToRequestSuspension"}, - {1458, nullptr, "IsPlayTimerAlarmDisabled"}, + {1456, &IParentalControlService::GetPlayTimerSettings, "GetPlayTimerSettings"}, + {1457, &IParentalControlService::GetPlayTimerEventToRequestSuspension, "GetPlayTimerEventToRequestSuspension"}, + {1458, &IParentalControlService::IsPlayTimerAlarmDisabled, "IsPlayTimerAlarmDisabled"}, {1471, nullptr, "NotifyWrongPinCodeInputManyTimes"}, {1472, nullptr, "CancelNetworkRequest"}, - {1473, nullptr, "GetUnlinkedEvent"}, + {1473, &IParentalControlService::GetUnlinkedEvent, "GetUnlinkedEvent"}, {1474, nullptr, "ClearUnlinkedEvent"}, {1601, nullptr, "DisableAllFeatures"}, {1602, nullptr, "PostEnableAllFeatures"}, @@ -131,6 +133,12 @@ public: }; // clang-format on RegisterHandlers(functions); + + synchronization_event = + service_context.CreateEvent("IParentalControlService::SynchronizationEvent"); + unlinked_event = service_context.CreateEvent("IParentalControlService::UnlinkedEvent"); + request_suspension_event = + service_context.CreateEvent("IParentalControlService::RequestSuspensionEvent"); } private: @@ -228,6 +236,17 @@ private: states.free_communication = true; } + void IsRestrictionTemporaryUnlocked(HLERequestContext& ctx) { + const bool is_temporary_unlocked = false; + + LOG_WARNING(Service_PCTL, "(STUBBED) called, is_temporary_unlocked={}", + is_temporary_unlocked); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push<u8>(is_temporary_unlocked); + } + void ConfirmStereoVisionPermission(HLERequestContext& ctx) { LOG_DEBUG(Service_PCTL, "called"); states.stereo_vision = true; @@ -268,6 +287,34 @@ private: rb.Push(pin_code[0] != '\0'); } + void GetSafetyLevel(HLERequestContext& ctx) { + const u32 safety_level = 0; + + LOG_WARNING(Service_PCTL, "(STUBBED) called, safety_level={}", safety_level); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(safety_level); + } + + void GetCurrentSettings(HLERequestContext& ctx) { + LOG_INFO(Service_PCTL, "called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushRaw(restriction_settings); + } + + void GetFreeCommunicationApplicationListCount(HLERequestContext& ctx) { + const u32 count = 4; + + LOG_WARNING(Service_PCTL, "(STUBBED) called, count={}", count); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(count); + } + void ConfirmStereoVisionRestrictionConfigurable(HLERequestContext& ctx) { LOG_DEBUG(Service_PCTL, "called"); @@ -300,6 +347,61 @@ private: } } + void IsPairingActive(HLERequestContext& ctx) { + const bool is_pairing_active = false; + + LOG_WARNING(Service_PCTL, "(STUBBED) called, is_pairing_active={}", is_pairing_active); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push<u8>(is_pairing_active); + } + + void GetSynchronizationEvent(HLERequestContext& ctx) { + LOG_INFO(Service_PCTL, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(synchronization_event->GetReadableEvent()); + } + + void GetPlayTimerSettings(HLERequestContext& ctx) { + LOG_WARNING(Service_PCTL, "(STUBBED) called"); + + const PlayTimerSettings timer_settings{}; + + IPC::ResponseBuilder rb{ctx, 15}; + rb.Push(ResultSuccess); + rb.PushRaw(timer_settings); + } + + void GetPlayTimerEventToRequestSuspension(HLERequestContext& ctx) { + LOG_INFO(Service_PCTL, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(request_suspension_event->GetReadableEvent()); + } + + void IsPlayTimerAlarmDisabled(HLERequestContext& ctx) { + const bool is_play_timer_alarm_disabled = false; + + LOG_INFO(Service_PCTL, "called, is_play_timer_alarm_disabled={}", + is_play_timer_alarm_disabled); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push<u8>(is_play_timer_alarm_disabled); + } + + void GetUnlinkedEvent(HLERequestContext& ctx) { + LOG_INFO(Service_PCTL, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(ResultSuccess); + rb.PushCopyObjects(unlinked_event->GetReadableEvent()); + } + void SetStereoVisionRestriction(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const auto can_use = rp.Pop<bool>(); @@ -364,10 +466,30 @@ private: bool disabled{}; }; + // This is nn::pctl::RestrictionSettings + struct RestrictionSettings { + u8 rating_age; + bool sns_post_restriction; + bool free_communication_restriction; + }; + static_assert(sizeof(RestrictionSettings) == 0x3, "RestrictionSettings has incorrect size."); + + // This is nn::pctl::PlayTimerSettings + struct PlayTimerSettings { + std::array<u32, 13> settings; + }; + static_assert(sizeof(PlayTimerSettings) == 0x34, "PlayTimerSettings has incorrect size."); + States states{}; ParentalControlSettings settings{}; + RestrictionSettings restriction_settings{}; std::array<char, 8> pin_code{}; Capability capability{}; + + Kernel::KEvent* synchronization_event; + Kernel::KEvent* unlinked_event; + Kernel::KEvent* request_suspension_event; + KernelHelpers::ServiceContext service_context; }; void Module::Interface::CreateService(HLERequestContext& ctx) { diff --git a/src/core/hle/service/server_manager.cpp b/src/core/hle/service/server_manager.cpp index d1e99b184..e2e399534 100644 --- a/src/core/hle/service/server_manager.cpp +++ b/src/core/hle/service/server_manager.cpp @@ -102,16 +102,17 @@ Result ServerManager::RegisterNamedService(const std::string& service_name, m_system.ServiceManager().RegisterService(service_name, max_sessions, handler))); // Get the registered port. - auto port = m_system.ServiceManager().GetServicePort(service_name); - ASSERT(port.Succeeded()); + Kernel::KPort* port{}; + ASSERT( + R_SUCCEEDED(m_system.ServiceManager().GetServicePort(std::addressof(port), service_name))); // Open a new reference to the server port. - (*port)->GetServerPort().Open(); + port->GetServerPort().Open(); // Begin tracking the server port. { std::scoped_lock ll{m_list_mutex}; - m_ports.emplace(std::addressof((*port)->GetServerPort()), std::move(handler)); + m_ports.emplace(std::addressof(port->GetServerPort()), std::move(handler)); } // Signal the wakeup event. diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 69cdb5918..0ad607391 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -43,7 +43,7 @@ #include "core/hle/service/ncm/ncm.h" #include "core/hle/service/nfc/nfc.h" #include "core/hle/service/nfp/nfp.h" -#include "core/hle/service/ngct/ngct.h" +#include "core/hle/service/ngc/ngc.h" #include "core/hle/service/nifm/nifm.h" #include "core/hle/service/nim/nim.h" #include "core/hle/service/npns/npns.h" @@ -257,7 +257,7 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system kernel.RunOnGuestCoreProcess("NCM", [&] { NCM::LoopProcess(system); }); kernel.RunOnGuestCoreProcess("nfc", [&] { NFC::LoopProcess(system); }); kernel.RunOnGuestCoreProcess("nfp", [&] { NFP::LoopProcess(system); }); - kernel.RunOnGuestCoreProcess("ngct", [&] { NGCT::LoopProcess(system); }); + kernel.RunOnGuestCoreProcess("ngc", [&] { NGC::LoopProcess(system); }); kernel.RunOnGuestCoreProcess("nifm", [&] { NIFM::LoopProcess(system); }); kernel.RunOnGuestCoreProcess("nim", [&] { NIM::LoopProcess(system); }); kernel.RunOnGuestCoreProcess("npns", [&] { NPNS::LoopProcess(system); }); diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index 45b2c43b7..d539ed0f4 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h @@ -79,8 +79,8 @@ protected: using HandlerFnP = void (Self::*)(HLERequestContext&); /// Used to gain exclusive access to the service members, e.g. from CoreTiming thread. - [[nodiscard]] std::scoped_lock<std::mutex> LockService() { - return std::scoped_lock{lock_service}; + [[nodiscard]] virtual std::unique_lock<std::mutex> LockService() { + return std::unique_lock{lock_service}; } /// System context that the service operates under. diff --git a/src/core/hle/service/set/set.cpp b/src/core/hle/service/set/set.cpp index f5788b481..2082b8ef7 100644 --- a/src/core/hle/service/set/set.cpp +++ b/src/core/hle/service/set/set.cpp @@ -11,66 +11,6 @@ namespace Service::Set { namespace { -constexpr std::array<LanguageCode, 18> available_language_codes = {{ - LanguageCode::JA, - LanguageCode::EN_US, - LanguageCode::FR, - LanguageCode::DE, - LanguageCode::IT, - LanguageCode::ES, - LanguageCode::ZH_CN, - LanguageCode::KO, - LanguageCode::NL, - LanguageCode::PT, - LanguageCode::RU, - LanguageCode::ZH_TW, - LanguageCode::EN_GB, - LanguageCode::FR_CA, - LanguageCode::ES_419, - LanguageCode::ZH_HANS, - LanguageCode::ZH_HANT, - LanguageCode::PT_BR, -}}; - -enum class KeyboardLayout : u64 { - Japanese = 0, - EnglishUs = 1, - EnglishUsInternational = 2, - EnglishUk = 3, - French = 4, - FrenchCa = 5, - Spanish = 6, - SpanishLatin = 7, - German = 8, - Italian = 9, - Portuguese = 10, - Russian = 11, - Korean = 12, - ChineseSimplified = 13, - ChineseTraditional = 14, -}; - -constexpr std::array<std::pair<LanguageCode, KeyboardLayout>, 18> language_to_layout{{ - {LanguageCode::JA, KeyboardLayout::Japanese}, - {LanguageCode::EN_US, KeyboardLayout::EnglishUs}, - {LanguageCode::FR, KeyboardLayout::French}, - {LanguageCode::DE, KeyboardLayout::German}, - {LanguageCode::IT, KeyboardLayout::Italian}, - {LanguageCode::ES, KeyboardLayout::Spanish}, - {LanguageCode::ZH_CN, KeyboardLayout::ChineseSimplified}, - {LanguageCode::KO, KeyboardLayout::Korean}, - {LanguageCode::NL, KeyboardLayout::EnglishUsInternational}, - {LanguageCode::PT, KeyboardLayout::Portuguese}, - {LanguageCode::RU, KeyboardLayout::Russian}, - {LanguageCode::ZH_TW, KeyboardLayout::ChineseTraditional}, - {LanguageCode::EN_GB, KeyboardLayout::EnglishUk}, - {LanguageCode::FR_CA, KeyboardLayout::FrenchCa}, - {LanguageCode::ES_419, KeyboardLayout::SpanishLatin}, - {LanguageCode::ZH_HANS, KeyboardLayout::ChineseSimplified}, - {LanguageCode::ZH_HANT, KeyboardLayout::ChineseTraditional}, - {LanguageCode::PT_BR, KeyboardLayout::Portuguese}, -}}; - constexpr std::size_t PRE_4_0_0_MAX_ENTRIES = 0xF; constexpr std::size_t POST_4_0_0_MAX_ENTRIES = 0x40; @@ -93,7 +33,8 @@ void GetAvailableLanguageCodesImpl(HLERequestContext& ctx, std::size_t max_entri } void GetKeyCodeMapImpl(HLERequestContext& ctx) { - const auto language_code = available_language_codes[Settings::values.language_index.GetValue()]; + const auto language_code = + available_language_codes[static_cast<s32>(Settings::values.language_index.GetValue())]; const auto key_code = std::find_if(language_to_layout.cbegin(), language_to_layout.cend(), [=](const auto& element) { return element.first == language_code; }); @@ -162,7 +103,7 @@ void SET::GetQuestFlag(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(static_cast<u32>(Settings::values.quest_flag.GetValue())); + rb.Push(static_cast<s32>(Settings::values.quest_flag.GetValue())); } void SET::GetLanguageCode(HLERequestContext& ctx) { @@ -170,7 +111,8 @@ void SET::GetLanguageCode(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); - rb.PushEnum(available_language_codes[Settings::values.language_index.GetValue()]); + rb.PushEnum( + available_language_codes[static_cast<s32>(Settings::values.language_index.GetValue())]); } void SET::GetRegionCode(HLERequestContext& ctx) { @@ -178,7 +120,7 @@ void SET::GetRegionCode(HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 3}; rb.Push(ResultSuccess); - rb.Push(Settings::values.region_index.GetValue()); + rb.Push(static_cast<u32>(Settings::values.region_index.GetValue())); } void SET::GetKeyCodeMap(HLERequestContext& ctx) { diff --git a/src/core/hle/service/set/set.h b/src/core/hle/service/set/set.h index 7fd3a7654..b61a3560d 100644 --- a/src/core/hle/service/set/set.h +++ b/src/core/hle/service/set/set.h @@ -32,6 +32,67 @@ enum class LanguageCode : u64 { ZH_HANT = 0x00746E61482D687A, PT_BR = 0x00000052422D7470, }; + +enum class KeyboardLayout : u64 { + Japanese = 0, + EnglishUs = 1, + EnglishUsInternational = 2, + EnglishUk = 3, + French = 4, + FrenchCa = 5, + Spanish = 6, + SpanishLatin = 7, + German = 8, + Italian = 9, + Portuguese = 10, + Russian = 11, + Korean = 12, + ChineseSimplified = 13, + ChineseTraditional = 14, +}; + +constexpr std::array<LanguageCode, 18> available_language_codes = {{ + LanguageCode::JA, + LanguageCode::EN_US, + LanguageCode::FR, + LanguageCode::DE, + LanguageCode::IT, + LanguageCode::ES, + LanguageCode::ZH_CN, + LanguageCode::KO, + LanguageCode::NL, + LanguageCode::PT, + LanguageCode::RU, + LanguageCode::ZH_TW, + LanguageCode::EN_GB, + LanguageCode::FR_CA, + LanguageCode::ES_419, + LanguageCode::ZH_HANS, + LanguageCode::ZH_HANT, + LanguageCode::PT_BR, +}}; + +static constexpr std::array<std::pair<LanguageCode, KeyboardLayout>, 18> language_to_layout{{ + {LanguageCode::JA, KeyboardLayout::Japanese}, + {LanguageCode::EN_US, KeyboardLayout::EnglishUs}, + {LanguageCode::FR, KeyboardLayout::French}, + {LanguageCode::DE, KeyboardLayout::German}, + {LanguageCode::IT, KeyboardLayout::Italian}, + {LanguageCode::ES, KeyboardLayout::Spanish}, + {LanguageCode::ZH_CN, KeyboardLayout::ChineseSimplified}, + {LanguageCode::KO, KeyboardLayout::Korean}, + {LanguageCode::NL, KeyboardLayout::EnglishUsInternational}, + {LanguageCode::PT, KeyboardLayout::Portuguese}, + {LanguageCode::RU, KeyboardLayout::Russian}, + {LanguageCode::ZH_TW, KeyboardLayout::ChineseTraditional}, + {LanguageCode::EN_GB, KeyboardLayout::EnglishUk}, + {LanguageCode::FR_CA, KeyboardLayout::FrenchCa}, + {LanguageCode::ES_419, KeyboardLayout::SpanishLatin}, + {LanguageCode::ZH_HANS, KeyboardLayout::ChineseSimplified}, + {LanguageCode::ZH_HANT, KeyboardLayout::ChineseTraditional}, + {LanguageCode::PT_BR, KeyboardLayout::Portuguese}, +}}; + LanguageCode GetLanguageCodeFromIndex(std::size_t idx); class SET final : public ServiceFramework<SET> { diff --git a/src/core/hle/service/set/set_sys.cpp b/src/core/hle/service/set/set_sys.cpp index 2e38d1cfc..165b97dad 100644 --- a/src/core/hle/service/set/set_sys.cpp +++ b/src/core/hle/service/set/set_sys.cpp @@ -4,10 +4,12 @@ #include "common/assert.h" #include "common/logging/log.h" #include "common/settings.h" +#include "common/string_util.h" #include "core/file_sys/errors.h" #include "core/file_sys/system_archive/system_version.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/ipc_helpers.h" +#include "core/hle/service/set/set.h" #include "core/hle/service/set/set_sys.h" namespace Service::Set { @@ -73,6 +75,16 @@ void GetFirmwareVersionImpl(HLERequestContext& ctx, GetFirmwareVersionType type) } } // Anonymous namespace +void SET_SYS::SetLanguageCode(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + language_code_setting = rp.PopEnum<LanguageCode>(); + + LOG_INFO(Service_SET, "called, language_code={}", language_code_setting); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + void SET_SYS::GetFirmwareVersion(HLERequestContext& ctx) { LOG_DEBUG(Service_SET, "called"); GetFirmwareVersionImpl(ctx, GetFirmwareVersionType::Version1); @@ -83,21 +95,113 @@ void SET_SYS::GetFirmwareVersion2(HLERequestContext& ctx) { GetFirmwareVersionImpl(ctx, GetFirmwareVersionType::Version2); } +void SET_SYS::GetAccountSettings(HLERequestContext& ctx) { + LOG_INFO(Service_SET, "called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushRaw(account_settings); +} + +void SET_SYS::SetAccountSettings(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + account_settings = rp.PopRaw<AccountSettings>(); + + LOG_INFO(Service_SET, "called, account_settings_flags={}", account_settings.flags); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + +void SET_SYS::GetEulaVersions(HLERequestContext& ctx) { + LOG_INFO(Service_SET, "called"); + + ctx.WriteBuffer(eula_versions); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(static_cast<u32>(eula_versions.size())); +} + +void SET_SYS::SetEulaVersions(HLERequestContext& ctx) { + const auto elements = ctx.GetReadBufferNumElements<EulaVersion>(); + const auto buffer_data = ctx.ReadBuffer(); + + LOG_INFO(Service_SET, "called, elements={}", elements); + + eula_versions.resize(elements); + for (std::size_t index = 0; index < elements; index++) { + const std::size_t start_index = index * sizeof(EulaVersion); + memcpy(eula_versions.data() + start_index, buffer_data.data() + start_index, + sizeof(EulaVersion)); + } + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + void SET_SYS::GetColorSetId(HLERequestContext& ctx) { LOG_DEBUG(Service_SET, "called"); IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(ResultSuccess); rb.PushEnum(color_set); } void SET_SYS::SetColorSetId(HLERequestContext& ctx) { - LOG_DEBUG(Service_SET, "called"); - IPC::RequestParser rp{ctx}; color_set = rp.PopEnum<ColorSet>(); + LOG_DEBUG(Service_SET, "called, color_set={}", color_set); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + +void SET_SYS::GetNotificationSettings(HLERequestContext& ctx) { + LOG_INFO(Service_SET, "called"); + + IPC::ResponseBuilder rb{ctx, 8}; + rb.Push(ResultSuccess); + rb.PushRaw(notification_settings); +} + +void SET_SYS::SetNotificationSettings(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + notification_settings = rp.PopRaw<NotificationSettings>(); + + LOG_INFO(Service_SET, "called, flags={}, volume={}, head_time={}:{}, tailt_time={}:{}", + notification_settings.flags.raw, notification_settings.volume, + notification_settings.start_time.hour, notification_settings.start_time.minute, + notification_settings.stop_time.hour, notification_settings.stop_time.minute); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + +void SET_SYS::GetAccountNotificationSettings(HLERequestContext& ctx) { + LOG_INFO(Service_SET, "called"); + + ctx.WriteBuffer(account_notifications); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(static_cast<u32>(account_notifications.size())); +} + +void SET_SYS::SetAccountNotificationSettings(HLERequestContext& ctx) { + const auto elements = ctx.GetReadBufferNumElements<AccountNotificationSettings>(); + const auto buffer_data = ctx.ReadBuffer(); + + LOG_INFO(Service_SET, "called, elements={}", elements); + + account_notifications.resize(elements); + for (std::size_t index = 0; index < elements; index++) { + const std::size_t start_index = index * sizeof(AccountNotificationSettings); + memcpy(account_notifications.data() + start_index, buffer_data.data() + start_index, + sizeof(AccountNotificationSettings)); + } + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); } @@ -177,17 +281,218 @@ void SET_SYS::GetSettingsItemValue(HLERequestContext& ctx) { rb.Push(response); } +void SET_SYS::GetTvSettings(HLERequestContext& ctx) { + LOG_INFO(Service_SET, "called"); + + IPC::ResponseBuilder rb{ctx, 10}; + rb.Push(ResultSuccess); + rb.PushRaw(tv_settings); +} + +void SET_SYS::SetTvSettings(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + tv_settings = rp.PopRaw<TvSettings>(); + + LOG_INFO(Service_SET, + "called, flags={}, cmu_mode={}, constrast_ratio={}, hdmi_content_type={}, " + "rgb_range={}, tv_gama={}, tv_resolution={}, tv_underscan={}", + tv_settings.flags.raw, tv_settings.cmu_mode, tv_settings.constrast_ratio, + tv_settings.hdmi_content_type, tv_settings.rgb_range, tv_settings.tv_gama, + tv_settings.tv_resolution, tv_settings.tv_underscan); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + +void SET_SYS::GetQuestFlag(HLERequestContext& ctx) { + LOG_WARNING(Service_SET, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushEnum(QuestFlag::Retail); +} + +void SET_SYS::SetRegionCode(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + region_code = rp.PopEnum<RegionCode>(); + + LOG_INFO(Service_SET, "called, region_code={}", region_code); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + +void SET_SYS::GetPrimaryAlbumStorage(HLERequestContext& ctx) { + LOG_WARNING(Service_SET, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushEnum(PrimaryAlbumStorage::SdCard); +} + +void SET_SYS::GetSleepSettings(HLERequestContext& ctx) { + LOG_INFO(Service_SET, "called"); + + IPC::ResponseBuilder rb{ctx, 5}; + rb.Push(ResultSuccess); + rb.PushRaw(sleep_settings); +} + +void SET_SYS::SetSleepSettings(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + sleep_settings = rp.PopRaw<SleepSettings>(); + + LOG_INFO(Service_SET, "called, flags={}, handheld_sleep_plan={}, console_sleep_plan={}", + sleep_settings.flags.raw, sleep_settings.handheld_sleep_plan, + sleep_settings.console_sleep_plan); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + +void SET_SYS::GetInitialLaunchSettings(HLERequestContext& ctx) { + LOG_INFO(Service_SET, "called"); + IPC::ResponseBuilder rb{ctx, 10}; + rb.Push(ResultSuccess); + rb.PushRaw(launch_settings); +} + +void SET_SYS::SetInitialLaunchSettings(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + launch_settings = rp.PopRaw<InitialLaunchSettings>(); + + LOG_INFO(Service_SET, "called, flags={}, timestamp={}", launch_settings.flags.raw, + launch_settings.timestamp.time_point); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + void SET_SYS::GetDeviceNickName(HLERequestContext& ctx) { LOG_DEBUG(Service_SET, "called"); + + ctx.WriteBuffer(::Settings::values.device_name.GetValue()); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); - ctx.WriteBuffer(::Settings::values.device_name.GetValue()); +} + +void SET_SYS::SetDeviceNickName(HLERequestContext& ctx) { + const std::string device_name = Common::StringFromBuffer(ctx.ReadBuffer()); + + LOG_INFO(Service_SET, "called, device_name={}", device_name); + + ::Settings::values.device_name = device_name; + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + +void SET_SYS::GetProductModel(HLERequestContext& ctx) { + const u32 product_model = 1; + + LOG_WARNING(Service_SET, "(STUBBED) called, product_model={}", product_model); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(product_model); +} + +void SET_SYS::GetMiiAuthorId(HLERequestContext& ctx) { + const auto author_id = Common::UUID::MakeDefault(); + + LOG_WARNING(Service_SET, "(STUBBED) called, author_id={}", author_id.FormattedString()); + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(ResultSuccess); + rb.PushRaw(author_id); +} + +void SET_SYS::GetAutoUpdateEnableFlag(HLERequestContext& ctx) { + u8 auto_update_flag{}; + + LOG_WARNING(Service_SET, "(STUBBED) called, auto_update_flag={}", auto_update_flag); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(auto_update_flag); +} + +void SET_SYS::GetBatteryPercentageFlag(HLERequestContext& ctx) { + u8 battery_percentage_flag{1}; + + LOG_WARNING(Service_SET, "(STUBBED) called, battery_percentage_flag={}", + battery_percentage_flag); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(battery_percentage_flag); +} + +void SET_SYS::GetErrorReportSharePermission(HLERequestContext& ctx) { + LOG_WARNING(Service_SET, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushEnum(ErrorReportSharePermission::Denied); +} + +void SET_SYS::GetAppletLaunchFlags(HLERequestContext& ctx) { + LOG_INFO(Service_SET, "called, applet_launch_flag={}", applet_launch_flag); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(applet_launch_flag); +} + +void SET_SYS::SetAppletLaunchFlags(HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + applet_launch_flag = rp.Pop<u32>(); + + LOG_INFO(Service_SET, "called, applet_launch_flag={}", applet_launch_flag); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultSuccess); +} + +void SET_SYS::GetKeyboardLayout(HLERequestContext& ctx) { + const auto language_code = + available_language_codes[static_cast<s32>(::Settings::values.language_index.GetValue())]; + const auto key_code = + std::find_if(language_to_layout.cbegin(), language_to_layout.cend(), + [=](const auto& element) { return element.first == language_code; }); + + KeyboardLayout selected_keyboard_layout = KeyboardLayout::EnglishUs; + if (key_code != language_to_layout.end()) { + selected_keyboard_layout = key_code->second; + } + + LOG_INFO(Service_SET, "called, selected_keyboard_layout={}", selected_keyboard_layout); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push(static_cast<u32>(selected_keyboard_layout)); +} + +void SET_SYS::GetChineseTraditionalInputMethod(HLERequestContext& ctx) { + LOG_WARNING(Service_SET, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.PushEnum(ChineseTraditionalInputMethod::Unknown0); +} + +void SET_SYS::GetFieldTestingFlag(HLERequestContext& ctx) { + LOG_WARNING(Service_SET, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(ResultSuccess); + rb.Push<u8>(false); } SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} { // clang-format off static const FunctionInfo functions[] = { - {0, nullptr, "SetLanguageCode"}, + {0, &SET_SYS::SetLanguageCode, "SetLanguageCode"}, {1, nullptr, "SetNetworkSettings"}, {2, nullptr, "GetNetworkSettings"}, {3, &SET_SYS::GetFirmwareVersion, "GetFirmwareVersion"}, @@ -203,35 +508,35 @@ SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} { {14, nullptr, "SetExternalSteadyClockSourceId"}, {15, nullptr, "GetUserSystemClockContext"}, {16, nullptr, "SetUserSystemClockContext"}, - {17, nullptr, "GetAccountSettings"}, - {18, nullptr, "SetAccountSettings"}, + {17, &SET_SYS::GetAccountSettings, "GetAccountSettings"}, + {18, &SET_SYS::SetAccountSettings, "SetAccountSettings"}, {19, nullptr, "GetAudioVolume"}, {20, nullptr, "SetAudioVolume"}, - {21, nullptr, "GetEulaVersions"}, - {22, nullptr, "SetEulaVersions"}, + {21, &SET_SYS::GetEulaVersions, "GetEulaVersions"}, + {22, &SET_SYS::SetEulaVersions, "SetEulaVersions"}, {23, &SET_SYS::GetColorSetId, "GetColorSetId"}, {24, &SET_SYS::SetColorSetId, "SetColorSetId"}, {25, nullptr, "GetConsoleInformationUploadFlag"}, {26, nullptr, "SetConsoleInformationUploadFlag"}, {27, nullptr, "GetAutomaticApplicationDownloadFlag"}, {28, nullptr, "SetAutomaticApplicationDownloadFlag"}, - {29, nullptr, "GetNotificationSettings"}, - {30, nullptr, "SetNotificationSettings"}, - {31, nullptr, "GetAccountNotificationSettings"}, - {32, nullptr, "SetAccountNotificationSettings"}, + {29, &SET_SYS::GetNotificationSettings, "GetNotificationSettings"}, + {30, &SET_SYS::SetNotificationSettings, "SetNotificationSettings"}, + {31, &SET_SYS::GetAccountNotificationSettings, "GetAccountNotificationSettings"}, + {32, &SET_SYS::SetAccountNotificationSettings, "SetAccountNotificationSettings"}, {35, nullptr, "GetVibrationMasterVolume"}, {36, nullptr, "SetVibrationMasterVolume"}, {37, &SET_SYS::GetSettingsItemValueSize, "GetSettingsItemValueSize"}, {38, &SET_SYS::GetSettingsItemValue, "GetSettingsItemValue"}, - {39, nullptr, "GetTvSettings"}, - {40, nullptr, "SetTvSettings"}, + {39, &SET_SYS::GetTvSettings, "GetTvSettings"}, + {40, &SET_SYS::SetTvSettings, "SetTvSettings"}, {41, nullptr, "GetEdid"}, {42, nullptr, "SetEdid"}, {43, nullptr, "GetAudioOutputMode"}, {44, nullptr, "SetAudioOutputMode"}, {45, nullptr, "IsForceMuteOnHeadphoneRemoved"}, {46, nullptr, "SetForceMuteOnHeadphoneRemoved"}, - {47, nullptr, "GetQuestFlag"}, + {47, &SET_SYS::GetQuestFlag, "GetQuestFlag"}, {48, nullptr, "SetQuestFlag"}, {49, nullptr, "GetDataDeletionSettings"}, {50, nullptr, "SetDataDeletionSettings"}, @@ -241,13 +546,13 @@ SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} { {54, nullptr, "SetDeviceTimeZoneLocationName"}, {55, nullptr, "GetWirelessCertificationFileSize"}, {56, nullptr, "GetWirelessCertificationFile"}, - {57, nullptr, "SetRegionCode"}, + {57, &SET_SYS::SetRegionCode, "SetRegionCode"}, {58, nullptr, "GetNetworkSystemClockContext"}, {59, nullptr, "SetNetworkSystemClockContext"}, {60, nullptr, "IsUserSystemClockAutomaticCorrectionEnabled"}, {61, nullptr, "SetUserSystemClockAutomaticCorrectionEnabled"}, {62, nullptr, "GetDebugModeFlag"}, - {63, nullptr, "GetPrimaryAlbumStorage"}, + {63, &SET_SYS::GetPrimaryAlbumStorage, "GetPrimaryAlbumStorage"}, {64, nullptr, "SetPrimaryAlbumStorage"}, {65, nullptr, "GetUsb30EnableFlag"}, {66, nullptr, "SetUsb30EnableFlag"}, @@ -255,15 +560,15 @@ SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} { {68, nullptr, "GetSerialNumber"}, {69, nullptr, "GetNfcEnableFlag"}, {70, nullptr, "SetNfcEnableFlag"}, - {71, nullptr, "GetSleepSettings"}, - {72, nullptr, "SetSleepSettings"}, + {71, &SET_SYS::GetSleepSettings, "GetSleepSettings"}, + {72, &SET_SYS::SetSleepSettings, "SetSleepSettings"}, {73, nullptr, "GetWirelessLanEnableFlag"}, {74, nullptr, "SetWirelessLanEnableFlag"}, - {75, nullptr, "GetInitialLaunchSettings"}, - {76, nullptr, "SetInitialLaunchSettings"}, + {75, &SET_SYS::GetInitialLaunchSettings, "GetInitialLaunchSettings"}, + {76, &SET_SYS::SetInitialLaunchSettings, "SetInitialLaunchSettings"}, {77, &SET_SYS::GetDeviceNickName, "GetDeviceNickName"}, - {78, nullptr, "SetDeviceNickName"}, - {79, nullptr, "GetProductModel"}, + {78, &SET_SYS::SetDeviceNickName, "SetDeviceNickName"}, + {79, &SET_SYS::GetProductModel, "GetProductModel"}, {80, nullptr, "GetLdnChannel"}, {81, nullptr, "SetLdnChannel"}, {82, nullptr, "AcquireTelemetryDirtyFlagEventHandle"}, @@ -274,16 +579,16 @@ SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} { {87, nullptr, "SetPtmFuelGaugeParameter"}, {88, nullptr, "GetBluetoothEnableFlag"}, {89, nullptr, "SetBluetoothEnableFlag"}, - {90, nullptr, "GetMiiAuthorId"}, + {90, &SET_SYS::GetMiiAuthorId, "GetMiiAuthorId"}, {91, nullptr, "SetShutdownRtcValue"}, {92, nullptr, "GetShutdownRtcValue"}, {93, nullptr, "AcquireFatalDirtyFlagEventHandle"}, {94, nullptr, "GetFatalDirtyFlags"}, - {95, nullptr, "GetAutoUpdateEnableFlag"}, + {95, &SET_SYS::GetAutoUpdateEnableFlag, "GetAutoUpdateEnableFlag"}, {96, nullptr, "SetAutoUpdateEnableFlag"}, {97, nullptr, "GetNxControllerSettings"}, {98, nullptr, "SetNxControllerSettings"}, - {99, nullptr, "GetBatteryPercentageFlag"}, + {99, &SET_SYS::GetBatteryPercentageFlag, "GetBatteryPercentageFlag"}, {100, nullptr, "SetBatteryPercentageFlag"}, {101, nullptr, "GetExternalRtcResetFlag"}, {102, nullptr, "SetExternalRtcResetFlag"}, @@ -308,10 +613,10 @@ SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} { {121, nullptr, "SetPushNotificationActivityModeOnSleep"}, {122, nullptr, "GetServiceDiscoveryControlSettings"}, {123, nullptr, "SetServiceDiscoveryControlSettings"}, - {124, nullptr, "GetErrorReportSharePermission"}, + {124, &SET_SYS::GetErrorReportSharePermission, "GetErrorReportSharePermission"}, {125, nullptr, "SetErrorReportSharePermission"}, - {126, nullptr, "GetAppletLaunchFlags"}, - {127, nullptr, "SetAppletLaunchFlags"}, + {126, &SET_SYS::GetAppletLaunchFlags, "GetAppletLaunchFlags"}, + {127, &SET_SYS::SetAppletLaunchFlags, "SetAppletLaunchFlags"}, {128, nullptr, "GetConsoleSixAxisSensorAccelerationBias"}, {129, nullptr, "SetConsoleSixAxisSensorAccelerationBias"}, {130, nullptr, "GetConsoleSixAxisSensorAngularVelocityBias"}, @@ -320,7 +625,7 @@ SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} { {133, nullptr, "SetConsoleSixAxisSensorAccelerationGain"}, {134, nullptr, "GetConsoleSixAxisSensorAngularVelocityGain"}, {135, nullptr, "SetConsoleSixAxisSensorAngularVelocityGain"}, - {136, nullptr, "GetKeyboardLayout"}, + {136, &SET_SYS::GetKeyboardLayout, "GetKeyboardLayout"}, {137, nullptr, "SetKeyboardLayout"}, {138, nullptr, "GetWebInspectorFlag"}, {139, nullptr, "GetAllowedSslHosts"}, @@ -354,7 +659,7 @@ SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} { {167, nullptr, "SetUsb30DeviceEnableFlag"}, {168, nullptr, "GetThemeId"}, {169, nullptr, "SetThemeId"}, - {170, nullptr, "GetChineseTraditionalInputMethod"}, + {170, &SET_SYS::GetChineseTraditionalInputMethod, "GetChineseTraditionalInputMethod"}, {171, nullptr, "SetChineseTraditionalInputMethod"}, {172, nullptr, "GetPtmCycleCountReliability"}, {173, nullptr, "SetPtmCycleCountReliability"}, @@ -385,12 +690,16 @@ SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} { {198, nullptr, "SetButtonConfigRegisteredSettingsEmbedded"}, {199, nullptr, "GetButtonConfigRegisteredSettings"}, {200, nullptr, "SetButtonConfigRegisteredSettings"}, - {201, nullptr, "GetFieldTestingFlag"}, + {201, &SET_SYS::GetFieldTestingFlag, "GetFieldTestingFlag"}, {202, nullptr, "SetFieldTestingFlag"}, {203, nullptr, "GetPanelCrcMode"}, {204, nullptr, "SetPanelCrcMode"}, {205, nullptr, "GetNxControllerSettingsEx"}, {206, nullptr, "SetNxControllerSettingsEx"}, + {207, nullptr, "GetHearingProtectionSafeguardFlag"}, + {208, nullptr, "SetHearingProtectionSafeguardFlag"}, + {209, nullptr, "GetHearingProtectionSafeguardRemainingTime"}, + {210, nullptr, "SetHearingProtectionSafeguardRemainingTime"}, }; // clang-format on diff --git a/src/core/hle/service/set/set_sys.h b/src/core/hle/service/set/set_sys.h index 1efbcc97a..c7dba2a9e 100644 --- a/src/core/hle/service/set/set_sys.h +++ b/src/core/hle/service/set/set_sys.h @@ -3,7 +3,9 @@ #pragma once +#include "common/uuid.h" #include "core/hle/service/service.h" +#include "core/hle/service/time/clock_types.h" namespace Core { class System; @@ -23,15 +25,331 @@ private: BasicBlack = 1, }; - void GetSettingsItemValueSize(HLERequestContext& ctx); - void GetSettingsItemValue(HLERequestContext& ctx); + /// Indicates the current console is a retail or kiosk unit + enum class QuestFlag : u8 { + Retail = 0, + Kiosk = 1, + }; + + /// This is nn::settings::system::TvResolution + enum class TvResolution : u32 { + Auto, + Resolution1080p, + Resolution720p, + Resolution480p, + }; + + /// This is nn::settings::system::HdmiContentType + enum class HdmiContentType : u32 { + None, + Graphics, + Cinema, + Photo, + Game, + }; + + /// This is nn::settings::system::RgbRange + enum class RgbRange : u32 { + Auto, + Full, + Limited, + }; + + /// This is nn::settings::system::CmuMode + enum class CmuMode : u32 { + None, + ColorInvert, + HighContrast, + GrayScale, + }; + + /// This is nn::settings::system::PrimaryAlbumStorage + enum class PrimaryAlbumStorage : u32 { + Nand, + SdCard, + }; + + /// This is nn::settings::system::NotificationVolume + enum class NotificationVolume : u32 { + Mute, + Low, + High, + }; + + /// This is nn::settings::system::ChineseTraditionalInputMethod + enum class ChineseTraditionalInputMethod : u32 { + Unknown0 = 0, + Unknown1 = 1, + Unknown2 = 2, + }; + + /// This is nn::settings::system::ErrorReportSharePermission + enum class ErrorReportSharePermission : u32 { + NotConfirmed, + Granted, + Denied, + }; + + /// This is nn::settings::system::FriendPresenceOverlayPermission + enum class FriendPresenceOverlayPermission : u8 { + NotConfirmed, + NoDisplay, + FavoriteFriends, + Friends, + }; + + /// This is nn::settings::system::HandheldSleepPlan + enum class HandheldSleepPlan : u32 { + Sleep1Min, + Sleep3Min, + Sleep5Min, + Sleep10Min, + Sleep30Min, + Never, + }; + + /// This is nn::settings::system::ConsoleSleepPlan + enum class ConsoleSleepPlan : u32 { + Sleep1Hour, + Sleep2Hour, + Sleep3Hour, + Sleep6Hour, + Sleep12Hour, + Never, + }; + + /// This is nn::settings::system::RegionCode + enum class RegionCode : u32 { + Japan, + Usa, + Europe, + Australia, + HongKongTaiwanKorea, + China, + }; + + /// This is nn::settings::system::EulaVersionClockType + enum class EulaVersionClockType : u32 { + NetworkSystemClock, + SteadyClock, + }; + + /// This is nn::settings::system::SleepFlag + struct SleepFlag { + union { + u32 raw{}; + + BitField<0, 1, u32> SleepsWhilePlayingMedia; + BitField<1, 1, u32> WakesAtPowerStateChange; + }; + }; + static_assert(sizeof(SleepFlag) == 4, "TvFlag is an invalid size"); + + /// This is nn::settings::system::TvFlag + struct TvFlag { + union { + u32 raw{}; + + BitField<0, 1, u32> Allows4k; + BitField<1, 1, u32> Allows3d; + BitField<2, 1, u32> AllowsCec; + BitField<3, 1, u32> PreventsScreenBurnIn; + }; + }; + static_assert(sizeof(TvFlag) == 4, "TvFlag is an invalid size"); + + /// This is nn::settings::system::InitialLaunchFlag + struct InitialLaunchFlag { + union { + u32 raw{}; + + BitField<0, 1, u32> InitialLaunchCompletionFlag; + BitField<8, 1, u32> InitialLaunchUserAdditionFlag; + BitField<16, 1, u32> InitialLaunchTimestampFlag; + }; + }; + static_assert(sizeof(InitialLaunchFlag) == 4, "InitialLaunchFlag is an invalid size"); + + /// This is nn::settings::system::NotificationFlag + struct NotificationFlag { + union { + u32 raw{}; + + BitField<0, 1, u32> RingtoneFlag; + BitField<1, 1, u32> DownloadCompletionFlag; + BitField<8, 1, u32> EnablesNews; + BitField<9, 1, u32> IncomingLampFlag; + }; + }; + static_assert(sizeof(NotificationFlag) == 4, "NotificationFlag is an invalid size"); + + /// This is nn::settings::system::AccountNotificationFlag + struct AccountNotificationFlag { + union { + u32 raw{}; + + BitField<0, 1, u32> FriendOnlineFlag; + BitField<1, 1, u32> FriendRequestFlag; + BitField<8, 1, u32> CoralInvitationFlag; + }; + }; + static_assert(sizeof(AccountNotificationFlag) == 4, + "AccountNotificationFlag is an invalid size"); + + /// This is nn::settings::system::TvSettings + struct TvSettings { + TvFlag flags; + TvResolution tv_resolution; + HdmiContentType hdmi_content_type; + RgbRange rgb_range; + CmuMode cmu_mode; + u32 tv_underscan; + f32 tv_gama; + f32 constrast_ratio; + }; + static_assert(sizeof(TvSettings) == 0x20, "TvSettings is an invalid size"); + + /// This is nn::settings::system::NotificationTime + struct NotificationTime { + u32 hour; + u32 minute; + }; + static_assert(sizeof(NotificationTime) == 0x8, "NotificationTime is an invalid size"); + + /// This is nn::settings::system::NotificationSettings + struct NotificationSettings { + NotificationFlag flags; + NotificationVolume volume; + NotificationTime start_time; + NotificationTime stop_time; + }; + static_assert(sizeof(NotificationSettings) == 0x18, "NotificationSettings is an invalid size"); + + /// This is nn::settings::system::AccountSettings + struct AccountSettings { + u32 flags; + }; + static_assert(sizeof(AccountSettings) == 0x4, "AccountSettings is an invalid size"); + + /// This is nn::settings::system::AccountNotificationSettings + struct AccountNotificationSettings { + Common::UUID uid; + AccountNotificationFlag flags; + FriendPresenceOverlayPermission friend_presence_permission; + FriendPresenceOverlayPermission friend_invitation_permission; + INSERT_PADDING_BYTES(0x2); + }; + static_assert(sizeof(AccountNotificationSettings) == 0x18, + "AccountNotificationSettings is an invalid size"); + + /// This is nn::settings::system::InitialLaunchSettings + struct SleepSettings { + SleepFlag flags; + HandheldSleepPlan handheld_sleep_plan; + ConsoleSleepPlan console_sleep_plan; + }; + static_assert(sizeof(SleepSettings) == 0xc, "SleepSettings is incorrect size"); + + /// This is nn::settings::system::InitialLaunchSettings + struct InitialLaunchSettings { + InitialLaunchFlag flags; + INSERT_PADDING_BYTES(0x4); + Time::Clock::SteadyClockTimePoint timestamp; + }; + static_assert(sizeof(InitialLaunchSettings) == 0x20, "InitialLaunchSettings is incorrect size"); + + /// This is nn::settings::system::InitialLaunchSettings + struct EulaVersion { + u32 version; + RegionCode region_code; + EulaVersionClockType clock_type; + INSERT_PADDING_BYTES(0x4); + s64 posix_time; + Time::Clock::SteadyClockTimePoint timestamp; + }; + static_assert(sizeof(EulaVersion) == 0x30, "EulaVersion is incorrect size"); + + void SetLanguageCode(HLERequestContext& ctx); void GetFirmwareVersion(HLERequestContext& ctx); void GetFirmwareVersion2(HLERequestContext& ctx); + void GetAccountSettings(HLERequestContext& ctx); + void SetAccountSettings(HLERequestContext& ctx); + void GetEulaVersions(HLERequestContext& ctx); + void SetEulaVersions(HLERequestContext& ctx); void GetColorSetId(HLERequestContext& ctx); void SetColorSetId(HLERequestContext& ctx); + void GetNotificationSettings(HLERequestContext& ctx); + void SetNotificationSettings(HLERequestContext& ctx); + void GetAccountNotificationSettings(HLERequestContext& ctx); + void SetAccountNotificationSettings(HLERequestContext& ctx); + void GetSettingsItemValueSize(HLERequestContext& ctx); + void GetSettingsItemValue(HLERequestContext& ctx); + void GetTvSettings(HLERequestContext& ctx); + void SetTvSettings(HLERequestContext& ctx); + void GetQuestFlag(HLERequestContext& ctx); + void SetRegionCode(HLERequestContext& ctx); + void GetPrimaryAlbumStorage(HLERequestContext& ctx); + void GetSleepSettings(HLERequestContext& ctx); + void SetSleepSettings(HLERequestContext& ctx); + void GetInitialLaunchSettings(HLERequestContext& ctx); + void SetInitialLaunchSettings(HLERequestContext& ctx); void GetDeviceNickName(HLERequestContext& ctx); + void SetDeviceNickName(HLERequestContext& ctx); + void GetProductModel(HLERequestContext& ctx); + void GetMiiAuthorId(HLERequestContext& ctx); + void GetAutoUpdateEnableFlag(HLERequestContext& ctx); + void GetBatteryPercentageFlag(HLERequestContext& ctx); + void GetErrorReportSharePermission(HLERequestContext& ctx); + void GetAppletLaunchFlags(HLERequestContext& ctx); + void SetAppletLaunchFlags(HLERequestContext& ctx); + void GetKeyboardLayout(HLERequestContext& ctx); + void GetChineseTraditionalInputMethod(HLERequestContext& ctx); + void GetFieldTestingFlag(HLERequestContext& ctx); + + AccountSettings account_settings{ + .flags = {}, + }; ColorSet color_set = ColorSet::BasicWhite; + + NotificationSettings notification_settings{ + .flags = {0x300}, + .volume = NotificationVolume::High, + .start_time = {.hour = 9, .minute = 0}, + .stop_time = {.hour = 21, .minute = 0}, + }; + + std::vector<AccountNotificationSettings> account_notifications{}; + + TvSettings tv_settings{ + .flags = {0xc}, + .tv_resolution = TvResolution::Auto, + .hdmi_content_type = HdmiContentType::Game, + .rgb_range = RgbRange::Auto, + .cmu_mode = CmuMode::None, + .tv_underscan = {}, + .tv_gama = 1.0f, + .constrast_ratio = 0.5f, + }; + + InitialLaunchSettings launch_settings{ + .flags = {0x10001}, + .timestamp = {}, + }; + + SleepSettings sleep_settings{ + .flags = {0x3}, + .handheld_sleep_plan = HandheldSleepPlan::Sleep10Min, + .console_sleep_plan = ConsoleSleepPlan::Sleep1Hour, + }; + + u32 applet_launch_flag{}; + + std::vector<EulaVersion> eula_versions{}; + + RegionCode region_code; + + LanguageCode language_code_setting; }; } // namespace Service::Set diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp index 1608fa24c..9ab718e0a 100644 --- a/src/core/hle/service/sm/sm.cpp +++ b/src/core/hle/service/sm/sm.cpp @@ -52,8 +52,7 @@ static Result ValidateServiceName(const std::string& name) { Result ServiceManager::RegisterService(std::string name, u32 max_sessions, SessionRequestHandlerPtr handler) { - - CASCADE_CODE(ValidateServiceName(name)); + R_TRY(ValidateServiceName(name)); std::scoped_lock lk{lock}; if (registered_services.find(name) != registered_services.end()) { @@ -77,7 +76,7 @@ Result ServiceManager::RegisterService(std::string name, u32 max_sessions, } Result ServiceManager::UnregisterService(const std::string& name) { - CASCADE_CODE(ValidateServiceName(name)); + R_TRY(ValidateServiceName(name)); std::scoped_lock lk{lock}; const auto iter = registered_services.find(name); @@ -92,8 +91,8 @@ Result ServiceManager::UnregisterService(const std::string& name) { return ResultSuccess; } -ResultVal<Kernel::KPort*> ServiceManager::GetServicePort(const std::string& name) { - CASCADE_CODE(ValidateServiceName(name)); +Result ServiceManager::GetServicePort(Kernel::KPort** out_port, const std::string& name) { + R_TRY(ValidateServiceName(name)); std::scoped_lock lk{lock}; auto it = service_ports.find(name); @@ -102,7 +101,8 @@ ResultVal<Kernel::KPort*> ServiceManager::GetServicePort(const std::string& name return Service::SM::ResultNotRegistered; } - return it->second; + *out_port = it->second; + return ResultSuccess; } /** @@ -122,32 +122,34 @@ void SM::Initialize(HLERequestContext& ctx) { } void SM::GetService(HLERequestContext& ctx) { - auto result = GetServiceImpl(ctx); + Kernel::KClientSession* client_session{}; + auto result = GetServiceImpl(&client_session, ctx); if (ctx.GetIsDeferred()) { // Don't overwrite the command buffer. return; } - if (result.Succeeded()) { + if (result == ResultSuccess) { IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles}; - rb.Push(result.Code()); - rb.PushMoveObjects(result.Unwrap()); + rb.Push(result); + rb.PushMoveObjects(client_session); } else { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(result.Code()); + rb.Push(result); } } void SM::GetServiceTipc(HLERequestContext& ctx) { - auto result = GetServiceImpl(ctx); + Kernel::KClientSession* client_session{}; + auto result = GetServiceImpl(&client_session, ctx); if (ctx.GetIsDeferred()) { // Don't overwrite the command buffer. return; } IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles}; - rb.Push(result.Code()); - rb.PushMoveObjects(result.Succeeded() ? result.Unwrap() : nullptr); + rb.Push(result); + rb.PushMoveObjects(result == ResultSuccess ? client_session : nullptr); } static std::string PopServiceName(IPC::RequestParser& rp) { @@ -161,7 +163,7 @@ static std::string PopServiceName(IPC::RequestParser& rp) { return result; } -ResultVal<Kernel::KClientSession*> SM::GetServiceImpl(HLERequestContext& ctx) { +Result SM::GetServiceImpl(Kernel::KClientSession** out_client_session, HLERequestContext& ctx) { if (!ctx.GetManager()->GetIsInitializedForSm()) { return Service::SM::ResultInvalidClient; } @@ -170,18 +172,18 @@ ResultVal<Kernel::KClientSession*> SM::GetServiceImpl(HLERequestContext& ctx) { std::string name(PopServiceName(rp)); // Find the named port. - auto port_result = service_manager.GetServicePort(name); - if (port_result.Code() == Service::SM::ResultInvalidServiceName) { + Kernel::KPort* port{}; + auto port_result = service_manager.GetServicePort(&port, name); + if (port_result == Service::SM::ResultInvalidServiceName) { LOG_ERROR(Service_SM, "Invalid service name '{}'", name); return Service::SM::ResultInvalidServiceName; } - if (port_result.Failed()) { + if (port_result != ResultSuccess) { LOG_INFO(Service_SM, "Waiting for service {} to become available", name); ctx.SetIsDeferred(); return Service::SM::ResultNotRegistered; } - auto& port = port_result.Unwrap(); // Create a new session. Kernel::KClientSession* session{}; @@ -192,7 +194,8 @@ ResultVal<Kernel::KClientSession*> SM::GetServiceImpl(HLERequestContext& ctx) { LOG_DEBUG(Service_SM, "called service={} -> session={}", name, session->GetId()); - return session; + *out_client_session = session; + return ResultSuccess; } void SM::RegisterService(HLERequestContext& ctx) { diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h index 6697f4007..14bfaf8c2 100644 --- a/src/core/hle/service/sm/sm.h +++ b/src/core/hle/service/sm/sm.h @@ -42,7 +42,7 @@ private: void RegisterService(HLERequestContext& ctx); void UnregisterService(HLERequestContext& ctx); - ResultVal<Kernel::KClientSession*> GetServiceImpl(HLERequestContext& ctx); + Result GetServiceImpl(Kernel::KClientSession** out_client_session, HLERequestContext& ctx); ServiceManager& service_manager; Kernel::KernelCore& kernel; @@ -55,7 +55,7 @@ public: Result RegisterService(std::string name, u32 max_sessions, SessionRequestHandlerPtr handler); Result UnregisterService(const std::string& name); - ResultVal<Kernel::KPort*> GetServicePort(const std::string& name); + Result GetServicePort(Kernel::KPort** out_port, const std::string& name); template <Common::DerivedFrom<SessionRequestHandler> T> std::shared_ptr<T> GetService(const std::string& service_name) const { diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp index 11f8efbac..85849d5f3 100644 --- a/src/core/hle/service/sockets/bsd.cpp +++ b/src/core/hle/service/sockets/bsd.cpp @@ -170,7 +170,7 @@ void BSD::Socket(HLERequestContext& ctx) { } void BSD::Select(HLERequestContext& ctx) { - LOG_WARNING(Service, "(STUBBED) called"); + LOG_DEBUG(Service, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 4}; @@ -1029,6 +1029,11 @@ BSD::~BSD() { } } +std::unique_lock<std::mutex> BSD::LockService() { + // Do not lock socket IClient instances. + return {}; +} + BSDCFG::BSDCFG(Core::System& system_) : ServiceFramework{system_, "bsdcfg"} { // clang-format off static const FunctionInfo functions[] = { diff --git a/src/core/hle/service/sockets/bsd.h b/src/core/hle/service/sockets/bsd.h index 430edb97c..161f22b9b 100644 --- a/src/core/hle/service/sockets/bsd.h +++ b/src/core/hle/service/sockets/bsd.h @@ -186,6 +186,9 @@ private: // Callback identifier for the OnProxyPacketReceived event. Network::RoomMember::CallbackHandle<Network::ProxyPacket> proxy_packet_received; + +protected: + virtual std::unique_lock<std::mutex> LockService() override; }; class BSDCFG final : public ServiceFramework<BSDCFG> { diff --git a/src/core/hle/service/sockets/nsd.cpp b/src/core/hle/service/sockets/nsd.cpp index 5dfcaabb1..491b76d48 100644 --- a/src/core/hle/service/sockets/nsd.cpp +++ b/src/core/hle/service/sockets/nsd.cpp @@ -19,6 +19,12 @@ enum class ServerEnvironmentType : u8 { Dp, }; +// This is nn::nsd::EnvironmentIdentifier +struct EnvironmentIdentifier { + std::array<u8, 8> identifier; +}; +static_assert(sizeof(EnvironmentIdentifier) == 0x8); + NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, name} { // clang-format off static const FunctionInfo functions[] = { @@ -54,7 +60,7 @@ NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, na RegisterHandlers(functions); } -static ResultVal<std::string> ResolveImpl(const std::string& fqdn_in) { +static std::string ResolveImpl(const std::string& fqdn_in) { // The real implementation makes various substitutions. // For now we just return the string as-is, which is good enough when not // connecting to real Nintendo servers. @@ -64,13 +70,10 @@ static ResultVal<std::string> ResolveImpl(const std::string& fqdn_in) { static Result ResolveCommon(const std::string& fqdn_in, std::array<char, 0x100>& fqdn_out) { const auto res = ResolveImpl(fqdn_in); - if (res.Failed()) { - return res.Code(); - } - if (res->size() >= fqdn_out.size()) { + if (res.size() >= fqdn_out.size()) { return ResultOverflow; } - std::memcpy(fqdn_out.data(), res->c_str(), res->size() + 1); + std::memcpy(fqdn_out.data(), res.c_str(), res.size() + 1); return ResultSuccess; } @@ -104,8 +107,9 @@ void NSD::ResolveEx(HLERequestContext& ctx) { } void NSD::GetEnvironmentIdentifier(HLERequestContext& ctx) { - const std::string environment_identifier = "lp1"; - ctx.WriteBuffer(environment_identifier); + constexpr EnvironmentIdentifier lp1 = { + .identifier = {'l', 'p', '1', '\0', '\0', '\0', '\0', '\0'}}; + ctx.WriteBuffer(lp1); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(ResultSuccess); diff --git a/src/core/hle/service/sockets/sfdnsres.cpp b/src/core/hle/service/sockets/sfdnsres.cpp index 22e4a6f49..c657c4efd 100644 --- a/src/core/hle/service/sockets/sfdnsres.cpp +++ b/src/core/hle/service/sockets/sfdnsres.cpp @@ -150,6 +150,12 @@ static std::pair<u32, GetAddrInfoError> GetHostByNameRequestImpl(HLERequestConte const std::string host = Common::StringFromBuffer(host_buffer); // For now, ignore options, which are in input buffer 1 for GetHostByNameRequestWithOptions. + // Prevent resolution of Nintendo servers + if (host.find("srv.nintendo.net") != std::string::npos) { + LOG_WARNING(Network, "Resolution of hostname {} requested, returning EAI_AGAIN", host); + return {0, GetAddrInfoError::AGAIN}; + } + auto res = Network::GetAddressInfo(host, /*service*/ std::nullopt); if (!res.has_value()) { return {0, Translate(res.error())}; @@ -261,6 +267,12 @@ static std::pair<u32, GetAddrInfoError> GetAddrInfoRequestImpl(HLERequestContext const auto host_buffer = ctx.ReadBuffer(0); const std::string host = Common::StringFromBuffer(host_buffer); + // Prevent resolution of Nintendo servers + if (host.find("srv.nintendo.net") != std::string::npos) { + LOG_WARNING(Network, "Resolution of hostname {} requested, returning EAI_AGAIN", host); + return {0, GetAddrInfoError::AGAIN}; + } + std::optional<std::string> service = std::nullopt; if (ctx.CanReadBuffer(1)) { const std::span<const u8> service_buffer = ctx.ReadBuffer(1); diff --git a/src/core/hle/service/sockets/sockets.h b/src/core/hle/service/sockets/sockets.h index 77426c46e..f86af01a4 100644 --- a/src/core/hle/service/sockets/sockets.h +++ b/src/core/hle/service/sockets/sockets.h @@ -18,7 +18,9 @@ enum class Errno : u32 { AGAIN = 11, INVAL = 22, MFILE = 24, + PIPE = 32, MSGSIZE = 90, + CONNABORTED = 103, CONNRESET = 104, NOTCONN = 107, TIMEDOUT = 110, diff --git a/src/core/hle/service/sockets/sockets_translate.cpp b/src/core/hle/service/sockets/sockets_translate.cpp index c1187209f..aed05250c 100644 --- a/src/core/hle/service/sockets/sockets_translate.cpp +++ b/src/core/hle/service/sockets/sockets_translate.cpp @@ -23,10 +23,14 @@ Errno Translate(Network::Errno value) { return Errno::INVAL; case Network::Errno::MFILE: return Errno::MFILE; + case Network::Errno::PIPE: + return Errno::PIPE; case Network::Errno::NOTCONN: return Errno::NOTCONN; case Network::Errno::TIMEDOUT: return Errno::TIMEDOUT; + case Network::Errno::CONNABORTED: + return Errno::CONNABORTED; case Network::Errno::CONNRESET: return Errno::CONNRESET; case Network::Errno::INPROGRESS: diff --git a/src/core/hle/service/spl/spl_module.cpp b/src/core/hle/service/spl/spl_module.cpp index 0227d4393..549e6f4fa 100644 --- a/src/core/hle/service/spl/spl_module.cpp +++ b/src/core/hle/service/spl/spl_module.cpp @@ -19,7 +19,8 @@ namespace Service::SPL { Module::Interface::Interface(Core::System& system_, std::shared_ptr<Module> module_, const char* name) : ServiceFramework{system_, name}, module{std::move(module_)}, - rng(Settings::values.rng_seed.GetValue().value_or(std::time(nullptr))) {} + rng(Settings::values.rng_seed_enabled ? Settings::values.rng_seed.GetValue() + : static_cast<u32>(std::time(nullptr))) {} Module::Interface::~Interface() = default; @@ -29,10 +30,10 @@ void Module::Interface::GetConfig(HLERequestContext& ctx) { // This should call svcCallSecureMonitor with the appropriate args. // Since we do not have it implemented yet, we will use this for now. - const auto smc_result = GetConfigImpl(config_item); - const auto result_code = smc_result.Code(); + u64 smc_result{}; + const auto result_code = GetConfigImpl(&smc_result, config_item); - if (smc_result.Failed()) { + if (result_code != ResultSuccess) { LOG_ERROR(Service_SPL, "called, config_item={}, result_code={}", config_item, result_code.raw); @@ -41,11 +42,11 @@ void Module::Interface::GetConfig(HLERequestContext& ctx) { } LOG_DEBUG(Service_SPL, "called, config_item={}, result_code={}, smc_result={}", config_item, - result_code.raw, *smc_result); + result_code.raw, smc_result); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(result_code); - rb.Push(*smc_result); + rb.Push(smc_result); } void Module::Interface::ModularExponentiate(HLERequestContext& ctx) { @@ -98,7 +99,7 @@ void Module::Interface::GetBootReason(HLERequestContext& ctx) { rb.Push(ResultSecureMonitorNotImplemented); } -ResultVal<u64> Module::Interface::GetConfigImpl(ConfigItem config_item) const { +Result Module::Interface::GetConfigImpl(u64* out_config, ConfigItem config_item) const { switch (config_item) { case ConfigItem::DisableProgramVerification: case ConfigItem::DramId: @@ -120,40 +121,50 @@ ResultVal<u64> Module::Interface::GetConfigImpl(ConfigItem config_item) const { return ResultSecureMonitorNotImplemented; case ConfigItem::ExosphereApiVersion: // Get information about the current exosphere version. - return (u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MAJOR} << 56) | - (u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MINOR} << 48) | - (u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MICRO} << 40) | - (static_cast<u64>(HLE::ApiVersion::GetTargetFirmware())); + *out_config = (u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MAJOR} << 56) | + (u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MINOR} << 48) | + (u64{HLE::ApiVersion::ATMOSPHERE_RELEASE_VERSION_MICRO} << 40) | + (static_cast<u64>(HLE::ApiVersion::GetTargetFirmware())); + return ResultSuccess; case ConfigItem::ExosphereNeedsReboot: // We are executing, so we aren't in the process of rebooting. - return u64{0}; + *out_config = u64{0}; + return ResultSuccess; case ConfigItem::ExosphereNeedsShutdown: // We are executing, so we aren't in the process of shutting down. - return u64{0}; + *out_config = u64{0}; + return ResultSuccess; case ConfigItem::ExosphereGitCommitHash: // Get information about the current exosphere git commit hash. - return u64{0}; + *out_config = u64{0}; + return ResultSuccess; case ConfigItem::ExosphereHasRcmBugPatch: // Get information about whether this unit has the RCM bug patched. - return u64{0}; + *out_config = u64{0}; + return ResultSuccess; case ConfigItem::ExosphereBlankProdInfo: // Get whether this unit should simulate a "blanked" PRODINFO. - return u64{0}; + *out_config = u64{0}; + return ResultSuccess; case ConfigItem::ExosphereAllowCalWrites: // Get whether this unit should allow writing to the calibration partition. - return u64{0}; + *out_config = u64{0}; + return ResultSuccess; case ConfigItem::ExosphereEmummcType: // Get what kind of emummc this unit has active. - return u64{0}; + *out_config = u64{0}; + return ResultSuccess; case ConfigItem::ExospherePayloadAddress: // Gets the physical address of the reboot payload buffer, if one exists. return ResultSecureMonitorNotInitialized; case ConfigItem::ExosphereLogConfiguration: // Get the log configuration. - return u64{0}; + *out_config = u64{0}; + return ResultSuccess; case ConfigItem::ExosphereForceEnableUsb30: // Get whether usb 3.0 should be force-enabled. - return u64{0}; + *out_config = u64{0}; + return ResultSuccess; default: return ResultSecureMonitorInvalidArgument; } diff --git a/src/core/hle/service/spl/spl_module.h b/src/core/hle/service/spl/spl_module.h index e074e115d..06dcffa6c 100644 --- a/src/core/hle/service/spl/spl_module.h +++ b/src/core/hle/service/spl/spl_module.h @@ -35,7 +35,7 @@ public: std::shared_ptr<Module> module; private: - ResultVal<u64> GetConfigImpl(ConfigItem config_item) const; + Result GetConfigImpl(u64* out_config, ConfigItem config_item) const; std::mt19937 rng; }; diff --git a/src/core/hle/service/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp index 9c96f9763..6c8427b0d 100644 --- a/src/core/hle/service/ssl/ssl.cpp +++ b/src/core/hle/service/ssl/ssl.cpp @@ -4,6 +4,7 @@ #include "common/string_util.h" #include "core/core.h" +#include "core/hle/result.h" #include "core/hle/service/ipc_helpers.h" #include "core/hle/service/server_manager.h" #include "core/hle/service/service.h" @@ -138,15 +139,14 @@ private: bool do_not_close_socket = false; bool get_server_cert_chain = false; std::shared_ptr<Network::SocketBase> socket; - bool did_set_host_name = false; bool did_handshake = false; - ResultVal<s32> SetSocketDescriptorImpl(s32 fd) { + Result SetSocketDescriptorImpl(s32* out_fd, s32 fd) { LOG_DEBUG(Service_SSL, "called, fd={}", fd); ASSERT(!did_handshake); auto bsd = system.ServiceManager().GetService<Service::Sockets::BSD>("bsd:u"); ASSERT_OR_EXECUTE(bsd, { return ResultInternalError; }); - s32 ret_fd; + // Based on https://switchbrew.org/wiki/SSL_services#SetSocketDescriptor if (do_not_close_socket) { auto res = bsd->DuplicateSocketImpl(fd); @@ -156,9 +156,9 @@ private: } fd = *res; fd_to_close = fd; - ret_fd = fd; + *out_fd = fd; } else { - ret_fd = -1; + *out_fd = -1; } std::optional<std::shared_ptr<Network::SocketBase>> sock = bsd->GetSocket(fd); if (!sock.has_value()) { @@ -167,17 +167,13 @@ private: } socket = std::move(*sock); backend->SetSocket(socket); - return ret_fd; + return ResultSuccess; } Result SetHostNameImpl(const std::string& hostname) { LOG_DEBUG(Service_SSL, "called. hostname={}", hostname); ASSERT(!did_handshake); - Result res = backend->SetHostName(hostname); - if (res == ResultSuccess) { - did_set_host_name = true; - } - return res; + return backend->SetHostName(hostname); } Result SetVerifyOptionImpl(u32 option) { @@ -207,9 +203,6 @@ private: Result DoHandshakeImpl() { ASSERT_OR_EXECUTE(!did_handshake && socket, { return ResultNoSocket; }); - ASSERT_OR_EXECUTE_MSG( - did_set_host_name, { return ResultInternalError; }, - "Expected SetHostName before DoHandshake"); Result res = backend->DoHandshake(); did_handshake = res.IsSuccess(); return res; @@ -247,34 +240,36 @@ private: return ret; } - ResultVal<std::vector<u8>> ReadImpl(size_t size) { + Result ReadImpl(std::vector<u8>* out_data, size_t size) { ASSERT_OR_EXECUTE(did_handshake, { return ResultInternalError; }); - std::vector<u8> res(size); - ResultVal<size_t> actual = backend->Read(res); - if (actual.Failed()) { - return actual.Code(); + size_t actual_size{}; + Result res = backend->Read(&actual_size, *out_data); + if (res != ResultSuccess) { + return res; } - res.resize(*actual); + out_data->resize(actual_size); return res; } - ResultVal<size_t> WriteImpl(std::span<const u8> data) { + Result WriteImpl(size_t* out_size, std::span<const u8> data) { ASSERT_OR_EXECUTE(did_handshake, { return ResultInternalError; }); - return backend->Write(data); + return backend->Write(out_size, data); } - ResultVal<s32> PendingImpl() { + Result PendingImpl(s32* out_pending) { LOG_WARNING(Service_SSL, "(STUBBED) called."); - return 0; + *out_pending = 0; + return ResultSuccess; } void SetSocketDescriptor(HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - const s32 fd = rp.Pop<s32>(); - const ResultVal<s32> res = SetSocketDescriptorImpl(fd); + const s32 in_fd = rp.Pop<s32>(); + s32 out_fd{-1}; + const Result res = SetSocketDescriptorImpl(&out_fd, in_fd); IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(res.Code()); - rb.Push<s32>(res.ValueOr(-1)); + rb.Push(res); + rb.Push<s32>(out_fd); } void SetHostName(HLERequestContext& ctx) { @@ -313,14 +308,15 @@ private: }; static_assert(sizeof(OutputParameters) == 0x8); - const Result res = DoHandshakeImpl(); + Result res = DoHandshakeImpl(); OutputParameters out{}; if (res == ResultSuccess) { - auto certs = backend->GetServerCerts(); - if (certs.Succeeded()) { - const std::vector<u8> certs_buf = SerializeServerCerts(*certs); + std::vector<std::vector<u8>> certs; + res = backend->GetServerCerts(&certs); + if (res == ResultSuccess) { + const std::vector<u8> certs_buf = SerializeServerCerts(certs); ctx.WriteBuffer(certs_buf); - out.certs_count = static_cast<u32>(certs->size()); + out.certs_count = static_cast<u32>(certs.size()); out.certs_size = static_cast<u32>(certs_buf.size()); } } @@ -330,29 +326,32 @@ private: } void Read(HLERequestContext& ctx) { - const ResultVal<std::vector<u8>> res = ReadImpl(ctx.GetWriteBufferSize()); + std::vector<u8> output_bytes; + const Result res = ReadImpl(&output_bytes, ctx.GetWriteBufferSize()); IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(res.Code()); - if (res.Succeeded()) { - rb.Push(static_cast<u32>(res->size())); - ctx.WriteBuffer(*res); + rb.Push(res); + if (res == ResultSuccess) { + rb.Push(static_cast<u32>(output_bytes.size())); + ctx.WriteBuffer(output_bytes); } else { rb.Push(static_cast<u32>(0)); } } void Write(HLERequestContext& ctx) { - const ResultVal<size_t> res = WriteImpl(ctx.ReadBuffer()); + size_t write_size{0}; + const Result res = WriteImpl(&write_size, ctx.ReadBuffer()); IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(res.Code()); - rb.Push(static_cast<u32>(res.ValueOr(0))); + rb.Push(res); + rb.Push(static_cast<u32>(write_size)); } void Pending(HLERequestContext& ctx) { - const ResultVal<s32> res = PendingImpl(); + s32 pending_size{0}; + const Result res = PendingImpl(&pending_size); IPC::ResponseBuilder rb{ctx, 3}; - rb.Push(res.Code()); - rb.Push<s32>(res.ValueOr(0)); + rb.Push(res); + rb.Push<s32>(pending_size); } void SetSessionCacheMode(HLERequestContext& ctx) { @@ -438,13 +437,14 @@ private: void CreateConnection(HLERequestContext& ctx) { LOG_WARNING(Service_SSL, "called"); - auto backend_res = CreateSSLConnectionBackend(); + std::unique_ptr<SSLConnectionBackend> backend; + const Result res = CreateSSLConnectionBackend(&backend); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(backend_res.Code()); - if (backend_res.Succeeded()) { + rb.Push(res); + if (res == ResultSuccess) { rb.PushIpcInterface<ISslConnection>(system, ssl_version, shared_data, - std::move(*backend_res)); + std::move(backend)); } } diff --git a/src/core/hle/service/ssl/ssl_backend.h b/src/core/hle/service/ssl/ssl_backend.h index 409f4367c..a2ec8e694 100644 --- a/src/core/hle/service/ssl/ssl_backend.h +++ b/src/core/hle/service/ssl/ssl_backend.h @@ -35,11 +35,11 @@ public: virtual void SetSocket(std::shared_ptr<Network::SocketBase> socket) = 0; virtual Result SetHostName(const std::string& hostname) = 0; virtual Result DoHandshake() = 0; - virtual ResultVal<size_t> Read(std::span<u8> data) = 0; - virtual ResultVal<size_t> Write(std::span<const u8> data) = 0; - virtual ResultVal<std::vector<std::vector<u8>>> GetServerCerts() = 0; + virtual Result Read(size_t* out_size, std::span<u8> data) = 0; + virtual Result Write(size_t* out_size, std::span<const u8> data) = 0; + virtual Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) = 0; }; -ResultVal<std::unique_ptr<SSLConnectionBackend>> CreateSSLConnectionBackend(); +Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend); } // namespace Service::SSL diff --git a/src/core/hle/service/ssl/ssl_backend_none.cpp b/src/core/hle/service/ssl/ssl_backend_none.cpp index 2f4f23c42..a7fafd0a3 100644 --- a/src/core/hle/service/ssl/ssl_backend_none.cpp +++ b/src/core/hle/service/ssl/ssl_backend_none.cpp @@ -7,7 +7,7 @@ namespace Service::SSL { -ResultVal<std::unique_ptr<SSLConnectionBackend>> CreateSSLConnectionBackend() { +Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend) { LOG_ERROR(Service_SSL, "Can't create SSL connection because no SSL backend is available on this platform"); return ResultInternalError; diff --git a/src/core/hle/service/ssl/ssl_backend_openssl.cpp b/src/core/hle/service/ssl/ssl_backend_openssl.cpp index 6ca869dbf..5714e6f3c 100644 --- a/src/core/hle/service/ssl/ssl_backend_openssl.cpp +++ b/src/core/hle/service/ssl/ssl_backend_openssl.cpp @@ -105,31 +105,30 @@ public: return ResultInternalError; } } - return HandleReturn("SSL_do_handshake", 0, ret).Code(); + return HandleReturn("SSL_do_handshake", 0, ret); } - ResultVal<size_t> Read(std::span<u8> data) override { - size_t actual; - const int ret = SSL_read_ex(ssl, data.data(), data.size(), &actual); - return HandleReturn("SSL_read_ex", actual, ret); + Result Read(size_t* out_size, std::span<u8> data) override { + const int ret = SSL_read_ex(ssl, data.data(), data.size(), out_size); + return HandleReturn("SSL_read_ex", out_size, ret); } - ResultVal<size_t> Write(std::span<const u8> data) override { - size_t actual; - const int ret = SSL_write_ex(ssl, data.data(), data.size(), &actual); - return HandleReturn("SSL_write_ex", actual, ret); + Result Write(size_t* out_size, std::span<const u8> data) override { + const int ret = SSL_write_ex(ssl, data.data(), data.size(), out_size); + return HandleReturn("SSL_write_ex", out_size, ret); } - ResultVal<size_t> HandleReturn(const char* what, size_t actual, int ret) { + Result HandleReturn(const char* what, size_t* actual, int ret) { const int ssl_err = SSL_get_error(ssl, ret); CheckOpenSSLErrors(); switch (ssl_err) { case SSL_ERROR_NONE: - return actual; + return ResultSuccess; case SSL_ERROR_ZERO_RETURN: LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_ZERO_RETURN", what); // DoHandshake special-cases this, but for Read and Write: - return size_t(0); + *actual = 0; + return ResultSuccess; case SSL_ERROR_WANT_READ: LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_WANT_READ", what); return ResultWouldBlock; @@ -139,20 +138,20 @@ public: default: if (ssl_err == SSL_ERROR_SYSCALL && got_read_eof) { LOG_DEBUG(Service_SSL, "{} => SSL_ERROR_SYSCALL because server hung up", what); - return size_t(0); + *actual = 0; + return ResultSuccess; } LOG_ERROR(Service_SSL, "{} => other SSL_get_error return value {}", what, ssl_err); return ResultInternalError; } } - ResultVal<std::vector<std::vector<u8>>> GetServerCerts() override { + Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) override { STACK_OF(X509)* chain = SSL_get_peer_cert_chain(ssl); if (!chain) { LOG_ERROR(Service_SSL, "SSL_get_peer_cert_chain returned nullptr"); return ResultInternalError; } - std::vector<std::vector<u8>> ret; int count = sk_X509_num(chain); ASSERT(count >= 0); for (int i = 0; i < count; i++) { @@ -161,16 +160,15 @@ public: unsigned char* buf = nullptr; int len = i2d_X509(x509, &buf); ASSERT_OR_EXECUTE(len >= 0 && buf, { continue; }); - ret.emplace_back(buf, buf + len); + out_certs->emplace_back(buf, buf + len); OPENSSL_free(buf); } - return ret; + return ResultSuccess; } ~SSLConnectionBackendOpenSSL() { - // these are null-tolerant: + // this is null-tolerant: SSL_free(ssl); - BIO_free(bio); } static void KeyLogCallback(const SSL* ssl, const char* line) { @@ -253,13 +251,13 @@ public: std::shared_ptr<Network::SocketBase> socket; }; -ResultVal<std::unique_ptr<SSLConnectionBackend>> CreateSSLConnectionBackend() { +Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend) { auto conn = std::make_unique<SSLConnectionBackendOpenSSL>(); - const Result res = conn->Init(); - if (res.IsFailure()) { - return res; - } - return conn; + + R_TRY(conn->Init()); + + *out_backend = std::move(conn); + return ResultSuccess; } namespace { diff --git a/src/core/hle/service/ssl/ssl_backend_schannel.cpp b/src/core/hle/service/ssl/ssl_backend_schannel.cpp index d8074339a..212057cfc 100644 --- a/src/core/hle/service/ssl/ssl_backend_schannel.cpp +++ b/src/core/hle/service/ssl/ssl_backend_schannel.cpp @@ -31,9 +31,9 @@ CredHandle cred_handle; static void OneTimeInit() { schannel_cred.dwVersion = SCHANNEL_CRED_VERSION; schannel_cred.dwFlags = - SCH_USE_STRONG_CRYPTO | // don't allow insecure protocols - SCH_CRED_AUTO_CRED_VALIDATION | // validate certs - SCH_CRED_NO_DEFAULT_CREDS; // don't automatically present a client certificate + SCH_USE_STRONG_CRYPTO | // don't allow insecure protocols + SCH_CRED_NO_SERVERNAME_CHECK | // don't validate server names + SCH_CRED_NO_DEFAULT_CREDS; // don't automatically present a client certificate // ^ I'm assuming that nobody would want to connect Yuzu to a // service that requires some OS-provided corporate client // certificate, and presenting one to some arbitrary server @@ -227,16 +227,15 @@ public: ciphertext_read_buf.size()); } - const SECURITY_STATUS ret = - InitializeSecurityContextA(&cred_handle, initial_call_done ? &ctxt : nullptr, - // Caller ensured we have set a hostname: - const_cast<char*>(hostname.value().c_str()), req, - 0, // Reserved1 - 0, // TargetDataRep not used with Schannel - initial_call_done ? &input_desc : nullptr, - 0, // Reserved2 - initial_call_done ? nullptr : &ctxt, &output_desc, &attr, - nullptr); // ptsExpiry + char* hostname_ptr = hostname ? const_cast<char*>(hostname->c_str()) : nullptr; + const SECURITY_STATUS ret = InitializeSecurityContextA( + &cred_handle, initial_call_done ? &ctxt : nullptr, hostname_ptr, req, + 0, // Reserved1 + 0, // TargetDataRep not used with Schannel + initial_call_done ? &input_desc : nullptr, + 0, // Reserved2 + initial_call_done ? nullptr : &ctxt, &output_desc, &attr, + nullptr); // ptsExpiry if (output_buffers[0].pvBuffer) { const std::span span(static_cast<u8*>(output_buffers[0].pvBuffer), @@ -299,21 +298,22 @@ public: return ResultSuccess; } - ResultVal<size_t> Read(std::span<u8> data) override { + Result Read(size_t* out_size, std::span<u8> data) override { + *out_size = 0; if (handshake_state != HandshakeState::Connected) { LOG_ERROR(Service_SSL, "Called Read but we did not successfully handshake"); return ResultInternalError; } if (data.size() == 0 || got_read_eof) { - return size_t(0); + return ResultSuccess; } while (1) { if (!cleartext_read_buf.empty()) { - const size_t read_size = std::min(cleartext_read_buf.size(), data.size()); - std::memcpy(data.data(), cleartext_read_buf.data(), read_size); + *out_size = std::min(cleartext_read_buf.size(), data.size()); + std::memcpy(data.data(), cleartext_read_buf.data(), *out_size); cleartext_read_buf.erase(cleartext_read_buf.begin(), - cleartext_read_buf.begin() + read_size); - return read_size; + cleartext_read_buf.begin() + *out_size); + return ResultSuccess; } if (!ciphertext_read_buf.empty()) { SecBuffer empty{ @@ -366,7 +366,8 @@ public: case SEC_I_CONTEXT_EXPIRED: // Server hung up by sending close_notify. got_read_eof = true; - return size_t(0); + *out_size = 0; + return ResultSuccess; default: LOG_ERROR(Service_SSL, "DecryptMessage failed: {}", Common::NativeErrorToString(ret)); @@ -379,18 +380,21 @@ public: } if (ciphertext_read_buf.empty()) { got_read_eof = true; - return size_t(0); + *out_size = 0; + return ResultSuccess; } } } - ResultVal<size_t> Write(std::span<const u8> data) override { + Result Write(size_t* out_size, std::span<const u8> data) override { + *out_size = 0; + if (handshake_state != HandshakeState::Connected) { LOG_ERROR(Service_SSL, "Called Write but we did not successfully handshake"); return ResultInternalError; } if (data.size() == 0) { - return size_t(0); + return ResultSuccess; } data = data.subspan(0, std::min<size_t>(data.size(), stream_sizes.cbMaximumMessage)); if (!cleartext_write_buf.empty()) { @@ -402,7 +406,7 @@ public: LOG_ERROR(Service_SSL, "Called Write but buffer does not match previous buffer"); return ResultInternalError; } - return WriteAlreadyEncryptedData(); + return WriteAlreadyEncryptedData(out_size); } else { cleartext_write_buf.assign(data.begin(), data.end()); } @@ -448,21 +452,21 @@ public: tmp_data_buf.end()); ciphertext_write_buf.insert(ciphertext_write_buf.end(), trailer_buf.begin(), trailer_buf.end()); - return WriteAlreadyEncryptedData(); + return WriteAlreadyEncryptedData(out_size); } - ResultVal<size_t> WriteAlreadyEncryptedData() { + Result WriteAlreadyEncryptedData(size_t* out_size) { const Result r = FlushCiphertextWriteBuf(); if (r != ResultSuccess) { return r; } // write buf is empty - const size_t cleartext_bytes_written = cleartext_write_buf.size(); + *out_size = cleartext_write_buf.size(); cleartext_write_buf.clear(); - return cleartext_bytes_written; + return ResultSuccess; } - ResultVal<std::vector<std::vector<u8>>> GetServerCerts() override { + Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) override { PCCERT_CONTEXT returned_cert = nullptr; const SECURITY_STATUS ret = QueryContextAttributes(&ctxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &returned_cert); @@ -473,16 +477,16 @@ public: return ResultInternalError; } PCCERT_CONTEXT some_cert = nullptr; - std::vector<std::vector<u8>> certs; - while ((some_cert = CertEnumCertificatesInStore(returned_cert->hCertStore, some_cert))) { - certs.emplace_back(static_cast<u8*>(some_cert->pbCertEncoded), - static_cast<u8*>(some_cert->pbCertEncoded) + - some_cert->cbCertEncoded); + while ((some_cert = CertEnumCertificatesInStore(returned_cert->hCertStore, some_cert)) != + nullptr) { + out_certs->emplace_back(static_cast<u8*>(some_cert->pbCertEncoded), + static_cast<u8*>(some_cert->pbCertEncoded) + + some_cert->cbCertEncoded); } - std::reverse(certs.begin(), - certs.end()); // Windows returns certs in reverse order from what we want + std::reverse(out_certs->begin(), + out_certs->end()); // Windows returns certs in reverse order from what we want CertFreeCertificateContext(returned_cert); - return certs; + return ResultSuccess; } ~SSLConnectionBackendSchannel() { @@ -532,13 +536,13 @@ public: size_t read_buf_fill_size = 0; }; -ResultVal<std::unique_ptr<SSLConnectionBackend>> CreateSSLConnectionBackend() { +Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend) { auto conn = std::make_unique<SSLConnectionBackendSchannel>(); - const Result res = conn->Init(); - if (res.IsFailure()) { - return res; - } - return conn; + + R_TRY(conn->Init()); + + *out_backend = std::move(conn); + return ResultSuccess; } } // namespace Service::SSL diff --git a/src/core/hle/service/ssl/ssl_backend_securetransport.cpp b/src/core/hle/service/ssl/ssl_backend_securetransport.cpp index b3083cbad..c48914f64 100644 --- a/src/core/hle/service/ssl/ssl_backend_securetransport.cpp +++ b/src/core/hle/service/ssl/ssl_backend_securetransport.cpp @@ -100,27 +100,23 @@ public: Result DoHandshake() override { OSStatus status = SSLHandshake(context); - return HandleReturn("SSLHandshake", 0, status).Code(); + return HandleReturn("SSLHandshake", 0, status); } - ResultVal<size_t> Read(std::span<u8> data) override { - size_t actual; - OSStatus status = SSLRead(context, data.data(), data.size(), &actual); - ; - return HandleReturn("SSLRead", actual, status); + Result Read(size_t* out_size, std::span<u8> data) override { + OSStatus status = SSLRead(context, data.data(), data.size(), out_size); + return HandleReturn("SSLRead", out_size, status); } - ResultVal<size_t> Write(std::span<const u8> data) override { - size_t actual; - OSStatus status = SSLWrite(context, data.data(), data.size(), &actual); - ; - return HandleReturn("SSLWrite", actual, status); + Result Write(size_t* out_size, std::span<const u8> data) override { + OSStatus status = SSLWrite(context, data.data(), data.size(), out_size); + return HandleReturn("SSLWrite", out_size, status); } - ResultVal<size_t> HandleReturn(const char* what, size_t actual, OSStatus status) { + Result HandleReturn(const char* what, size_t* actual, OSStatus status) { switch (status) { case 0: - return actual; + return ResultSuccess; case errSSLWouldBlock: return ResultWouldBlock; default: { @@ -136,22 +132,21 @@ public: } } - ResultVal<std::vector<std::vector<u8>>> GetServerCerts() override { + Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) override { CFReleaser<SecTrustRef> trust; OSStatus status = SSLCopyPeerTrust(context, &trust.ptr); if (status) { LOG_ERROR(Service_SSL, "SSLCopyPeerTrust failed: {}", OSStatusToString(status)); return ResultInternalError; } - std::vector<std::vector<u8>> ret; for (CFIndex i = 0, count = SecTrustGetCertificateCount(trust); i < count; i++) { SecCertificateRef cert = SecTrustGetCertificateAtIndex(trust, i); CFReleaser<CFDataRef> data(SecCertificateCopyData(cert)); ASSERT_OR_EXECUTE(data, { return ResultInternalError; }); const u8* ptr = CFDataGetBytePtr(data); - ret.emplace_back(ptr, ptr + CFDataGetLength(data)); + out_certs->emplace_back(ptr, ptr + CFDataGetLength(data)); } - return ret; + return ResultSuccess; } static OSStatus ReadCallback(SSLConnectionRef connection, void* data, size_t* dataLength) { @@ -210,13 +205,13 @@ private: std::shared_ptr<Network::SocketBase> socket; }; -ResultVal<std::unique_ptr<SSLConnectionBackend>> CreateSSLConnectionBackend() { +Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend) { auto conn = std::make_unique<SSLConnectionBackendSecureTransport>(); - const Result res = conn->Init(); - if (res.IsFailure()) { - return res; - } - return conn; + + R_TRY(conn->Init()); + + *out_backend = std::move(conn); + return ResultSuccess; } } // namespace Service::SSL 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 5d60be67a..1b96de37a 100644 --- a/src/core/hle/service/time/time_zone_content_manager.cpp +++ b/src/core/hle/service/time/time_zone_content_manager.cpp @@ -3,6 +3,7 @@ #include <chrono> #include <sstream> +#include <utility> #include "common/logging/log.h" #include "common/settings.h" @@ -46,14 +47,14 @@ static FileSys::VirtualDir GetTimeZoneBinary(Core::System& system) { return FileSys::ExtractRomFS(romfs); } -static std::vector<std::string> BuildLocationNameCache(Core::System& system) { - const FileSys::VirtualDir extracted_romfs{GetTimeZoneBinary(system)}; - if (!extracted_romfs) { +static std::vector<std::string> BuildLocationNameCache( + const FileSys::VirtualDir& time_zone_binary) { + if (!time_zone_binary) { LOG_ERROR(Service_Time, "Failed to extract RomFS for {:016X}!", time_zone_binary_titleid); return {}; } - const FileSys::VirtualFile binary_list{extracted_romfs->GetFile("binaryList.txt")}; + const FileSys::VirtualFile binary_list{time_zone_binary->GetFile("binaryList.txt")}; if (!binary_list) { LOG_ERROR(Service_Time, "{:016X} has no file binaryList.txt!", time_zone_binary_titleid); return {}; @@ -73,10 +74,12 @@ static std::vector<std::string> BuildLocationNameCache(Core::System& system) { } TimeZoneContentManager::TimeZoneContentManager(Core::System& system_) - : system{system_}, location_name_cache{BuildLocationNameCache(system)} {} + : system{system_}, time_zone_binary{GetTimeZoneBinary(system)}, + location_name_cache{BuildLocationNameCache(time_zone_binary)} {} void TimeZoneContentManager::Initialize(TimeManager& time_manager) { - const auto timezone_setting = Settings::GetTimeZoneString(); + const auto timezone_setting = + Settings::GetTimeZoneString(Settings::values.time_zone_index.GetValue()); if (FileSys::VirtualFile vfs_file; GetTimeZoneInfoFile(timezone_setting, vfs_file) == ResultSuccess) { @@ -111,13 +114,12 @@ Result TimeZoneContentManager::GetTimeZoneInfoFile(const std::string& location_n return ERROR_TIME_NOT_FOUND; } - const FileSys::VirtualDir extracted_romfs{GetTimeZoneBinary(system)}; - if (!extracted_romfs) { + if (!time_zone_binary) { LOG_ERROR(Service_Time, "Failed to extract RomFS for {:016X}!", time_zone_binary_titleid); return ERROR_TIME_NOT_FOUND; } - const FileSys::VirtualDir zoneinfo_dir{extracted_romfs->GetSubdirectory("zoneinfo")}; + const FileSys::VirtualDir zoneinfo_dir{time_zone_binary->GetSubdirectory("zoneinfo")}; if (!zoneinfo_dir) { LOG_ERROR(Service_Time, "{:016X} has no directory zoneinfo!", time_zone_binary_titleid); return ERROR_TIME_NOT_FOUND; diff --git a/src/core/hle/service/time/time_zone_content_manager.h b/src/core/hle/service/time/time_zone_content_manager.h index 3d94b6428..a6f9698bc 100644 --- a/src/core/hle/service/time/time_zone_content_manager.h +++ b/src/core/hle/service/time/time_zone_content_manager.h @@ -6,6 +6,7 @@ #include <string> #include <vector> +#include "core/file_sys/vfs_types.h" #include "core/hle/service/time/time_zone_manager.h" namespace Core { @@ -41,6 +42,7 @@ private: Core::System& system; TimeZoneManager time_zone_manager; + const FileSys::VirtualDir time_zone_binary; const std::vector<std::string> location_name_cache; }; diff --git a/src/core/hle/service/vi/display/vi_display.cpp b/src/core/hle/service/vi/display/vi_display.cpp index 69af2868a..f0b5eff8a 100644 --- a/src/core/hle/service/vi/display/vi_display.cpp +++ b/src/core/hle/service/vi/display/vi_display.cpp @@ -58,14 +58,15 @@ const Layer& Display::GetLayer(std::size_t index) const { return *layers.at(index); } -ResultVal<Kernel::KReadableEvent*> Display::GetVSyncEvent() { +Result Display::GetVSyncEvent(Kernel::KReadableEvent** out_vsync_event) { if (got_vsync_event) { return ResultPermissionDenied; } got_vsync_event = true; - return GetVSyncEventUnchecked(); + *out_vsync_event = GetVSyncEventUnchecked(); + return ResultSuccess; } Kernel::KReadableEvent* Display::GetVSyncEventUnchecked() { diff --git a/src/core/hle/service/vi/display/vi_display.h b/src/core/hle/service/vi/display/vi_display.h index 3f31d1f32..101cbce20 100644 --- a/src/core/hle/service/vi/display/vi_display.h +++ b/src/core/hle/service/vi/display/vi_display.h @@ -85,7 +85,7 @@ public: * @returns The internal Vsync event if it has not yet been retrieved, * VI::ResultPermissionDenied otherwise. */ - [[nodiscard]] ResultVal<Kernel::KReadableEvent*> GetVSyncEvent(); + [[nodiscard]] Result GetVSyncEvent(Kernel::KReadableEvent** out_vsync_event); /// Gets the internal vsync event. Kernel::KReadableEvent* GetVSyncEventUnchecked(); diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp index 1b193f00c..2eb978379 100644 --- a/src/core/hle/service/vi/vi.cpp +++ b/src/core/hle/service/vi/vi.cpp @@ -217,7 +217,7 @@ private: IPC::ResponseBuilder rb{ctx, 6}; rb.Push(ResultSuccess); - if (Settings::values.use_docked_mode.GetValue()) { + if (Settings::IsDockedMode()) { rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth)); rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight)); } else { @@ -683,9 +683,9 @@ private: LOG_DEBUG(Service_VI, "called. display_id={}", display_id); - const auto vsync_event = nv_flinger.FindVsyncEvent(display_id); - if (vsync_event.Failed()) { - const auto result = vsync_event.Code(); + Kernel::KReadableEvent* vsync_event{}; + const auto result = nv_flinger.FindVsyncEvent(&vsync_event, display_id); + if (result != ResultSuccess) { if (result == ResultNotFound) { LOG_ERROR(Service_VI, "Vsync event was not found for display_id={}", display_id); } @@ -697,7 +697,7 @@ private: IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(ResultSuccess); - rb.PushCopyObjects(*vsync_event); + rb.PushCopyObjects(vsync_event); } void ConvertScalingMode(HLERequestContext& ctx) { @@ -705,15 +705,16 @@ private: const auto mode = rp.PopEnum<NintendoScaleMode>(); LOG_DEBUG(Service_VI, "called mode={}", mode); - const auto converted_mode = ConvertScalingModeImpl(mode); + ConvertedScaleMode converted_mode{}; + const auto result = ConvertScalingModeImpl(&converted_mode, mode); - if (converted_mode.Succeeded()) { + if (result == ResultSuccess) { IPC::ResponseBuilder rb{ctx, 4}; rb.Push(ResultSuccess); - rb.PushEnum(*converted_mode); + rb.PushEnum(converted_mode); } else { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(converted_mode.Code()); + rb.Push(result); } } @@ -760,18 +761,24 @@ private: rb.Push(alignment); } - static ResultVal<ConvertedScaleMode> ConvertScalingModeImpl(NintendoScaleMode mode) { + static Result ConvertScalingModeImpl(ConvertedScaleMode* out_scaling_mode, + NintendoScaleMode mode) { switch (mode) { case NintendoScaleMode::None: - return ConvertedScaleMode::None; + *out_scaling_mode = ConvertedScaleMode::None; + return ResultSuccess; case NintendoScaleMode::Freeze: - return ConvertedScaleMode::Freeze; + *out_scaling_mode = ConvertedScaleMode::Freeze; + return ResultSuccess; case NintendoScaleMode::ScaleToWindow: - return ConvertedScaleMode::ScaleToWindow; + *out_scaling_mode = ConvertedScaleMode::ScaleToWindow; + return ResultSuccess; case NintendoScaleMode::ScaleAndCrop: - return ConvertedScaleMode::ScaleAndCrop; + *out_scaling_mode = ConvertedScaleMode::ScaleAndCrop; + return ResultSuccess; case NintendoScaleMode::PreserveAspectRatio: - return ConvertedScaleMode::PreserveAspectRatio; + *out_scaling_mode = ConvertedScaleMode::PreserveAspectRatio; + return ResultSuccess; default: LOG_ERROR(Service_VI, "Invalid scaling mode specified, mode={}", mode); return ResultOperationFailed; diff --git a/src/core/internal_network/network.cpp b/src/core/internal_network/network.cpp index 28f89c599..a983f23ea 100644 --- a/src/core/internal_network/network.cpp +++ b/src/core/internal_network/network.cpp @@ -39,19 +39,41 @@ namespace Network { namespace { +enum class CallType { + Send, + Other, +}; + #ifdef _WIN32 using socklen_t = int; +SOCKET interrupt_socket = static_cast<SOCKET>(-1); + +void InterruptSocketOperations() { + closesocket(interrupt_socket); +} + +void AcknowledgeInterrupt() { + interrupt_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); +} + void Initialize() { WSADATA wsa_data; (void)WSAStartup(MAKEWORD(2, 2), &wsa_data); + + AcknowledgeInterrupt(); } void Finalize() { + InterruptSocketOperations(); WSACleanup(); } +SOCKET GetInterruptSocket() { + return interrupt_socket; +} + sockaddr TranslateFromSockAddrIn(SockAddrIn input) { sockaddr_in result; @@ -96,7 +118,7 @@ bool EnableNonBlock(SOCKET fd, bool enable) { return ioctlsocket(fd, FIONBIO, &value) != SOCKET_ERROR; } -Errno TranslateNativeError(int e) { +Errno TranslateNativeError(int e, CallType call_type = CallType::Other) { switch (e) { case 0: return Errno::SUCCESS; @@ -112,6 +134,14 @@ Errno TranslateNativeError(int e) { return Errno::AGAIN; case WSAECONNREFUSED: return Errno::CONNREFUSED; + case WSAECONNABORTED: + if (call_type == CallType::Send) { + // Winsock yields WSAECONNABORTED from `send` in situations where Unix + // systems, and actual Switches, yield EPIPE. + return Errno::PIPE; + } else { + return Errno::CONNABORTED; + } case WSAECONNRESET: return Errno::CONNRESET; case WSAEHOSTUNREACH: @@ -144,9 +174,42 @@ constexpr int SD_RECEIVE = SHUT_RD; constexpr int SD_SEND = SHUT_WR; constexpr int SD_BOTH = SHUT_RDWR; -void Initialize() {} +int interrupt_pipe_fd[2] = {-1, -1}; -void Finalize() {} +void Initialize() { + if (pipe(interrupt_pipe_fd) != 0) { + LOG_ERROR(Network, "Failed to create interrupt pipe!"); + } + int flags = fcntl(interrupt_pipe_fd[0], F_GETFL); + ASSERT_MSG(fcntl(interrupt_pipe_fd[0], F_SETFL, flags | O_NONBLOCK) == 0, + "Failed to set nonblocking state for interrupt pipe"); +} + +void Finalize() { + if (interrupt_pipe_fd[0] >= 0) { + close(interrupt_pipe_fd[0]); + } + if (interrupt_pipe_fd[1] >= 0) { + close(interrupt_pipe_fd[1]); + } +} + +void InterruptSocketOperations() { + u8 value = 0; + ASSERT(write(interrupt_pipe_fd[1], &value, sizeof(value)) == 1); +} + +void AcknowledgeInterrupt() { + u8 value = 0; + ssize_t ret = read(interrupt_pipe_fd[0], &value, sizeof(value)); + if (ret != 1 && errno != EAGAIN && errno != EWOULDBLOCK) { + LOG_ERROR(Network, "Failed to acknowledge interrupt on shutdown"); + } +} + +SOCKET GetInterruptSocket() { + return interrupt_pipe_fd[0]; +} sockaddr TranslateFromSockAddrIn(SockAddrIn input) { sockaddr_in result; @@ -198,7 +261,7 @@ bool EnableNonBlock(int fd, bool enable) { return fcntl(fd, F_SETFL, flags) == 0; } -Errno TranslateNativeError(int e) { +Errno TranslateNativeError(int e, CallType call_type = CallType::Other) { switch (e) { case 0: return Errno::SUCCESS; @@ -208,6 +271,10 @@ Errno TranslateNativeError(int e) { return Errno::INVAL; case EMFILE: return Errno::MFILE; + case EPIPE: + return Errno::PIPE; + case ECONNABORTED: + return Errno::CONNABORTED; case ENOTCONN: return Errno::NOTCONN; case EAGAIN: @@ -236,13 +303,13 @@ Errno TranslateNativeError(int e) { #endif -Errno GetAndLogLastError() { +Errno GetAndLogLastError(CallType call_type = CallType::Other) { #ifdef _WIN32 int e = WSAGetLastError(); #else int e = errno; #endif - const Errno err = TranslateNativeError(e); + const Errno err = TranslateNativeError(e, call_type); if (err == Errno::AGAIN || err == Errno::TIMEDOUT || err == Errno::INPROGRESS) { // These happen during normal operation, so only log them at debug level. LOG_DEBUG(Network, "Socket operation error: {}", Common::NativeErrorToString(e)); @@ -473,10 +540,24 @@ NetworkInstance::~NetworkInstance() { Finalize(); } +void CancelPendingSocketOperations() { + InterruptSocketOperations(); +} + +void RestartSocketOperations() { + AcknowledgeInterrupt(); +} + std::optional<IPv4Address> GetHostIPv4Address() { const auto network_interface = Network::GetSelectedNetworkInterface(); if (!network_interface.has_value()) { - LOG_DEBUG(Network, "GetSelectedNetworkInterface returned no interface"); + // Only print the error once to avoid log spam + static bool print_error = true; + if (print_error) { + LOG_ERROR(Network, "GetSelectedNetworkInterface returned no interface"); + print_error = false; + } + return {}; } @@ -537,7 +618,14 @@ std::pair<s32, Errno> Poll(std::vector<PollFD>& pollfds, s32 timeout) { return result; }); - const int result = WSAPoll(host_pollfds.data(), static_cast<ULONG>(num), timeout); + host_pollfds.push_back(WSAPOLLFD{ + .fd = GetInterruptSocket(), + .events = POLLIN, + .revents = 0, + }); + + const int result = + WSAPoll(host_pollfds.data(), static_cast<ULONG>(host_pollfds.size()), timeout); if (result == 0) { ASSERT(std::all_of(host_pollfds.begin(), host_pollfds.end(), [](WSAPOLLFD fd) { return fd.revents == 0; })); @@ -604,6 +692,24 @@ Errno Socket::Initialize(Domain domain, Type type, Protocol protocol) { std::pair<SocketBase::AcceptResult, Errno> Socket::Accept() { sockaddr_in addr; socklen_t addrlen = sizeof(addr); + + std::vector<WSAPOLLFD> host_pollfds{ + WSAPOLLFD{fd, POLLIN, 0}, + WSAPOLLFD{GetInterruptSocket(), POLLIN, 0}, + }; + + while (true) { + const int pollres = + WSAPoll(host_pollfds.data(), static_cast<ULONG>(host_pollfds.size()), -1); + if (host_pollfds[1].revents != 0) { + // Interrupt signaled before a client could be accepted, break + return {AcceptResult{}, Errno::AGAIN}; + } + if (pollres > 0) { + break; + } + } + const SOCKET new_socket = accept(fd, reinterpret_cast<sockaddr*>(&addr), &addrlen); if (new_socket == INVALID_SOCKET) { @@ -725,13 +831,17 @@ std::pair<s32, Errno> Socket::Send(std::span<const u8> message, int flags) { ASSERT(message.size() < static_cast<size_t>(std::numeric_limits<int>::max())); ASSERT(flags == 0); + int native_flags = 0; +#if YUZU_UNIX + native_flags |= MSG_NOSIGNAL; // do not send us SIGPIPE +#endif const auto result = send(fd, reinterpret_cast<const char*>(message.data()), - static_cast<int>(message.size()), 0); + static_cast<int>(message.size()), native_flags); if (result != SOCKET_ERROR) { return {static_cast<s32>(result), Errno::SUCCESS}; } - return {-1, GetAndLogLastError()}; + return {-1, GetAndLogLastError(CallType::Send)}; } std::pair<s32, Errno> Socket::SendTo(u32 flags, std::span<const u8> message, @@ -753,7 +863,7 @@ std::pair<s32, Errno> Socket::SendTo(u32 flags, std::span<const u8> message, return {static_cast<s32>(result), Errno::SUCCESS}; } - return {-1, GetAndLogLastError()}; + return {-1, GetAndLogLastError(CallType::Send)}; } Errno Socket::Close() { diff --git a/src/core/internal_network/network.h b/src/core/internal_network/network.h index badcb8369..b7b7d773a 100644 --- a/src/core/internal_network/network.h +++ b/src/core/internal_network/network.h @@ -33,10 +33,12 @@ enum class Errno { BADF, INVAL, MFILE, + PIPE, NOTCONN, AGAIN, CONNREFUSED, CONNRESET, + CONNABORTED, HOSTUNREACH, NETDOWN, NETUNREACH, @@ -94,6 +96,9 @@ public: ~NetworkInstance(); }; +void CancelPendingSocketOperations(); +void RestartSocketOperations(); + #ifdef _WIN32 constexpr IPv4Address TranslateIPv4(in_addr addr) { auto& bytes = addr.S_un.S_un_b; diff --git a/src/core/internal_network/network_interface.cpp b/src/core/internal_network/network_interface.cpp index 4c909a6d3..7c37f660b 100644 --- a/src/core/internal_network/network_interface.cpp +++ b/src/core/internal_network/network_interface.cpp @@ -200,7 +200,14 @@ std::optional<NetworkInterface> GetSelectedNetworkInterface() { }); if (res == network_interfaces.end()) { - LOG_DEBUG(Network, "Couldn't find selected interface \"{}\"", selected_network_interface); + // Only print the error once to avoid log spam + static bool print_error = true; + if (print_error) { + LOG_ERROR(Network, "Couldn't find selected interface \"{}\"", + selected_network_interface); + print_error = false; + } + return std::nullopt; } diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp index e04ad19db..5a42dea48 100644 --- a/src/core/loader/deconstructed_rom_directory.cpp +++ b/src/core/loader/deconstructed_rom_directory.cpp @@ -18,7 +18,7 @@ namespace Loader { AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_, bool override_update_) - : AppLoader(std::move(file_)), override_update(override_update_) { + : AppLoader(std::move(file_)), override_update(override_update_), is_hbl(false) { const auto file_dir = file->GetContainingDirectory(); // Title ID @@ -69,9 +69,9 @@ AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys } AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory( - FileSys::VirtualDir directory, bool override_update_) + FileSys::VirtualDir directory, bool override_update_, bool is_hbl_) : AppLoader(directory->GetFile("main")), dir(std::move(directory)), - override_update(override_update_) {} + override_update(override_update_), is_hbl(is_hbl_) {} FileType AppLoader_DeconstructedRomDirectory::IdentifyType(const FileSys::VirtualFile& dir_file) { if (FileSys::IsDirectoryExeFS(dir_file->GetContainingDirectory())) { @@ -147,13 +147,13 @@ AppLoader_DeconstructedRomDirectory::LoadResult AppLoader_DeconstructedRomDirect } // Setup the process code layout - if (process.LoadFromMetadata(metadata, code_size).IsError()) { + if (process.LoadFromMetadata(metadata, code_size, is_hbl).IsError()) { return {ResultStatus::ErrorUnableToParseKernelMetadata, {}}; } // Load NSO modules modules.clear(); - const VAddr base_address{GetInteger(process.GetPageTable().GetCodeRegionStart())}; + const VAddr base_address{GetInteger(process.GetEntryPoint())}; VAddr next_load_addr{base_address}; const FileSys::PatchManager pm{metadata.GetTitleID(), system.GetFileSystemController(), system.GetContentProvider()}; diff --git a/src/core/loader/deconstructed_rom_directory.h b/src/core/loader/deconstructed_rom_directory.h index f7702225e..1e9f765c9 100644 --- a/src/core/loader/deconstructed_rom_directory.h +++ b/src/core/loader/deconstructed_rom_directory.h @@ -27,7 +27,8 @@ public: // Overload to accept exefs directory. Must contain 'main' and 'main.npdm' explicit AppLoader_DeconstructedRomDirectory(FileSys::VirtualDir directory, - bool override_update_ = false); + bool override_update_ = false, + bool is_hbl_ = false); /** * Identifies whether or not the given file is a deconstructed ROM directory. @@ -62,6 +63,7 @@ private: std::string name; u64 title_id{}; bool override_update; + bool is_hbl; Modules modules; }; diff --git a/src/core/loader/kip.cpp b/src/core/loader/kip.cpp index ffe976b94..bf56a08b4 100644 --- a/src/core/loader/kip.cpp +++ b/src/core/loader/kip.cpp @@ -90,13 +90,14 @@ AppLoader::LoadResult AppLoader_KIP::Load(Kernel::KProcess& process, codeset.DataSegment().size += kip->GetBSSSize(); // Setup the process code layout - if (process.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size()) + if (process + .LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size(), false) .IsError()) { return {ResultStatus::ErrorNotInitialized, {}}; } codeset.memory = std::move(program_image); - const VAddr base_address = GetInteger(process.GetPageTable().GetCodeRegionStart()); + const VAddr base_address = GetInteger(process.GetEntryPoint()); process.LoadModule(std::move(codeset), base_address); LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", kip->GetName(), base_address); diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index f24474ed8..b6e355622 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -108,7 +108,7 @@ std::string GetFileTypeString(FileType type) { return "unknown"; } -constexpr std::array<const char*, 66> RESULT_MESSAGES{ +constexpr std::array<const char*, 68> RESULT_MESSAGES{ "The operation completed successfully.", "The loader requested to load is already loaded.", "The operation is not implemented.", @@ -135,7 +135,7 @@ constexpr std::array<const char*, 66> RESULT_MESSAGES{ "The titlekey and/or titlekek is incorrect or the section header is invalid.", "The XCI file is missing a Program-type NCA.", "The NCA file is not an application.", - "The ExeFS partition could not be found.", + "The Program-type NCA contains no executable. An update may be required.", "The XCI file has a bad header.", "The XCI file is missing a partition.", "The file could not be found or does not exist.", @@ -169,12 +169,14 @@ constexpr std::array<const char*, 66> RESULT_MESSAGES{ "The BKTR-type NCA has a bad Subsection block.", "The BKTR-type NCA has a bad Relocation bucket.", "The BKTR-type NCA has a bad Subsection bucket.", - "The BKTR-type NCA is missing the base RomFS.", + "Game updates cannot be loaded directly. Load the base game instead.", "The NSP or XCI does not contain an update in addition to the base game.", "The KIP file has a bad header.", "The KIP BLZ decompression of the section failed unexpectedly.", "The INI file has a bad header.", "The INI file contains more than the maximum allowable number of KIP files.", + "Integrity verification could not be performed for this file.", + "Integrity verification failed.", }; std::string GetResultStatusString(ResultStatus status) { diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 7a2a52fd4..b4828f7cd 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -3,6 +3,7 @@ #pragma once +#include <functional> #include <iosfwd> #include <memory> #include <optional> @@ -79,8 +80,6 @@ enum class ResultStatus : u16 { ErrorBadPFSHeader, ErrorIncorrectPFSFileSize, ErrorBadNCAHeader, - ErrorCompressedNCA, - ErrorSparseNCA, ErrorMissingProductionKeyFile, ErrorMissingHeaderKey, ErrorIncorrectHeaderKey, @@ -134,6 +133,8 @@ enum class ResultStatus : u16 { ErrorBLZDecompressionFailed, ErrorBadINIHeader, ErrorINITooManyKIPs, + ErrorIntegrityVerificationNotImplemented, + ErrorIntegrityVerificationFailed, }; std::string GetResultStatusString(ResultStatus status); @@ -172,6 +173,13 @@ public: virtual LoadResult Load(Kernel::KProcess& process, Core::System& system) = 0; /** + * Try to verify the integrity of the file. + */ + virtual ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) { + return ResultStatus::ErrorIntegrityVerificationNotImplemented; + } + + /** * Get the code (typically .code section) of the application * * @param[out] buffer Reference to buffer to store data @@ -276,16 +284,6 @@ public: } /** - * Gets the difference between the start of the IVFC header and the start of level 6 (RomFS) - * data. Needed for BKTR patching. - * - * @return IVFC offset for RomFS. - */ - virtual u64 ReadRomFSIVFCOffset() const { - return 0; - } - - /** * Get the title of the application * * @param[out] title Reference to store the application title into diff --git a/src/core/loader/nax.cpp b/src/core/loader/nax.cpp index cf35b1249..3b7b005ff 100644 --- a/src/core/loader/nax.cpp +++ b/src/core/loader/nax.cpp @@ -76,10 +76,6 @@ ResultStatus AppLoader_NAX::ReadRomFS(FileSys::VirtualFile& dir) { return nca_loader->ReadRomFS(dir); } -u64 AppLoader_NAX::ReadRomFSIVFCOffset() const { - return nca_loader->ReadRomFSIVFCOffset(); -} - ResultStatus AppLoader_NAX::ReadProgramId(u64& out_program_id) { return nca_loader->ReadProgramId(out_program_id); } diff --git a/src/core/loader/nax.h b/src/core/loader/nax.h index d7f70db43..81df2bbcd 100644 --- a/src/core/loader/nax.h +++ b/src/core/loader/nax.h @@ -39,7 +39,6 @@ public: LoadResult Load(Kernel::KProcess& process, Core::System& system) override; ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; - u64 ReadRomFSIVFCOffset() const override; ResultStatus ReadProgramId(u64& out_program_id) override; ResultStatus ReadBanner(std::vector<u8>& buffer) override; diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp index 513af194d..4feb6968a 100644 --- a/src/core/loader/nca.cpp +++ b/src/core/loader/nca.cpp @@ -3,13 +3,18 @@ #include <utility> +#include "common/hex_util.h" +#include "common/scope_exit.h" #include "core/core.h" #include "core/file_sys/content_archive.h" +#include "core/file_sys/nca_metadata.h" +#include "core/file_sys/registered_cache.h" #include "core/file_sys/romfs_factory.h" #include "core/hle/kernel/k_process.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/loader/deconstructed_rom_directory.h" #include "core/loader/nca.h" +#include "mbedtls/sha256.h" namespace Loader { @@ -43,9 +48,23 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::KProcess& process, Core::S return {ResultStatus::ErrorNCANotProgram, {}}; } - const auto exefs = nca->GetExeFS(); + auto exefs = nca->GetExeFS(); if (exefs == nullptr) { - return {ResultStatus::ErrorNoExeFS, {}}; + LOG_INFO(Loader, "No ExeFS found in NCA, looking for ExeFS from update"); + + // This NCA may be a sparse base of an installed title. + // Try to fetch the ExeFS from the installed update. + const auto& installed = system.GetContentProvider(); + const auto update_nca = installed.GetEntry(FileSys::GetUpdateTitleID(nca->GetTitleId()), + FileSys::ContentRecordType::Program); + + if (update_nca) { + exefs = update_nca->GetExeFS(); + } + + if (exefs == nullptr) { + return {ResultStatus::ErrorNoExeFS, {}}; + } } directory_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(exefs, true); @@ -64,6 +83,79 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::KProcess& process, Core::S return load_result; } +ResultStatus AppLoader_NCA::VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) { + using namespace Common::Literals; + + constexpr size_t NcaFileNameWithHashLength = 36; + constexpr size_t NcaFileNameHashLength = 32; + constexpr size_t NcaSha256HashLength = 32; + constexpr size_t NcaSha256HalfHashLength = NcaSha256HashLength / 2; + + // Get the file name. + const auto name = file->GetName(); + + // We won't try to verify meta NCAs. + if (name.ends_with(".cnmt.nca")) { + return ResultStatus::Success; + } + + // Check if we can verify this file. NCAs should be named after their hashes. + if (!name.ends_with(".nca") || name.size() != NcaFileNameWithHashLength) { + LOG_WARNING(Loader, "Unable to validate NCA with name {}", name); + return ResultStatus::ErrorIntegrityVerificationNotImplemented; + } + + // Get the expected truncated hash of the NCA. + const auto input_hash = + Common::HexStringToVector(file->GetName().substr(0, NcaFileNameHashLength), false); + + // Declare buffer to read into. + std::vector<u8> buffer(4_MiB); + + // Initialize sha256 verification context. + mbedtls_sha256_context ctx; + mbedtls_sha256_init(&ctx); + mbedtls_sha256_starts_ret(&ctx, 0); + + // Ensure we maintain a clean state on exit. + SCOPE_EXIT({ mbedtls_sha256_free(&ctx); }); + + // Declare counters. + const size_t total_size = file->GetSize(); + size_t processed_size = 0; + + // Begin iterating the file. + while (processed_size < total_size) { + // Refill the buffer. + const size_t intended_read_size = std::min(buffer.size(), total_size - processed_size); + const size_t read_size = file->Read(buffer.data(), intended_read_size, processed_size); + + // Update the hash function with the buffer contents. + mbedtls_sha256_update_ret(&ctx, buffer.data(), read_size); + + // Update counters. + processed_size += read_size; + + // Call the progress function. + if (!progress_callback(processed_size, total_size)) { + return ResultStatus::ErrorIntegrityVerificationFailed; + } + } + + // Finalize context and compute the output hash. + std::array<u8, NcaSha256HashLength> output_hash; + mbedtls_sha256_finish_ret(&ctx, output_hash.data()); + + // Compare to expected. + if (std::memcmp(input_hash.data(), output_hash.data(), NcaSha256HalfHashLength) != 0) { + LOG_ERROR(Loader, "NCA hash mismatch detected for file {}", name); + return ResultStatus::ErrorIntegrityVerificationFailed; + } + + // File verified. + return ResultStatus::Success; +} + ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) { if (nca == nullptr) { return ResultStatus::ErrorNotInitialized; @@ -77,14 +169,6 @@ ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) { return ResultStatus::Success; } -u64 AppLoader_NCA::ReadRomFSIVFCOffset() const { - if (nca == nullptr) { - return 0; - } - - return nca->GetBaseIVFCOffset(); -} - ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) { if (nca == nullptr || nca->GetStatus() != ResultStatus::Success) { return ResultStatus::ErrorNotInitialized; diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h index d22d9146e..96779e27f 100644 --- a/src/core/loader/nca.h +++ b/src/core/loader/nca.h @@ -39,8 +39,9 @@ public: LoadResult Load(Kernel::KProcess& process, Core::System& system) override; + ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) override; + ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; - u64 ReadRomFSIVFCOffset() const override; ResultStatus ReadProgramId(u64& out_program_id) override; ResultStatus ReadBanner(std::vector<u8>& buffer) override; diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp index 506808b5d..69f1a54ed 100644 --- a/src/core/loader/nro.cpp +++ b/src/core/loader/nro.cpp @@ -196,14 +196,15 @@ static bool LoadNroImpl(Kernel::KProcess& process, const std::vector<u8>& data) program_image.resize(static_cast<u32>(program_image.size()) + bss_size); // Setup the process code layout - if (process.LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size()) + if (process + .LoadFromMetadata(FileSys::ProgramMetadata::GetDefault(), program_image.size(), false) .IsError()) { return false; } // Load codeset for current process codeset.memory = std::move(program_image); - process.LoadModule(std::move(codeset), process.GetPageTable().GetCodeRegionStart()); + process.LoadModule(std::move(codeset), process.GetEntryPoint()); return true; } diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index 74cc9579f..1350da8dc 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -127,13 +127,14 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::KProcess& process, Core:: } // Apply patches if necessary - if (pm && (pm->HasNSOPatch(nso_header.build_id) || Settings::values.dump_nso)) { + const auto name = nso_file.GetName(); + if (pm && (pm->HasNSOPatch(nso_header.build_id, name) || Settings::values.dump_nso)) { std::vector<u8> pi_header(sizeof(NSOHeader) + program_image.size()); std::memcpy(pi_header.data(), &nso_header, sizeof(NSOHeader)); std::memcpy(pi_header.data() + sizeof(NSOHeader), program_image.data(), program_image.size()); - pi_header = pm->PatchNSO(pi_header, nso_file.GetName()); + pi_header = pm->PatchNSO(pi_header, name); std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.data()); } @@ -167,7 +168,7 @@ AppLoader_NSO::LoadResult AppLoader_NSO::Load(Kernel::KProcess& process, Core::S modules.clear(); // Load module - const VAddr base_address = GetInteger(process.GetPageTable().GetCodeRegionStart()); + const VAddr base_address = GetInteger(process.GetEntryPoint()); if (!LoadModule(process, system, *file, base_address, true, true)) { return {ResultStatus::ErrorLoadingNSO, {}}; } diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp index 80663e0e0..f4ab75b77 100644 --- a/src/core/loader/nsp.cpp +++ b/src/core/loader/nsp.cpp @@ -30,7 +30,8 @@ AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file_, } if (nsp->IsExtractedType()) { - secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(nsp->GetExeFS()); + secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>( + nsp->GetExeFS(), false, file->GetName() == "hbl.nsp"); } else { const auto control_nca = nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Control); @@ -117,12 +118,44 @@ AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::KProcess& process, Core::S return result; } -ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& out_file) { - return secondary_loader->ReadRomFS(out_file); +ResultStatus AppLoader_NSP::VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) { + // Extracted-type NSPs can't be verified. + if (nsp->IsExtractedType()) { + return ResultStatus::ErrorIntegrityVerificationNotImplemented; + } + + // Get list of all NCAs. + const auto ncas = nsp->GetNCAsCollapsed(); + + size_t total_size = 0; + size_t processed_size = 0; + + // Loop over NCAs, collecting the total size to verify. + for (const auto& nca : ncas) { + total_size += nca->GetBaseFile()->GetSize(); + } + + // Loop over NCAs again, verifying each. + for (const auto& nca : ncas) { + AppLoader_NCA loader_nca(nca->GetBaseFile()); + + const auto NcaProgressCallback = [&](size_t nca_processed_size, size_t nca_total_size) { + return progress_callback(processed_size + nca_processed_size, total_size); + }; + + const auto verification_result = loader_nca.VerifyIntegrity(NcaProgressCallback); + if (verification_result != ResultStatus::Success) { + return verification_result; + } + + processed_size += nca->GetBaseFile()->GetSize(); + } + + return ResultStatus::Success; } -u64 AppLoader_NSP::ReadRomFSIVFCOffset() const { - return secondary_loader->ReadRomFSIVFCOffset(); +ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& out_file) { + return secondary_loader->ReadRomFS(out_file); } ResultStatus AppLoader_NSP::ReadUpdateRaw(FileSys::VirtualFile& out_file) { diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h index 003cc345c..7ce436c67 100644 --- a/src/core/loader/nsp.h +++ b/src/core/loader/nsp.h @@ -45,8 +45,9 @@ public: LoadResult Load(Kernel::KProcess& process, Core::System& system) override; + ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) override; + ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override; - u64 ReadRomFSIVFCOffset() const override; ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; ResultStatus ReadProgramId(u64& out_program_id) override; ResultStatus ReadProgramIds(std::vector<u64>& out_program_ids) override; diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp index c7b1b3815..12d72c380 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp @@ -85,12 +85,42 @@ AppLoader_XCI::LoadResult AppLoader_XCI::Load(Kernel::KProcess& process, Core::S return result; } -ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& out_file) { - return nca_loader->ReadRomFS(out_file); +ResultStatus AppLoader_XCI::VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) { + // Verify secure partition, as it is the only thing we can process. + auto secure_partition = xci->GetSecurePartitionNSP(); + + // Get list of all NCAs. + const auto ncas = secure_partition->GetNCAsCollapsed(); + + size_t total_size = 0; + size_t processed_size = 0; + + // Loop over NCAs, collecting the total size to verify. + for (const auto& nca : ncas) { + total_size += nca->GetBaseFile()->GetSize(); + } + + // Loop over NCAs again, verifying each. + for (const auto& nca : ncas) { + AppLoader_NCA loader_nca(nca->GetBaseFile()); + + const auto NcaProgressCallback = [&](size_t nca_processed_size, size_t nca_total_size) { + return progress_callback(processed_size + nca_processed_size, total_size); + }; + + const auto verification_result = loader_nca.VerifyIntegrity(NcaProgressCallback); + if (verification_result != ResultStatus::Success) { + return verification_result; + } + + processed_size += nca->GetBaseFile()->GetSize(); + } + + return ResultStatus::Success; } -u64 AppLoader_XCI::ReadRomFSIVFCOffset() const { - return nca_loader->ReadRomFSIVFCOffset(); +ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& out_file) { + return nca_loader->ReadRomFS(out_file); } ResultStatus AppLoader_XCI::ReadUpdateRaw(FileSys::VirtualFile& out_file) { diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h index 2affb6c6e..b02e136d3 100644 --- a/src/core/loader/xci.h +++ b/src/core/loader/xci.h @@ -45,8 +45,9 @@ public: LoadResult Load(Kernel::KProcess& process, Core::System& system) override; + ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) override; + ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override; - u64 ReadRomFSIVFCOffset() const override; ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; ResultStatus ReadProgramId(u64& out_program_id) override; ResultStatus ReadProgramIds(std::vector<u64>& out_program_ids) override; diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 513bc4edb..fa5273402 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -24,6 +24,16 @@ namespace Core::Memory { +namespace { + +bool AddressSpaceContains(const Common::PageTable& table, const Common::ProcessAddress addr, + const std::size_t size) { + const Common::ProcessAddress max_addr = 1ULL << table.GetAddressSpaceBits(); + return addr + size >= addr && addr + size <= max_addr; +} + +} // namespace + // Implementation class used to keep the specifics of the memory subsystem hidden // from outside classes. This also allows modification to the internals of the memory // subsystem without needing to rebuild all files that make use of the memory interface. @@ -191,6 +201,11 @@ struct Memory::Impl { std::size_t page_offset = addr & YUZU_PAGEMASK; bool user_accessible = true; + if (!AddressSpaceContains(page_table, addr, size)) [[unlikely]] { + on_unmapped(size, addr); + return false; + } + while (remaining_size) { const std::size_t copy_amount = std::min(static_cast<std::size_t>(YUZU_PAGESIZE) - page_offset, remaining_size); @@ -421,7 +436,7 @@ struct Memory::Impl { } void MarkRegionDebug(u64 vaddr, u64 size, bool debug) { - if (vaddr == 0) { + if (vaddr == 0 || !AddressSpaceContains(*current_page_table, vaddr, size)) { return; } @@ -478,7 +493,7 @@ struct Memory::Impl { } void RasterizerMarkRegionCached(u64 vaddr, u64 size, bool cached) { - if (vaddr == 0) { + if (vaddr == 0 || !AddressSpaceContains(*current_page_table, vaddr, size)) { return; } @@ -615,7 +630,7 @@ struct Memory::Impl { // AARCH64 masks the upper 16 bit of all memory accesses vaddr = vaddr & 0xffffffffffffULL; - if (vaddr >= 1uLL << current_page_table->GetAddressSpaceBits()) { + if (!AddressSpaceContains(*current_page_table, vaddr, 1)) [[unlikely]] { on_unmapped(); return nullptr; } diff --git a/src/core/memory.h b/src/core/memory.h index 2eb61ffd3..13047a545 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -509,9 +509,9 @@ class GuestMemory { public: GuestMemory() = delete; - explicit GuestMemory(M& memory_, u64 addr_, std::size_t size_, + explicit GuestMemory(M& memory, u64 addr, std::size_t size, Common::ScratchBuffer<T>* backup = nullptr) - : memory{memory_}, addr{addr_}, size{size_} { + : m_memory{memory}, m_addr{addr}, m_size{size} { static_assert(FLAGS & GuestMemoryFlags::Read || FLAGS & GuestMemoryFlags::Write); if constexpr (FLAGS & GuestMemoryFlags::Read) { Read(addr, size, backup); @@ -521,89 +521,97 @@ public: ~GuestMemory() = default; T* data() noexcept { - return data_span.data(); + return m_data_span.data(); } const T* data() const noexcept { - return data_span.data(); + return m_data_span.data(); + } + + size_t size() const noexcept { + return m_size; + } + + size_t size_bytes() const noexcept { + return this->size() * sizeof(T); } [[nodiscard]] T* begin() noexcept { - return data(); + return this->data(); } [[nodiscard]] const T* begin() const noexcept { - return data(); + return this->data(); } [[nodiscard]] T* end() noexcept { - return data() + size; + return this->data() + this->size(); } [[nodiscard]] const T* end() const noexcept { - return data() + size; + return this->data() + this->size(); } T& operator[](size_t index) noexcept { - return data_span[index]; + return m_data_span[index]; } const T& operator[](size_t index) const noexcept { - return data_span[index]; + return m_data_span[index]; } - void SetAddressAndSize(u64 addr_, std::size_t size_) noexcept { - addr = addr_; - size = size_; - addr_changed = true; + void SetAddressAndSize(u64 addr, std::size_t size) noexcept { + m_addr = addr; + m_size = size; + m_addr_changed = true; } - std::span<T> Read(u64 addr_, std::size_t size_, + std::span<T> Read(u64 addr, std::size_t size, Common::ScratchBuffer<T>* backup = nullptr) noexcept { - addr = addr_; - size = size_; - if (size == 0) { - is_data_copy = true; + m_addr = addr; + m_size = size; + if (m_size == 0) { + m_is_data_copy = true; return {}; } - if (TrySetSpan()) { + if (this->TrySetSpan()) { if constexpr (FLAGS & GuestMemoryFlags::Safe) { - memory.FlushRegion(addr, size * sizeof(T)); + m_memory.FlushRegion(m_addr, this->size_bytes()); } } else { if (backup) { - backup->resize_destructive(size); - data_span = *backup; + backup->resize_destructive(this->size()); + m_data_span = *backup; } else { - data_copy.resize(size); - data_span = std::span(data_copy); + m_data_copy.resize(this->size()); + m_data_span = std::span(m_data_copy); } - is_data_copy = true; - span_valid = true; + m_is_data_copy = true; + m_span_valid = true; if constexpr (FLAGS & GuestMemoryFlags::Safe) { - memory.ReadBlock(addr, data_span.data(), size * sizeof(T)); + m_memory.ReadBlock(m_addr, this->data(), this->size_bytes()); } else { - memory.ReadBlockUnsafe(addr, data_span.data(), size * sizeof(T)); + m_memory.ReadBlockUnsafe(m_addr, this->data(), this->size_bytes()); } } - return data_span; + return m_data_span; } void Write(std::span<T> write_data) noexcept { if constexpr (FLAGS & GuestMemoryFlags::Cached) { - memory.WriteBlockCached(addr, write_data.data(), size * sizeof(T)); + m_memory.WriteBlockCached(m_addr, write_data.data(), this->size_bytes()); } else if constexpr (FLAGS & GuestMemoryFlags::Safe) { - memory.WriteBlock(addr, write_data.data(), size * sizeof(T)); + m_memory.WriteBlock(m_addr, write_data.data(), this->size_bytes()); } else { - memory.WriteBlockUnsafe(addr, write_data.data(), size * sizeof(T)); + m_memory.WriteBlockUnsafe(m_addr, write_data.data(), this->size_bytes()); } } bool TrySetSpan() noexcept { - if (u8* ptr = memory.GetSpan(addr, size * sizeof(T)); ptr) { - data_span = {reinterpret_cast<T*>(ptr), size}; - span_valid = true; + if (u8* ptr = m_memory.GetSpan(m_addr, this->size_bytes()); ptr) { + m_data_span = {reinterpret_cast<T*>(ptr), this->size()}; + m_span_valid = true; return true; } return false; @@ -611,36 +619,36 @@ public: protected: bool IsDataCopy() const noexcept { - return is_data_copy; + return m_is_data_copy; } bool AddressChanged() const noexcept { - return addr_changed; + return m_addr_changed; } - M& memory; - u64 addr; - size_t size; - std::span<T> data_span{}; - std::vector<T> data_copy; - bool span_valid{false}; - bool is_data_copy{false}; - bool addr_changed{false}; + M& m_memory; + u64 m_addr{}; + size_t m_size{}; + std::span<T> m_data_span{}; + std::vector<T> m_data_copy{}; + bool m_span_valid{false}; + bool m_is_data_copy{false}; + bool m_addr_changed{false}; }; template <typename M, typename T, GuestMemoryFlags FLAGS> class GuestMemoryScoped : public GuestMemory<M, T, FLAGS> { public: GuestMemoryScoped() = delete; - explicit GuestMemoryScoped(M& memory_, u64 addr_, std::size_t size_, + explicit GuestMemoryScoped(M& memory, u64 addr, std::size_t size, Common::ScratchBuffer<T>* backup = nullptr) - : GuestMemory<M, T, FLAGS>(memory_, addr_, size_, backup) { + : GuestMemory<M, T, FLAGS>(memory, addr, size, backup) { if constexpr (!(FLAGS & GuestMemoryFlags::Read)) { if (!this->TrySetSpan()) { if (backup) { - this->data_span = *backup; - this->span_valid = true; - this->is_data_copy = true; + this->m_data_span = *backup; + this->m_span_valid = true; + this->m_is_data_copy = true; } } } @@ -648,24 +656,21 @@ public: ~GuestMemoryScoped() { if constexpr (FLAGS & GuestMemoryFlags::Write) { - if (this->size == 0) [[unlikely]] { + if (this->size() == 0) [[unlikely]] { return; } if (this->AddressChanged() || this->IsDataCopy()) { - ASSERT(this->span_valid); + ASSERT(this->m_span_valid); if constexpr (FLAGS & GuestMemoryFlags::Cached) { - this->memory.WriteBlockCached(this->addr, this->data_span.data(), - this->size * sizeof(T)); + this->m_memory.WriteBlockCached(this->m_addr, this->data(), this->size_bytes()); } else if constexpr (FLAGS & GuestMemoryFlags::Safe) { - this->memory.WriteBlock(this->addr, this->data_span.data(), - this->size * sizeof(T)); + this->m_memory.WriteBlock(this->m_addr, this->data(), this->size_bytes()); } else { - this->memory.WriteBlockUnsafe(this->addr, this->data_span.data(), - this->size * sizeof(T)); + this->m_memory.WriteBlockUnsafe(this->m_addr, this->data(), this->size_bytes()); } } else if constexpr (FLAGS & GuestMemoryFlags::Safe) { - this->memory.InvalidateRegion(this->addr, this->size * sizeof(T)); + this->m_memory.InvalidateRegion(this->m_addr, this->size_bytes()); } } } diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp index 7b52f61a7..a06e99166 100644 --- a/src/core/memory/cheat_engine.cpp +++ b/src/core/memory/cheat_engine.cpp @@ -154,7 +154,7 @@ std::vector<CheatEntry> TextCheatParser::Parse(std::string_view data) const { return {}; } - const auto value = static_cast<u32>(std::stoul(hex, nullptr, 0x10)); + const auto value = static_cast<u32>(std::strtoul(hex.c_str(), nullptr, 0x10)); out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] = value; diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp index b5b3e7eda..ed875d444 100644 --- a/src/core/reporter.cpp +++ b/src/core/reporter.cpp @@ -117,8 +117,8 @@ json GetProcessorStateDataAuto(Core::System& system) { arm.SaveContext(context); return GetProcessorStateData(process->Is64BitProcess() ? "AArch64" : "AArch32", - GetInteger(process->GetPageTable().GetCodeRegionStart()), - context.sp, context.pc, context.pstate, context.cpu_registers); + GetInteger(process->GetEntryPoint()), context.sp, context.pc, + context.pstate, context.cpu_registers); } json GetBacktraceData(Core::System& system) { diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp index 7a2f3c90a..c26179e03 100644 --- a/src/core/telemetry_session.cpp +++ b/src/core/telemetry_session.cpp @@ -14,6 +14,7 @@ #include "common/logging/log.h" #include "common/settings.h" +#include "common/settings_enums.h" #include "core/file_sys/control_metadata.h" #include "core/file_sys/patch_manager.h" #include "core/loader/loader.h" @@ -61,13 +62,13 @@ static const char* TranslateRenderer(Settings::RendererBackend backend) { return "Unknown"; } -static const char* TranslateGPUAccuracyLevel(Settings::GPUAccuracy backend) { +static const char* TranslateGPUAccuracyLevel(Settings::GpuAccuracy backend) { switch (backend) { - case Settings::GPUAccuracy::Normal: + case Settings::GpuAccuracy::Normal: return "Normal"; - case Settings::GPUAccuracy::High: + case Settings::GpuAccuracy::High: return "High"; - case Settings::GPUAccuracy::Extreme: + case Settings::GpuAccuracy::Extreme: return "Extreme"; } return "Unknown"; @@ -77,9 +78,9 @@ static const char* TranslateNvdecEmulation(Settings::NvdecEmulation backend) { switch (backend) { case Settings::NvdecEmulation::Off: return "Off"; - case Settings::NvdecEmulation::CPU: + case Settings::NvdecEmulation::Cpu: return "CPU"; - case Settings::NvdecEmulation::GPU: + case Settings::NvdecEmulation::Gpu: return "GPU"; } return "Unknown"; @@ -91,14 +92,26 @@ static constexpr const char* TranslateVSyncMode(Settings::VSyncMode mode) { return "Immediate"; case Settings::VSyncMode::Mailbox: return "Mailbox"; - case Settings::VSyncMode::FIFO: + case Settings::VSyncMode::Fifo: return "FIFO"; - case Settings::VSyncMode::FIFORelaxed: + case Settings::VSyncMode::FifoRelaxed: return "FIFO Relaxed"; } return "Unknown"; } +static constexpr const char* TranslateASTCDecodeMode(Settings::AstcDecodeMode mode) { + switch (mode) { + case Settings::AstcDecodeMode::Cpu: + return "CPU"; + case Settings::AstcDecodeMode::Gpu: + return "GPU"; + case Settings::AstcDecodeMode::CpuAsynchronous: + return "CPU Asynchronous"; + } + return "Unknown"; +} + u64 GetTelemetryId() { u64 telemetry_id{}; const auto filename = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir) / "telemetry_id"; @@ -240,7 +253,8 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader, // Log user configuration information constexpr auto field_type = Telemetry::FieldType::UserConfig; - AddField(field_type, "Audio_SinkId", Settings::values.sink_id.GetValue()); + AddField(field_type, "Audio_SinkId", + Settings::CanonicalizeEnum(Settings::values.sink_id.GetValue())); AddField(field_type, "Core_UseMultiCore", Settings::values.use_multi_core.GetValue()); AddField(field_type, "Renderer_Backend", TranslateRenderer(Settings::values.renderer_backend.GetValue())); @@ -254,14 +268,15 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader, Settings::values.use_asynchronous_gpu_emulation.GetValue()); AddField(field_type, "Renderer_NvdecEmulation", TranslateNvdecEmulation(Settings::values.nvdec_emulation.GetValue())); - AddField(field_type, "Renderer_AccelerateASTC", Settings::values.accelerate_astc.GetValue()); + AddField(field_type, "Renderer_AccelerateASTC", + TranslateASTCDecodeMode(Settings::values.accelerate_astc.GetValue())); AddField(field_type, "Renderer_UseVsync", TranslateVSyncMode(Settings::values.vsync_mode.GetValue())); AddField(field_type, "Renderer_ShaderBackend", static_cast<u32>(Settings::values.shader_backend.GetValue())); AddField(field_type, "Renderer_UseAsynchronousShaders", Settings::values.use_asynchronous_shaders.GetValue()); - AddField(field_type, "System_UseDockedMode", Settings::values.use_docked_mode.GetValue()); + AddField(field_type, "System_UseDockedMode", Settings::IsDockedMode()); } bool TelemetrySession::SubmitTestcase() { diff --git a/src/core/tools/renderdoc.cpp b/src/core/tools/renderdoc.cpp new file mode 100644 index 000000000..947fa6cb3 --- /dev/null +++ b/src/core/tools/renderdoc.cpp @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <renderdoc_app.h> + +#include "common/assert.h" +#include "common/dynamic_library.h" +#include "core/tools/renderdoc.h" + +#ifdef _WIN32 +#include <windows.h> +#else +#include <dlfcn.h> +#endif + +namespace Tools { + +RenderdocAPI::RenderdocAPI() { +#ifdef WIN32 + if (HMODULE mod = GetModuleHandleA("renderdoc.dll")) { + const auto RENDERDOC_GetAPI = + reinterpret_cast<pRENDERDOC_GetAPI>(GetProcAddress(mod, "RENDERDOC_GetAPI")); + const s32 ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_6_0, (void**)&rdoc_api); + ASSERT(ret == 1); + } +#else +#ifdef ANDROID + static constexpr const char RENDERDOC_LIB[] = "libVkLayer_GLES_RenderDoc.so"; +#else + static constexpr const char RENDERDOC_LIB[] = "librenderdoc.so"; +#endif + if (void* mod = dlopen(RENDERDOC_LIB, RTLD_NOW | RTLD_NOLOAD)) { + const auto RENDERDOC_GetAPI = + reinterpret_cast<pRENDERDOC_GetAPI>(dlsym(mod, "RENDERDOC_GetAPI")); + const s32 ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_6_0, (void**)&rdoc_api); + ASSERT(ret == 1); + } +#endif +} + +RenderdocAPI::~RenderdocAPI() = default; + +void RenderdocAPI::ToggleCapture() { + if (!rdoc_api) [[unlikely]] { + return; + } + if (!is_capturing) { + rdoc_api->StartFrameCapture(NULL, NULL); + } else { + rdoc_api->EndFrameCapture(NULL, NULL); + } + is_capturing = !is_capturing; +} + +} // namespace Tools diff --git a/src/core/tools/renderdoc.h b/src/core/tools/renderdoc.h new file mode 100644 index 000000000..0e5e43da5 --- /dev/null +++ b/src/core/tools/renderdoc.h @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +struct RENDERDOC_API_1_6_0; + +namespace Tools { + +class RenderdocAPI { +public: + explicit RenderdocAPI(); + ~RenderdocAPI(); + + void ToggleCapture(); + +private: + RENDERDOC_API_1_6_0* rdoc_api{}; + bool is_capturing{false}; +}; + +} // namespace Tools |