diff --git a/src/android/app/src/main/java/org/citron/citron_emu/fragments/HomeSettingsFragment.kt b/src/android/app/src/main/java/org/citron/citron_emu/fragments/HomeSettingsFragment.kt index 17d3b91a3f..7c437bdb76 100644 --- a/src/android/app/src/main/java/org/citron/citron_emu/fragments/HomeSettingsFragment.kt +++ b/src/android/app/src/main/java/org/citron/citron_emu/fragments/HomeSettingsFragment.kt @@ -426,7 +426,7 @@ class HomeSettingsFragment : Fragment() { binding.linearLayoutSettings.updatePadding(bottom = spacingNavigation) - if (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_LTR) { + if (view.layoutDirection == View.LAYOUT_DIRECTION_LTR) { binding.linearLayoutSettings.updatePadding(left = spacingNavigationRail) } else { binding.linearLayoutSettings.updatePadding(right = spacingNavigationRail) diff --git a/src/android/app/src/main/java/org/citron/citron_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/citron/citron_emu/ui/main/MainActivity.kt index 736a2e3536..3406a1bb6b 100644 --- a/src/android/app/src/main/java/org/citron/citron_emu/ui/main/MainActivity.kt +++ b/src/android/app/src/main/java/org/citron/citron_emu/ui/main/MainActivity.kt @@ -681,8 +681,8 @@ class MainActivity : AppCompatActivity(), ThemeProvider { } // Reinitialize relevant data - NativeLibrary.initializeSystem(true) NativeConfig.initializeGlobalConfig() + NativeLibrary.initializeSystem(true) gamesViewModel.reloadGames(false) driverViewModel.reloadDriverData() diff --git a/src/android/app/src/main/java/org/citron/citron_emu/utils/DirectoryInitialization.kt b/src/android/app/src/main/java/org/citron/citron_emu/utils/DirectoryInitialization.kt index 97f106a579..99cc10f854 100644 --- a/src/android/app/src/main/java/org/citron/citron_emu/utils/DirectoryInitialization.kt +++ b/src/android/app/src/main/java/org/citron/citron_emu/utils/DirectoryInitialization.kt @@ -23,9 +23,10 @@ object DirectoryInitialization { fun start() { if (!areDirectoriesReady) { initializeInternalStorage() - NativeLibrary.initializeSystem(false) NativeConfig.initializeGlobalConfig() migrateSettings() + NativeLibrary.reloadKeys() + NativeLibrary.initializeSystem(false) areDirectoriesReady = true } } diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index c83a3844ff..2bce197fe5 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -209,7 +209,22 @@ void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath) } void EmulationSession::InitializeSystem(bool reload) { + std::scoped_lock lock(m_mutex); + + if (m_is_running || m_load_result == Core::SystemResultStatus::Success) { + LOG_ERROR(Frontend, "Refusing to initialize system while emulation is active"); + return; + } + + auto& fs_controller = m_system.GetFileSystemController(); + if (fs_controller.GetInitStage() < Service::FileSystem::InitStage::SETTINGS_READY) { + LOG_ERROR(Frontend, "Refusing to initialize system before settings are ready"); + return; + } + if (!reload) { + m_system.Initialize(); + // Initialize logging system Common::Log::Initialize(); Common::Log::SetColorConsoleBackendEnabled(true); @@ -218,6 +233,11 @@ void EmulationSession::InitializeSystem(bool reload) { m_input_subsystem.Initialize(); } + Core::Crypto::KeyManager::Instance().ReloadKeys(); + Core::Crypto::KeyManager::Instance().PopulateTickets(); + LOG_INFO(Frontend, "InitializeSystem: keys_loaded_before_content={}", + Core::Crypto::KeyManager::Instance().AreKeysLoaded()); + // Initialize filesystem. m_system.SetFilesystem(m_vfs); m_system.GetUserChannel().clear(); @@ -225,7 +245,52 @@ void EmulationSession::InitializeSystem(bool reload) { m_system.SetContentProvider(std::make_unique()); m_system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::FrontendManual, m_manual_provider.get()); - m_system.GetFileSystemController().CreateFactories(*m_vfs); + fs_controller.SetInitStage(Service::FileSystem::InitStage::FS_READY); + fs_controller.InitializeContentSystem(*m_vfs); +} + + +void EmulationSession::RefreshContentSystemUnlocked() +{ + auto& fs = m_system.GetFileSystemController(); + if (auto filesystem = m_system.GetFilesystem()) { + fs.InitializeContentSystem(*filesystem); + } +} + + +void EmulationSession::RefreshContentSystem() { + std::scoped_lock lock(m_mutex); + RefreshContentSystemUnlocked(); +} + + +bool EmulationSession::RefreshContentIfIdle(bool keys_loaded) +{ + if (!keys_loaded) { + return false; + } + + std::scoped_lock lock(m_mutex); + if (m_is_running || m_load_result == Core::SystemResultStatus::Success) { + return false; + } + + RefreshContentSystemUnlocked(); + return true; +} + +void EmulationSession::SetFilesystemInitStage(Service::FileSystem::InitStage stage) { + std::scoped_lock lock(m_mutex); + m_system.GetFileSystemController().SetInitStage(stage); +} + +void EmulationSession::PromoteFilesystemInitStage(Service::FileSystem::InitStage stage) { + std::scoped_lock lock(m_mutex); + auto& controller = m_system.GetFileSystemController(); + if (controller.GetInitStage() < stage) { + controller.SetInitStage(stage); + } } void EmulationSession::SetAppletId(int applet_id) { @@ -561,8 +626,16 @@ jobjectArray Java_org_citron_citron_1emu_utils_GpuDriverHelper_getSystemDriverIn } jboolean Java_org_citron_citron_1emu_NativeLibrary_reloadKeys(JNIEnv* env, jclass clazz) { + auto& session = EmulationSession::GetInstance(); Core::Crypto::KeyManager::Instance().ReloadKeys(); - return static_cast(Core::Crypto::KeyManager::Instance().AreKeysLoaded()); + const bool keys_loaded = Core::Crypto::KeyManager::Instance().AreKeysLoaded(); + + const bool refreshed_content = session.RefreshContentIfIdle(keys_loaded); + LOG_INFO(Frontend, + "reloadKeys: keys_loaded={}, refreshed_content={}", + keys_loaded, refreshed_content); + + return static_cast(keys_loaded); } void Java_org_citron_citron_1emu_NativeLibrary_unpauseEmulation(JNIEnv* env, jclass clazz) { @@ -588,9 +661,6 @@ jboolean Java_org_citron_citron_1emu_NativeLibrary_isPaused(JNIEnv* env, jclass void Java_org_citron_citron_1emu_NativeLibrary_initializeSystem(JNIEnv* env, jclass clazz, jboolean reload) { // Initialize the emulated system. - if (!reload) { - EmulationSession::GetInstance().System().Initialize(); - } EmulationSession::GetInstance().InitializeSystem(reload); } @@ -893,9 +963,16 @@ void Java_org_citron_citron_1emu_NativeLibrary_clearFilesystemProvider(JNIEnv* e } jboolean Java_org_citron_citron_1emu_NativeLibrary_areKeysPresent(JNIEnv* env, jobject jobj) { - auto& system = EmulationSession::GetInstance().System(); - system.GetFileSystemController().CreateFactories(*system.GetFilesystem()); + auto& session = EmulationSession::GetInstance(); + Core::Crypto::KeyManager::Instance().ReloadKeys(); + const bool keys_loaded = + Core::Crypto::KeyManager::Instance().AreKeysLoaded(); + + const bool refreshed_content = session.RefreshContentIfIdle(keys_loaded); + + LOG_INFO(Frontend, "areKeysPresent: refreshed_content={}", refreshed_content); + return ContentManager::AreKeysPresent(); } diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h index 6a4551ada2..2688e033e7 100644 --- a/src/android/app/src/main/jni/native.h +++ b/src/android/app/src/main/jni/native.h @@ -7,6 +7,7 @@ #include "core/core.h" #include "core/file_sys/registered_cache.h" #include "core/hle/service/acc/profile_manager.h" +#include "core/hle/service/filesystem/filesystem.h" #include "core/perf_stats.h" #include "frontend_common/content_manager.h" #include "jni/emu_window/emu_window.h" @@ -46,6 +47,10 @@ public: const Core::PerfStatsResults& PerfStats(); void ConfigureFilesystemProvider(const std::string& filepath); void InitializeSystem(bool reload); + void RefreshContentSystem(); + bool RefreshContentIfIdle(bool keys_loaded); + void SetFilesystemInitStage(Service::FileSystem::InitStage stage); + void PromoteFilesystemInitStage(Service::FileSystem::InitStage stage); void SetAppletId(int applet_id); Core::SystemResultStatus InitializeEmulation(const std::string& filepath, const std::size_t program_index, @@ -58,6 +63,7 @@ public: static u64 GetProgramId(JNIEnv* env, jstring jprogramId); private: + void RefreshContentSystemUnlocked(); static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max); static void OnEmulationStopped(Core::SystemResultStatus result); static void ChangeProgram(std::size_t program_index); diff --git a/src/android/app/src/main/jni/native_config.cpp b/src/android/app/src/main/jni/native_config.cpp index 7293978c51..a29d965bea 100644 --- a/src/android/app/src/main/jni/native_config.cpp +++ b/src/android/app/src/main/jni/native_config.cpp @@ -12,11 +12,24 @@ #include "common/logging.h" #include "common/settings.h" #include "frontend_common/config.h" +#include "core/hle/service/filesystem/filesystem.h" #include "native.h" std::unique_ptr global_config; std::unique_ptr per_game_config; +namespace { + +void SetFilesystemInitStage(Service::FileSystem::InitStage stage) { + EmulationSession::GetInstance().SetFilesystemInitStage(stage); +} + +void PromoteFilesystemInitStage(Service::FileSystem::InitStage stage) { + EmulationSession::GetInstance().PromoteFilesystemInitStage(stage); +} + +} // namespace + template Settings::Setting* getSetting(JNIEnv* env, jstring jkey) { auto key = Common::Android::GetJString(env, jkey); @@ -36,14 +49,17 @@ extern "C" { void Java_org_citron_citron_1emu_utils_NativeConfig_initializeGlobalConfig(JNIEnv* env, jobject obj) { global_config = std::make_unique(); + PromoteFilesystemInitStage(Service::FileSystem::InitStage::SETTINGS_READY); } void Java_org_citron_citron_1emu_utils_NativeConfig_unloadGlobalConfig(JNIEnv* env, jobject obj) { global_config.reset(); + SetFilesystemInitStage(Service::FileSystem::InitStage::NONE); } void Java_org_citron_citron_1emu_utils_NativeConfig_reloadGlobalConfig(JNIEnv* env, jobject obj) { global_config->AndroidConfig::ReloadAllValues(); + PromoteFilesystemInitStage(Service::FileSystem::InitStage::SETTINGS_READY); } void Java_org_citron_citron_1emu_utils_NativeConfig_saveGlobalConfig(JNIEnv* env, jobject obj) { diff --git a/src/citron/main.cpp b/src/citron/main.cpp index 5faabe6cb8..6d739424c0 100644 --- a/src/citron/main.cpp +++ b/src/citron/main.cpp @@ -452,7 +452,7 @@ GMainWindow::GMainWindow(std::unique_ptr config_, bool has_broken_vulk // 1. First, create the factories LOG_INFO(Frontend, "Initializing factories..."); - system->GetFileSystemController().CreateFactories(*vfs); + system->GetFileSystemController().InitializeContentSystem(*vfs); autoloader_provider = std::make_unique(); system->RegisterContentProvider(FileSys::ContentProviderUnionSlot::Autoloader, @@ -2556,7 +2556,7 @@ void GMainWindow::OnEmulationStopped() { game_list->CancelPopulation(); // This is necessary to reset the in-memory state for the next launch. - system->GetFileSystemController().CreateFactories(*vfs, true); + system->GetFileSystemController().InitializeContentSystem(*vfs, true); // Refresh the game list now that the filesystem is valid again. game_list->ClearLaunchOverlays(); @@ -5130,7 +5130,7 @@ void GMainWindow::OnInstallFirmwareFromZip() { } // Re-scan VFS for the newly placed firmware files. - system->GetFileSystemController().CreateFactories(*vfs); + system->GetFileSystemController().InitializeContentSystem(*vfs); auto VerifyFirmwareCallback = [&](size_t total_size, size_t processed_size) { progress.setValue(85 + static_cast((processed_size * 15) / total_size)); @@ -5294,7 +5294,7 @@ void GMainWindow::OnInstallFirmware() { } // Re-scan VFS for the newly placed firmware files. - system->GetFileSystemController().CreateFactories(*vfs); + system->GetFileSystemController().InitializeContentSystem(*vfs); auto VerifyFirmwareCallback = [&](size_t total_size, size_t processed_size) { progress.setValue(90 + static_cast((processed_size * 10) / total_size)); @@ -5391,7 +5391,7 @@ void GMainWindow::OnInstallDecryptionKeys() { // and re-populate the game list in the UI if the user has already added // game folders. Core::Crypto::KeyManager::Instance().ReloadKeys(); - system->GetFileSystemController().CreateFactories(*vfs); + system->GetFileSystemController().InitializeContentSystem(*vfs); game_list->PopulateAsync(UISettings::values.game_dirs); if (ContentManager::AreKeysPresent()) { @@ -6066,7 +6066,7 @@ void GMainWindow::OnMouseActivity() { } void GMainWindow::OnCheckFirmwareDecryption() { - system->GetFileSystemController().CreateFactories(*vfs); + system->GetFileSystemController().InitializeContentSystem(*vfs); if (!ContentManager::AreKeysPresent()) { QMessageBox::warning(this, tr("Derivation Components Missing"), tr("Encryption keys are missing. " diff --git a/src/citron_cmd/citron.cpp b/src/citron_cmd/citron.cpp index a251f18855..7eaa203ee7 100644 --- a/src/citron_cmd/citron.cpp +++ b/src/citron_cmd/citron.cpp @@ -360,7 +360,7 @@ int main(int argc, char** argv) { system.SetContentProvider(std::make_unique()); system.SetFilesystem(std::make_shared()); - system.GetFileSystemController().CreateFactories(*system.GetFilesystem()); + system.GetFileSystemController().InitializeContentSystem(*system.GetFilesystem()); system.GetUserChannel().clear(); Service::AM::FrontendAppletParameters load_parameters{ diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index edab509765..8aecb5bf90 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -234,18 +234,33 @@ Ticket Ticket::Read(std::span raw_data) { switch (sig_type) { case SignatureType::RSA_4096_SHA1: case SignatureType::RSA_4096_SHA256: { + if (raw_data.size() < sizeof(RSA4096Ticket)) { + LOG_WARNING(Crypto, "Attempted to parse RSA4096 ticket buffer with invalid size {}.", + raw_data.size()); + return Ticket{std::monostate()}; + } RSA4096Ticket ticket{}; std::memcpy(&ticket, raw_data.data(), sizeof(ticket)); return Ticket{ticket}; } case SignatureType::RSA_2048_SHA1: case SignatureType::RSA_2048_SHA256: { + if (raw_data.size() < sizeof(RSA2048Ticket)) { + LOG_WARNING(Crypto, "Attempted to parse RSA2048 ticket buffer with invalid size {}.", + raw_data.size()); + return Ticket{std::monostate()}; + } RSA2048Ticket ticket{}; std::memcpy(&ticket, raw_data.data(), sizeof(ticket)); return Ticket{ticket}; } case SignatureType::ECDSA_SHA1: case SignatureType::ECDSA_SHA256: { + if (raw_data.size() < sizeof(ECDSATicket)) { + LOG_WARNING(Crypto, "Attempted to parse ECDSA ticket buffer with invalid size {}.", + raw_data.size()); + return Ticket{std::monostate()}; + } ECDSATicket ticket{}; std::memcpy(&ticket, raw_data.data(), sizeof(ticket)); return Ticket{ticket}; @@ -479,6 +494,22 @@ Loader::ResultStatus DeriveSDKeys(std::array& sd_keys, KeyManager& ke return Loader::ResultStatus::Success; } +static std::optional GetTicketSize(SignatureType sig_type) { + switch (sig_type) { + case SignatureType::RSA_4096_SHA1: + case SignatureType::RSA_4096_SHA256: + return sizeof(RSA4096Ticket); + case SignatureType::RSA_2048_SHA1: + case SignatureType::RSA_2048_SHA256: + return sizeof(RSA2048Ticket); + case SignatureType::ECDSA_SHA1: + case SignatureType::ECDSA_SHA256: + return sizeof(ECDSATicket); + default: + return std::nullopt; + } +} + std::vector GetTicketblob(const Common::FS::IOFile& ticket_save) { if (!ticket_save.IsOpen()) { return {}; @@ -490,21 +521,46 @@ std::vector GetTicketblob(const Common::FS::IOFile& ticket_save) { } std::vector out; - 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) { - // 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); - } + for (std::size_t offset = 0; offset + sizeof(SignatureType) <= buffer.size(); ++offset) { + SignatureType sig_type; + std::memcpy(&sig_type, buffer.data() + offset, sizeof(sig_type)); + + const auto ticket_size = GetTicketSize(sig_type); + if (!ticket_size) { + continue; + } + if (offset + *ticket_size > buffer.size()) { + break; + } + + auto ticket = Ticket::Read(std::span{buffer.data() + offset, *ticket_size}); + offset += *ticket_size - 1; + if (ticket.IsValid()) { + out.push_back(ticket); } } return out; } +static std::span GetTicketBytes(const Ticket& ticket) { + if (const auto* rsa4096 = std::get_if(&ticket.data)) { + return {reinterpret_cast(rsa4096), sizeof(RSA4096Ticket)}; + } + if (const auto* rsa2048 = std::get_if(&ticket.data)) { + return {reinterpret_cast(rsa2048), sizeof(RSA2048Ticket)}; + } + if (const auto* ecdsa = std::get_if(&ticket.data)) { + return {reinterpret_cast(ecdsa), sizeof(ECDSATicket)}; + } + + return {}; +} + +static bool TicketMatchesRightsId(const Ticket& ticket, const std::array& rights_id) { + return ticket.IsValid() && ticket.GetData().rights_id == rights_id; +} + template static std::array operator^(const std::array& lhs, const std::array& rhs) { @@ -1282,4 +1338,53 @@ bool KeyManager::AddTicket(const Ticket& ticket) { SetKey(S128KeyType::Titlekey, key.value(), rights_id[1], rights_id[0]); return true; } + +bool KeyManager::PersistTicket(const Ticket& ticket) { + if (!ticket.IsValid()) { + LOG_WARNING(Crypto, "Attempted to persist invalid ticket."); + return false; + } + + const auto ticket_path = Common::FS::GetCitronPath(Common::FS::CitronPath::NANDDir) / + (ticket.GetData().type == TitleKeyType::Common + ? "system/save/80000000000000e1" + : "system/save/80000000000000e2"); + if (!Common::FS::CreateParentDirs(ticket_path)) { + LOG_ERROR(Crypto, "Failed to create ticket save directory for {}", + Common::FS::PathToUTF8String(ticket_path)); + return false; + } + + if (Common::FS::Exists(ticket_path)) { + const Common::FS::IOFile existing_file{ticket_path, Common::FS::FileAccessMode::Read, + Common::FS::FileType::BinaryFile}; + const auto existing_tickets = GetTicketblob(existing_file); + if (std::any_of(existing_tickets.begin(), existing_tickets.end(), + [&ticket](const Ticket& existing_ticket) { + return TicketMatchesRightsId(existing_ticket, + ticket.GetData().rights_id); + })) { + return true; + } + } + + const auto ticket_bytes = GetTicketBytes(ticket); + if (ticket_bytes.empty()) { + LOG_WARNING(Crypto, "Unable to serialize invalid ticket variant."); + return false; + } + + const Common::FS::IOFile ticket_save{ticket_path, Common::FS::FileAccessMode::Append, + Common::FS::FileType::BinaryFile}; + if (!ticket_save.IsOpen()) { + return false; + } + + if (ticket_save.WriteSpan(ticket_bytes) != ticket_bytes.size()) { + LOG_ERROR(Crypto, "Failed to write ticket to {}", Common::FS::PathToUTF8String(ticket_path)); + return false; + } + + return ticket_save.Flush(); +} } // namespace Core::Crypto diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h index 2250eccec1..56c4e7e4f1 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h @@ -291,6 +291,7 @@ public: const std::map& GetPersonalizedTickets() const; bool AddTicket(const Ticket& ticket); + bool PersistTicket(const Ticket& ticket); void ReloadKeys(); bool AreKeysLoaded() const; diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 21f93dd4a0..df950da0fb 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -46,6 +46,12 @@ namespace FileSys { namespace { +// patch_manager.cpp only + const ContentProviderUnion* GetUnionProvider(const ContentProvider& p) { + return p.IsUnionProvider() + ? static_cast(&p) + : nullptr; +} constexpr u32 SINGLE_BYTE_MODULUS = 0x100; @@ -172,6 +178,8 @@ bool IsDirValidAndNonEmpty(const VirtualDir& dir) { } } // Anonymous namespace + + PatchManager::PatchManager(u64 title_id_, const Service::FileSystem::FileSystemController& fs_controller_, const ContentProvider& content_provider_) @@ -235,13 +243,10 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { if (found_best) { const auto update_tid = GetUpdateTitleID(title_id); if (active_update.is_external) { - const auto* content_provider_union = - static_cast(&content_provider); + const auto* content_provider_union = GetUnionProvider(content_provider); if (content_provider_union) { - if (const auto* external_provider = content_provider_union->GetExternalProvider()) { - best_update_raw = external_provider->GetEntryForVersion( - update_tid, ContentRecordType::Program, best_version); - } + best_update_raw = content_provider_union->GetExternalEntryForVersion( + update_tid, ContentRecordType::Program, best_version); } } else { best_update_raw = content_provider.GetEntryRaw(update_tid, ContentRecordType::Program); @@ -572,19 +577,25 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs if (found_best) { const auto update_tid = GetUpdateTitleID(title_id); if (active_update.is_external) { - const auto* content_provider_union = - static_cast(&content_provider); + const auto* content_provider_union = GetUnionProvider(content_provider); if (content_provider_union) { - if (const auto* external_provider = content_provider_union->GetExternalProvider()) { - best_update_raw = external_provider->GetEntryForVersion( - update_tid, type, best_version); - } + best_update_raw = + content_provider_union->GetExternalEntryForVersion(update_tid, type, + best_version); } } else { best_update_raw = content_provider.GetEntryRaw(update_tid, type); } } + if (type == ContentRecordType::Program || type == ContentRecordType::Control) { + LOG_INFO(Loader, + "PatchRomFS update lookup: title_id={:016X}, type={:02X}, found_best={}, " + "best_version={}, best_update_raw={}", + title_id, static_cast(type), found_best, best_version, + best_update_raw != nullptr); + } + // 3. Packed Update (Fallback) // If we still haven't found a best update, or if the packed one is enabled (named "Update" // usually? Or "Update (PACKED)"?) The GetPatches logic names it "Update" with version @@ -708,8 +719,7 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw) const { const auto update_disabled = std::find(disabled.cbegin(), disabled.cend(), "Update") != disabled.cend(); - const auto* content_provider_union = - static_cast(&content_provider); + const auto* content_provider_union = GetUnionProvider(content_provider); bool is_nand_control = true; bool is_nand_program = true; if (content_provider_union) { @@ -758,84 +768,79 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw) const { // Next, check for external updates if (content_provider_union) { - const auto* external_provider = content_provider_union->GetExternalProvider(); - if (external_provider) { - const auto updates = external_provider->ListUpdateVersions(update_tid); - for (const auto& update : updates) { - // Canonical name uses the raw version number (via FormatTitleVersion) so that - // the disabled_addons key matches what PatchExeFS/PatchRomFS check at boot. - // The NACP-derived string is only used as a UI display label. - const auto canonical_version_str = FormatTitleVersion(update.version); - const auto canonical_name = - fmt::format("External Files/Update v{}", canonical_version_str); + const auto updates = content_provider_union->ListExternalUpdateVersions(update_tid); + for (const auto& update : updates) { + // Canonical name uses the raw version number (via FormatTitleVersion) so that + // the disabled_addons key matches what PatchExeFS/PatchRomFS check at boot. + // The NACP-derived string is only used as a UI display label. + const auto canonical_version_str = FormatTitleVersion(update.version); + const auto canonical_name = + fmt::format("External Files/Update v{}", canonical_version_str); - // Attempt to resolve a human-readable display version from the Control NCA. - std::string display_version_str; - const auto control_file = external_provider->GetEntryForVersion( - update_tid, ContentRecordType::Control, update.version); - if (control_file) { - NCA control_nca(control_file); - if (control_nca.GetStatus() == Loader::ResultStatus::Success) { - if (auto control_romfs = control_nca.GetRomFS()) { - if (auto extracted = ExtractRomFS(control_romfs)) { - auto nacp_file = extracted->GetFile("control.nacp"); - if (!nacp_file) { - nacp_file = extracted->GetFile("Control.nacp"); - } - if (nacp_file) { - NACP control_nacp(nacp_file); - display_version_str = CleanNacpVersion(control_nacp.GetVersionString()); - } + // Attempt to resolve a human-readable display version from the Control NCA. + std::string display_version_str; + const auto control_file = content_provider_union->GetExternalEntryForVersion( + update_tid, ContentRecordType::Control, update.version); + if (control_file) { + NCA control_nca(control_file); + if (control_nca.GetStatus() == Loader::ResultStatus::Success) { + if (auto control_romfs = control_nca.GetRomFS()) { + if (auto extracted = ExtractRomFS(control_romfs)) { + auto nacp_file = extracted->GetFile("control.nacp"); + if (!nacp_file) { + nacp_file = extracted->GetFile("Control.nacp"); + } + if (nacp_file) { + NACP control_nacp(nacp_file); + display_version_str = + CleanNacpVersion(control_nacp.GetVersionString()); } } } } - - // The name stored in disabled_addons is the canonical one (FormatTitleVersion), - // ensuring stability across reboots regardless of NACP parse success. - // Also check the NACP-derived name for backwards compatibility. - bool patch_disabled = - std::find(disabled.cbegin(), disabled.cend(), canonical_name) != - disabled.cend(); - if (!patch_disabled && !display_version_str.empty() && - display_version_str != canonical_version_str) { - const auto nacp_name = - fmt::format("External Files/Update v{}", display_version_str); - patch_disabled = - std::find(disabled.cbegin(), disabled.cend(), nacp_name) != - disabled.cend(); - } - - // Use the human-readable string for display if available. - const auto display_name = display_version_str.empty() - ? canonical_name - : fmt::format("External Files/Update v{}", - display_version_str); - const auto version_str = - display_version_str.empty() ? canonical_version_str : display_version_str; - - // Deduplicate against installed system update if versions match - bool exists = false; - for (const auto& existing : out) { - if (existing.type == PatchType::Update && - existing.version == canonical_version_str) { - exists = true; - break; - } - } - if (exists) - continue; - - // Store canonical_name as .name so the UI checkbox writes the right key to - // disabled_addons, matching what PatchExeFS/PatchRomFS check. - Patch update_patch = {.enabled = !patch_disabled, - .name = canonical_name, - .version = version_str, - .type = PatchType::Update, - .program_id = title_id, - .title_id = title_id}; - out.push_back(update_patch); } + + // The name stored in disabled_addons is the canonical one (FormatTitleVersion), + // ensuring stability across reboots regardless of NACP parse success. + // Also check the NACP-derived name for backwards compatibility. + bool patch_disabled = std::find(disabled.cbegin(), disabled.cend(), canonical_name) != + disabled.cend(); + if (!patch_disabled && !display_version_str.empty() && + display_version_str != canonical_version_str) { + const auto nacp_name = fmt::format("External Files/Update v{}", display_version_str); + patch_disabled = + std::find(disabled.cbegin(), disabled.cend(), nacp_name) != disabled.cend(); + } + + // Use the human-readable string for display if available. + const auto display_name = display_version_str.empty() + ? canonical_name + : fmt::format("External Files/Update v{}", + display_version_str); + const auto version_str = + display_version_str.empty() ? canonical_version_str : display_version_str; + + // Deduplicate against installed system update if versions match + bool exists = false; + for (const auto& existing : out) { + if (existing.type == PatchType::Update && + existing.version == canonical_version_str) { + exists = true; + break; + } + } + if (exists) + continue; + + // Store canonical_name as .name so the UI checkbox writes the right key to + // disabled_addons, matching what PatchExeFS/PatchRomFS check. + Patch update_patch = {.enabled = !patch_disabled, + .name = canonical_name, + .version = version_str, + .type = PatchType::Update, + .program_id = title_id, + .title_id = title_id}; + out.push_back(update_patch); } } @@ -1088,53 +1093,48 @@ PatchManager::ActiveUpdate PatchManager::GetActiveUpdate() const { bool is_external = false; // 1. External Updates - const auto* content_provider_union = - static_cast(&content_provider); + const auto* content_provider_union = GetUnionProvider(content_provider); if (content_provider_union) { - const auto* external_provider = content_provider_union->GetExternalProvider(); - if (external_provider) { - const auto update_tid = GetUpdateTitleID(title_id); - const auto updates = external_provider->ListUpdateVersions(update_tid); - for (const auto& update : updates) { - const auto canonical_version_str = FormatTitleVersion(update.version); - const auto canonical_name = - fmt::format("External Files/Update v{}", canonical_version_str); + const auto update_tid = GetUpdateTitleID(title_id); + const auto updates = content_provider_union->ListExternalUpdateVersions(update_tid); + for (const auto& update : updates) { + const auto canonical_version_str = FormatTitleVersion(update.version); + const auto canonical_name = + fmt::format("External Files/Update v{}", canonical_version_str); - std::string nacp_version_str; - const auto control_file = external_provider->GetEntryForVersion( - update_tid, ContentRecordType::Control, update.version); - if (control_file) { - NCA control_nca(control_file); - if (control_nca.GetStatus() == Loader::ResultStatus::Success) { - if (auto control_romfs = control_nca.GetRomFS()) { - if (auto extracted = ExtractRomFS(control_romfs)) { - auto nacp_file = extracted->GetFile("control.nacp"); - if (!nacp_file) { - nacp_file = extracted->GetFile("Control.nacp"); - } - if (nacp_file) { - NACP nacp(nacp_file); - nacp_version_str = CleanNacpVersion(nacp.GetVersionString()); - } + std::string nacp_version_str; + const auto control_file = content_provider_union->GetExternalEntryForVersion( + update_tid, ContentRecordType::Control, update.version); + if (control_file) { + NCA control_nca(control_file); + if (control_nca.GetStatus() == Loader::ResultStatus::Success) { + if (auto control_romfs = control_nca.GetRomFS()) { + if (auto extracted = ExtractRomFS(control_romfs)) { + auto nacp_file = extracted->GetFile("control.nacp"); + if (!nacp_file) { + nacp_file = extracted->GetFile("Control.nacp"); + } + if (nacp_file) { + NACP nacp(nacp_file); + nacp_version_str = CleanNacpVersion(nacp.GetVersionString()); } } } } + } - bool patch_disabled = is_disabled(canonical_name); - if (!patch_disabled && !nacp_version_str.empty() && - nacp_version_str != canonical_version_str) { - const auto nacp_name = - fmt::format("External Files/Update v{}", nacp_version_str); - patch_disabled = is_disabled(nacp_name); - } + bool patch_disabled = is_disabled(canonical_name); + if (!patch_disabled && !nacp_version_str.empty() && + nacp_version_str != canonical_version_str) { + const auto nacp_name = fmt::format("External Files/Update v{}", nacp_version_str); + patch_disabled = is_disabled(nacp_name); + } - if (!patch_disabled) { - if (!found_best || update.version > best_version) { - best_version = update.version; - found_best = true; - is_external = true; - } + if (!patch_disabled) { + if (!found_best || update.version > best_version) { + best_version = update.version; + found_best = true; + is_external = true; } } } @@ -1232,31 +1232,38 @@ PatchManager::Metadata PatchManager::GetControlMetadata() const { } const auto active_update = GetActiveUpdate(); + LOG_INFO(Loader, + "GetControlMetadata: title_id={:016X}, active_update_found={}, version={}, " + "is_external={}", + title_id, active_update.found, active_update.version, active_update.is_external); if (active_update.found) { const auto update_tid = GetUpdateTitleID(title_id); if (active_update.is_external) { - const auto* content_provider_union = - static_cast(&content_provider); + const auto* content_provider_union = GetUnionProvider(content_provider); if (content_provider_union) { - if (const auto* external_provider = content_provider_union->GetExternalProvider()) { - auto control_file = external_provider->GetEntryForVersion( - update_tid, ContentRecordType::Control, active_update.version); - if (control_file) { - control_nca = std::make_unique(control_file); - } + auto control_file = content_provider_union->GetExternalEntryForVersion( + update_tid, ContentRecordType::Control, active_update.version); + if (control_file) { + control_nca = std::make_unique(control_file); } } } else { control_nca = content_provider.GetEntry(update_tid, ContentRecordType::Control); } + LOG_INFO(Loader, "GetControlMetadata: update control_nca={}", control_nca != nullptr); } if (control_nca == nullptr) { control_nca = content_provider.GetEntry(title_id, ContentRecordType::Control); + LOG_INFO(Loader, "GetControlMetadata: base control_nca={}", control_nca != nullptr); } - if (control_nca == nullptr) + if (control_nca == nullptr) { + LOG_WARNING(Loader, "GetControlMetadata: no control NCA for title_id={:016X}", title_id); return {}; + } + LOG_INFO(Loader, "GetControlMetadata: selected control status={}, type={:02X}", + static_cast(control_nca->GetStatus()), static_cast(control_nca->GetType())); return ParseControlNCA(*control_nca); } diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index 0de6fe57d3..31fa2c97a1 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -3,8 +3,10 @@ #include #include +#include #include #include +#include #include #include #include @@ -71,7 +73,7 @@ static bool FollowsNcaIdFormat(std::string_view name) { static std::string GetRelativePathFromNcaID(const std::array& nca_id, bool second_hex_upper, bool within_two_digit, bool cnmt_suffix) { if (!within_two_digit) { - const auto format_str = fmt::runtime(cnmt_suffix ? "{}.cnmt.nca" : "/{}.nca"); + const auto format_str = fmt::runtime(cnmt_suffix ? "/{}.cnmt.nca" : "/{}.nca"); return fmt::format(format_str, Common::HexToString(nca_id, second_hex_upper)); } @@ -424,32 +426,87 @@ std::vector RegisteredCache::AccumulateFiles() const { return ids; } -void RegisteredCache::ProcessFiles(const std::vector& ids) { +void RegisteredCache::ProcessFiles(const std::vector& ids, std::map& out_meta, + std::map& out_meta_id) const { for (const auto& id : ids) { const auto file = GetFileAtID(id); - if (file == nullptr) + if (file == nullptr) { + LOG_WARNING(Service_FS, "RegisteredCache ProcessFiles: missing file for nca_id={}", + Common::HexToString(id, false)); continue; + } const auto nca = std::make_shared(parser(file, id)); - if (nca->GetStatus() != Loader::ResultStatus::Success || - nca->GetType() != NCAContentType::Meta || nca->GetSubdirectories().empty()) { + if (nca->GetStatus() != Loader::ResultStatus::Success) { + LOG_WARNING(Service_FS, + "RegisteredCache ProcessFiles: failed nca_id={}, status={}", + Common::HexToString(id, false), static_cast(nca->GetStatus())); + continue; + } + + const auto nca_type = nca->GetType(); + if (nca_type != NCAContentType::Meta) { + LOG_INFO(Service_FS, + "RegisteredCache ProcessFiles: skipped non-meta nca_id={}, title_id={:016X}, " + "type={:02X}", + Common::HexToString(id, false), nca->GetTitleId(), static_cast(nca_type)); + continue; + } + + if (nca->GetSubdirectories().empty()) { + LOG_WARNING(Service_FS, + "RegisteredCache ProcessFiles: meta has no sections nca_id={}, " + "title_id={:016X}", + Common::HexToString(id, false), nca->GetTitleId()); continue; } const auto section0 = nca->GetSubdirectories()[0]; + bool found_cnmt = false; for (const auto& section0_file : section0->GetFiles()) { if (section0_file->GetExtension() != "cnmt") continue; - meta.insert_or_assign(nca->GetTitleId(), CNMT(section0_file)); - meta_id.insert_or_assign(nca->GetTitleId(), id); + CNMT cnmt(section0_file); + const auto title_id = cnmt.GetTitleID(); + LOG_INFO(Service_FS, + "RegisteredCache ProcessFiles: parsed meta nca_id={}, nca_title_id={:016X}, " + "cnmt_title_id={:016X}, title_type={:02X}, records={}", + Common::HexToString(id, false), nca->GetTitleId(), title_id, + static_cast(cnmt.GetType()), cnmt.GetContentRecords().size()); + + if (cnmt.GetType() == TitleType::AOC || cnmt.GetType() == TitleType::Update) { + const auto& content_records = cnmt.GetContentRecords(); + for (std::size_t record_index = 0; record_index < content_records.size(); + ++record_index) { + const auto& record = content_records[record_index]; + LOG_INFO(Service_FS, + "RegisteredCache ProcessFiles: meta record title_id={:016X}, " + "record_index={}, record_type={:02X}, record_nca_id={}, " + "record_file_found={}", + title_id, record_index, static_cast(record.type), + Common::HexToString(record.nca_id, false), + GetFileAtID(record.nca_id) != nullptr); + } + } + + out_meta.insert_or_assign(title_id, std::move(cnmt)); + out_meta_id.insert_or_assign(title_id, id); + found_cnmt = true; break; } + + if (!found_cnmt) { + LOG_WARNING(Service_FS, + "RegisteredCache ProcessFiles: meta section lacks cnmt nca_id={}, " + "title_id={:016X}", + Common::HexToString(id, false), nca->GetTitleId()); + } } } -void RegisteredCache::AccumulateCitronMeta() { +void RegisteredCache::AccumulateCitronMeta(std::map& out_citron_meta) const { const auto meta_dir = dir->GetSubdirectory("citron_meta"); if (meta_dir == nullptr) { return; @@ -461,7 +518,7 @@ void RegisteredCache::AccumulateCitronMeta() { } CNMT cnmt(file); - citron_meta.insert_or_assign(cnmt.GetTitleID(), std::move(cnmt)); + out_citron_meta.insert_or_assign(cnmt.GetTitleID(), std::move(cnmt)); } } @@ -471,8 +528,20 @@ void RegisteredCache::Refresh() { } const auto ids = AccumulateFiles(); - ProcessFiles(ids); - AccumulateCitronMeta(); + std::map new_meta; + std::map new_meta_id; + std::map new_citron_meta; + + ProcessFiles(ids, new_meta, new_meta_id); + AccumulateCitronMeta(new_citron_meta); + + meta.swap(new_meta); + meta_id.swap(new_meta_id); + citron_meta.swap(new_citron_meta); + + LOG_INFO(Service_FS, + "RegisteredCache refreshed: path={}, nca_ids={}, meta_entries={}, citron_meta={}", + dir->GetFullPath(), ids.size(), meta.size(), citron_meta.size()); } RegisteredCache::RegisteredCache(VirtualDir dir_, ContentProviderParsingFunction parsing_function) @@ -846,6 +915,7 @@ bool RegisteredCache::RawInstallCitronMeta(const CNMT& cnmt) { ContentProviderUnion::~ContentProviderUnion() = default; const ExternalContentProvider* ContentProviderUnion::GetExternalProvider() const { + std::shared_lock lock{providers_mutex}; auto it = providers.find(ContentProviderUnionSlot::External); if (it != providers.end()) { return static_cast(it->second); @@ -854,6 +924,7 @@ const ExternalContentProvider* ContentProviderUnion::GetExternalProvider() const } const ContentProvider* ContentProviderUnion::GetSlotProvider(ContentProviderUnionSlot slot) const { + std::shared_lock lock{providers_mutex}; auto it = providers.find(slot); if (it != providers.end()) { return it->second; @@ -862,14 +933,25 @@ const ContentProvider* ContentProviderUnion::GetSlotProvider(ContentProviderUnio } void ContentProviderUnion::SetSlot(ContentProviderUnionSlot slot, ContentProvider* provider) { + std::unique_lock lock{providers_mutex}; providers[slot] = provider; } +void ContentProviderUnion::SetSlots( + std::initializer_list> slots) { + std::unique_lock lock{providers_mutex}; + for (const auto& [slot, provider] : slots) { + providers[slot] = provider; + } +} + void ContentProviderUnion::ClearSlot(ContentProviderUnionSlot slot) { + std::unique_lock lock{providers_mutex}; providers[slot] = nullptr; } void ContentProviderUnion::Refresh() { + std::unique_lock lock{providers_mutex}; for (auto& provider : providers) { if (provider.second == nullptr) continue; @@ -879,6 +961,7 @@ void ContentProviderUnion::Refresh() { } bool ContentProviderUnion::HasEntry(u64 title_id, ContentRecordType type) const { + std::shared_lock lock{providers_mutex}; for (const auto& provider : providers) { if (provider.second == nullptr) continue; @@ -891,6 +974,7 @@ bool ContentProviderUnion::HasEntry(u64 title_id, ContentRecordType type) const } std::optional ContentProviderUnion::GetEntryVersion(u64 title_id) const { + std::shared_lock lock{providers_mutex}; for (const auto& provider : providers) { if (provider.second == nullptr) continue; @@ -904,6 +988,7 @@ std::optional ContentProviderUnion::GetEntryVersion(u64 title_id) const { } VirtualFile ContentProviderUnion::GetEntryUnparsed(u64 title_id, ContentRecordType type) const { + std::shared_lock lock{providers_mutex}; for (const auto& provider : providers) { if (provider.second == nullptr) continue; @@ -917,6 +1002,7 @@ VirtualFile ContentProviderUnion::GetEntryUnparsed(u64 title_id, ContentRecordTy } VirtualFile ContentProviderUnion::GetEntryRaw(u64 title_id, ContentRecordType type) const { + std::shared_lock lock{providers_mutex}; for (const auto& provider : providers) { if (provider.second == nullptr) continue; @@ -930,6 +1016,7 @@ VirtualFile ContentProviderUnion::GetEntryRaw(u64 title_id, ContentRecordType ty } std::unique_ptr ContentProviderUnion::GetEntry(u64 title_id, ContentRecordType type) const { + std::shared_lock lock{providers_mutex}; for (const auto& provider : providers) { if (provider.second == nullptr) continue; @@ -947,6 +1034,7 @@ std::vector ContentProviderUnion::ListEntriesFilter( std::optional title_id) const { std::vector out; + std::shared_lock lock{providers_mutex}; for (const auto& provider : providers) { if (provider.second == nullptr) continue; @@ -967,6 +1055,7 @@ ContentProviderUnion::ListEntriesFilterOrigin(std::optional title_id) const { std::vector> out; + std::shared_lock lock{providers_mutex}; for (const auto& provider : providers) { if (provider.second == nullptr) continue; @@ -988,6 +1077,7 @@ ContentProviderUnion::ListEntriesFilterOrigin(std::optional ContentProviderUnion::GetSlotForEntry( u64 title_id, ContentRecordType type) const { + std::shared_lock lock{providers_mutex}; const auto iter = std::find_if(providers.begin(), providers.end(), [title_id, type](const auto& provider) { return provider.second != nullptr && provider.second->HasEntry(title_id, type); @@ -1000,6 +1090,29 @@ std::optional ContentProviderUnion::GetSlotForEntry( return iter->first; } +VirtualFile ContentProviderUnion::GetExternalEntryForVersion(u64 title_id, ContentRecordType type, + u32 version) const { + std::shared_lock lock{providers_mutex}; + auto it = providers.find(ContentProviderUnionSlot::External); + if (it == providers.end() || it->second == nullptr) { + return nullptr; + } + + return static_cast(it->second) + ->GetEntryForVersion(title_id, type, version); +} + +std::vector ContentProviderUnion::ListExternalUpdateVersions( + u64 title_id) const { + std::shared_lock lock{providers_mutex}; + auto it = providers.find(ContentProviderUnionSlot::External); + if (it == providers.end() || it->second == nullptr) { + return {}; + } + + return static_cast(it->second)->ListUpdateVersions(title_id); +} + ManualContentProvider::~ManualContentProvider() = default; void ManualContentProvider::AddEntry(TitleType title_type, ContentRecordType content_type, diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h index e1071314d4..952ed1eb25 100644 --- a/src/core/file_sys/registered_cache.h +++ b/src/core/file_sys/registered_cache.h @@ -6,11 +6,14 @@ #include #include +#include #include #include #include +#include #include #include +#include #include #include #include "common/common_types.h" @@ -77,6 +80,8 @@ class ContentProvider { public: virtual ~ContentProvider(); + virtual bool IsUnionProvider() const { return false; } + virtual void Refresh() = 0; virtual bool HasEntry(u64 title_id, ContentRecordType type) const = 0; @@ -195,8 +200,9 @@ private: std::function proc, std::function filter) const; std::vector AccumulateFiles() const; - void ProcessFiles(const std::vector& ids); - void AccumulateCitronMeta(); + void ProcessFiles(const std::vector& ids, std::map& out_meta, + std::map& out_meta_id) const; + void AccumulateCitronMeta(std::map& out_citron_meta) const; std::optional GetNcaIDFromMetadata(u64 title_id, ContentRecordType type) const; VirtualFile GetFileAtID(NcaID id) const; VirtualFile OpenFileOrDirectoryConcat(const VirtualDir& open_dir, std::string_view path) const; @@ -230,7 +236,11 @@ class ContentProviderUnion : public ContentProvider { public: ~ContentProviderUnion() override; + bool IsUnionProvider() const override { return true; } + void SetSlot(ContentProviderUnionSlot slot, ContentProvider* provider); + void SetSlots( + std::initializer_list> slots); void ClearSlot(ContentProviderUnionSlot slot); void Refresh() override; @@ -251,10 +261,15 @@ public: std::optional GetSlotForEntry(u64 title_id, ContentRecordType type) const; + VirtualFile GetExternalEntryForVersion(u64 title_id, ContentRecordType type, + u32 version) const; + std::vector ListExternalUpdateVersions(u64 title_id) const; + const class ExternalContentProvider* GetExternalProvider() const; const ContentProvider* GetSlotProvider(ContentProviderUnionSlot slot) const; private: + mutable std::shared_mutex providers_mutex; std::map providers; }; diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp index d408612cc5..c485ea6886 100644 --- a/src/core/file_sys/submission_package.cpp +++ b/src/core/file_sys/submission_package.cpp @@ -195,6 +195,10 @@ void NSP::SetTicketKeys(const std::vector& files) { LOG_WARNING(Common_Filesystem, "Could not load NSP ticket {}", ticket_file->GetName()); continue; } + if (!keys.PersistTicket(ticket)) { + LOG_WARNING(Common_Filesystem, "Could not persist NSP ticket {}", + ticket_file->GetName()); + } } } diff --git a/src/core/hle/service/aoc/addon_content_manager.cpp b/src/core/hle/service/aoc/addon_content_manager.cpp index affbd72605..9e56a45cd9 100644 --- a/src/core/hle/service/aoc/addon_content_manager.cpp +++ b/src/core/hle/service/aoc/addon_content_manager.cpp @@ -58,6 +58,22 @@ static std::vector AccumulateAOCTitleIDs(Core::System& system) { return add_on_content; } +static std::vector GetAOCTitleIDsForBase(const std::vector& add_on_content, u64 base) { + std::vector out; + for (const auto title_id : add_on_content) { + if (CheckAOCTitleIDMatchesBase(title_id, base)) { + out.push_back(title_id); + } + } + + std::sort(out.begin(), out.end()); + return out; +} + +static u32 GetGuestAOCIndex(std::size_t ordinal) { + return static_cast(ordinal + 1); +} + IAddOnContentManager::IAddOnContentManager(Core::System& system_) : ServiceFramework{system_, "aoc:u"}, add_on_content{AccumulateAOCTitleIDs(system)}, service_context{system_, "aoc:u"} { @@ -98,8 +114,8 @@ IAddOnContentManager::~IAddOnContentManager() { } Result IAddOnContentManager::CountAddOnContent(Out out_count, ClientProcessId process_id) { - LOG_DEBUG(Service_AOC, "called. process_id={}", process_id.pid); - + // LOG_DEBUG(Service_AOC, "called. process_id={}", process_id.pid); + LOG_WARNING(Service_AOC, "CountAddOnContent called. process_id={}", process_id.pid); const auto current = system.GetApplicationProcessProgramID(); const auto& disabled = Settings::values.disabled_addons[current]; @@ -108,10 +124,10 @@ Result IAddOnContentManager::CountAddOnContent(Out out_count, ClientProcess R_SUCCEED(); } - *out_count = static_cast( - std::count_if(add_on_content.begin(), add_on_content.end(), - [current](u64 tid) { return CheckAOCTitleIDMatchesBase(tid, current); })); - + *out_count = static_cast(GetAOCTitleIDsForBase(add_on_content, current).size()); + LOG_WARNING(Service_AOC, + "CountAddOnContent: program_id={:016X}, count={}", + current, *out_count); R_SUCCEED(); } @@ -124,20 +140,16 @@ Result IAddOnContentManager::ListAddOnContent(Out out_count, const auto current = FileSys::GetBaseTitleID(system.GetApplicationProcessProgramID()); std::vector out; + const auto matching_aocs = GetAOCTitleIDsForBase(add_on_content, current); const auto& disabled = Settings::values.disabled_addons[current]; if (std::find(disabled.begin(), disabled.end(), "DLC") == disabled.end()) { LOG_WARNING(Service_AOC, "Filtering AOCs for base title ID: {:016X}", current); - for (u64 content_id : add_on_content) { - const auto aoc_base = FileSys::GetBaseTitleID(content_id); - if (aoc_base != current) { - // LOG_WARNING(Service_AOC, "Skipping AOC {:016X} (Base: {:016X})", content_id, - // aoc_base); - continue; - } - - LOG_WARNING(Service_AOC, "Match! AOC {:016X} belongs to current app. Adding to list.", - content_id); - out.push_back(static_cast(FileSys::GetAOCID(content_id))); + for (std::size_t i = 0; i < matching_aocs.size(); ++i) { + const auto guest_index = GetGuestAOCIndex(i); + LOG_WARNING(Service_AOC, + "Match! AOC {:016X} belongs to current app. Adding guest_index={}.", + matching_aocs[i], guest_index); + out.push_back(guest_index); } } else { LOG_WARNING(Service_AOC, "DLCs are disabled for this title {:016X}", current); @@ -146,7 +158,23 @@ Result IAddOnContentManager::ListAddOnContent(Out out_count, // TODO(DarkLordZach): Find the correct error code. R_UNLESS(out.size() >= offset, ResultUnknown); - *out_count = static_cast(std::min(out.size() - offset, count)); + const auto buffer_count = out_addons.size() / sizeof(u32); + *out_count = static_cast(std::min({out.size() - offset, static_cast(count), + buffer_count})); + LOG_WARNING(Service_AOC, + "ListAddOnContent result: base_id={:016X}, matched_total={}, offset={}, " + "requested_count={}, returned_count={}, buffer_bytes={}", + current, out.size(), offset, count, *out_count, out_addons.size()); + + for (u32 i = 0; i < *out_count; ++i) { + const auto addon_index = out[offset + i]; + const auto physical_title_id = matching_aocs[addon_index - 1]; + + LOG_WARNING(Service_AOC, + "ListAddOnContent output[{}]: addon_index={}, physical_title_id={:016X}", + i, addon_index, physical_title_id); + } + std::rotate(out.begin(), out.begin() + offset, out.end()); std::memcpy(out_addons.data(), out.data(), *out_count * sizeof(u32)); @@ -166,9 +194,7 @@ Result IAddOnContentManager::CountAddOnContentByApplicationId(Out out_count R_SUCCEED(); } - *out_count = static_cast( - std::count_if(add_on_content.begin(), add_on_content.end(), - [current](u64 tid) { return CheckAOCTitleIDMatchesBase(tid, current); })); + *out_count = static_cast(GetAOCTitleIDsForBase(add_on_content, current).size()); R_SUCCEED(); } @@ -182,18 +208,16 @@ Result IAddOnContentManager::ListAddOnContentByApplicationId( const auto current = FileSys::GetBaseTitleID(application_id); std::vector out; + const auto matching_aocs = GetAOCTitleIDsForBase(add_on_content, current); const auto& disabled = Settings::values.disabled_addons[current]; if (std::find(disabled.begin(), disabled.end(), "DLC") == disabled.end()) { LOG_WARNING(Service_AOC, "Filtering AOCs for base title ID: {:016X}", current); - for (u64 content_id : add_on_content) { - const auto aoc_base = FileSys::GetBaseTitleID(content_id); - if (aoc_base != current) { - continue; - } - - LOG_WARNING(Service_AOC, "Match! AOC {:016X} belongs to current app. Adding to list.", - content_id); - out.push_back(static_cast(FileSys::GetAOCID(content_id))); + for (std::size_t i = 0; i < matching_aocs.size(); ++i) { + const auto guest_index = GetGuestAOCIndex(i); + LOG_WARNING(Service_AOC, + "Match! AOC {:016X} belongs to current app. Adding guest_index={}.", + matching_aocs[i], guest_index); + out.push_back(guest_index); } } else { LOG_WARNING(Service_AOC, "DLCs are disabled for this title {:016X}", current); @@ -202,7 +226,24 @@ Result IAddOnContentManager::ListAddOnContentByApplicationId( // TODO(DarkLordZach): Find the correct error code. R_UNLESS(out.size() >= offset, ResultUnknown); - *out_count = static_cast(std::min(out.size() - offset, count)); + const auto buffer_count = out_addons.size() / sizeof(u32); + *out_count = static_cast(std::min({out.size() - offset, static_cast(count), + buffer_count})); + LOG_WARNING(Service_AOC, + "ListAddOnContentByApplicationId result: base_id={:016X}, matched_total={}, " + "offset={}, requested_count={}, returned_count={}, buffer_bytes={}", + current, out.size(), offset, count, *out_count, out_addons.size()); + + for (u32 i = 0; i < *out_count; ++i) { + const auto addon_index = out[offset + i]; + const auto physical_title_id = matching_aocs[addon_index - 1]; + + LOG_WARNING(Service_AOC, + "ListAddOnContentByApplicationId output[{}]: addon_index={}, " + "physical_title_id={:016X}", + i, addon_index, physical_title_id); + } + std::rotate(out.begin(), out.begin() + offset, out.end()); std::memcpy(out_addons.data(), out.data(), *out_count * sizeof(u32)); @@ -212,8 +253,8 @@ Result IAddOnContentManager::ListAddOnContentByApplicationId( Result IAddOnContentManager::GetAddOnContentBaseId(Out out_title_id, ClientProcessId process_id) { - LOG_DEBUG(Service_AOC, "called. process_id={}", process_id.pid); - + // LOG_DEBUG(Service_AOC, "called. process_id={}", process_id.pid); + LOG_WARNING(Service_AOC, "GetAddOnContentBaseId called. process_id={}", process_id.pid); const auto title_id = system.GetApplicationProcessProgramID(); const FileSys::PatchManager pm{title_id, system.GetFileSystemController(), system.GetContentProvider()}; @@ -221,16 +262,33 @@ Result IAddOnContentManager::GetAddOnContentBaseId(Out out_title_id, const auto res = pm.GetControlMetadata(); if (res.first == nullptr) { *out_title_id = FileSys::GetAOCBaseTitleID(title_id); + LOG_WARNING(Service_AOC, + "GetAddOnContentBaseId fallback: program_id={:016X}, aoc_base={:016X}", + title_id, *out_title_id); R_SUCCEED(); } *out_title_id = res.first->GetDLCBaseTitleId(); + LOG_WARNING(Service_AOC, + "GetAddOnContentBaseId metadata: program_id={:016X}, aoc_base={:016X}", + title_id, *out_title_id); R_SUCCEED(); } Result IAddOnContentManager::PrepareAddOnContent(s32 addon_index, ClientProcessId process_id) { - LOG_WARNING(Service_AOC, "(STUBBED) called with addon_index={}, process_id={}", addon_index, + const auto program_id = system.GetApplicationProcessProgramID(); + const auto aoc_base_id = FileSys::GetAOCBaseTitleID(program_id); + const auto matching_aocs = GetAOCTitleIDsForBase(add_on_content, program_id); + u64 physical_title_id = 0; + if (addon_index > 0 && static_cast(addon_index) <= matching_aocs.size()) { + physical_title_id = matching_aocs[static_cast(addon_index) - 1]; + } + + LOG_WARNING(Service_AOC, + "PrepareAddOnContent: program_id={:016X}, aoc_base={:016X}, " + "addon_index={}, physical_title_id={:016X}, process_id={}", + program_id, aoc_base_id, addon_index, physical_title_id, process_id.pid); R_SUCCEED(); diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index 4a1803ed3b..b0e04af724 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -803,45 +803,84 @@ FileSys::VirtualDir FileSystemController::GetBCATDirectory(u64 title_id) const { return bis_factory->GetBCATDirectory(title_id); } +void FileSystemController::SetInitStage(InitStage stage) { + init_stage = stage; +} + +InitStage FileSystemController::GetInitStage() const { + return init_stage; +} + +void FileSystemController::InitializeContentSystem(FileSys::VfsFilesystem& vfs, bool overwrite) { + if (init_stage < InitStage::FS_READY) { + LOG_ERROR(Service_FS, "InitializeContentSystem called too early: init_stage={}", + static_cast(init_stage)); + return; + } + + CreateFactories(vfs, overwrite); +} + void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) { - if (overwrite) { - bis_factory = nullptr; - sdmc_factory = nullptr; - external_provider = nullptr; + if (init_stage < InitStage::FS_READY) { + LOG_ERROR(Service_FS, "CreateFactories called too early: init_stage={}", + static_cast(init_stage)); + return; } using CitronPath = Common::FS::CitronPath; const auto sdmc_dir_path = Common::FS::GetCitronPath(CitronPath::SDMCDir); const auto sdmc_load_dir_path = sdmc_dir_path / "atmosphere/contents"; const auto rw_mode = FileSys::OpenMode::ReadWrite; + const auto nand_dir_path = Common::FS::GetCitronPathString(CitronPath::NANDDir); + const auto load_dir_path = Common::FS::GetCitronPathString(CitronPath::LoadDir); + const auto dump_dir_path = Common::FS::GetCitronPathString(CitronPath::DumpDir); - auto nand_directory = - vfs.OpenDirectory(Common::FS::GetCitronPathString(CitronPath::NANDDir), rw_mode); + LOG_INFO(Service_FS, "CreateFactories: overwrite={}, NANDDir={}, SDMCDir={}, LoadDir={}", + overwrite, nand_dir_path, Common::FS::PathToUTF8String(sdmc_dir_path), load_dir_path); + + auto nand_directory = vfs.OpenDirectory(nand_dir_path, rw_mode); auto sd_directory = vfs.OpenDirectory(Common::FS::PathToUTF8String(sdmc_dir_path), rw_mode); - auto load_directory = vfs.OpenDirectory(Common::FS::GetCitronPathString(CitronPath::LoadDir), - FileSys::OpenMode::Read); + auto load_directory = vfs.OpenDirectory(load_dir_path, FileSys::OpenMode::Read); auto sd_load_directory = vfs.OpenDirectory(Common::FS::PathToUTF8String(sdmc_load_dir_path), FileSys::OpenMode::Read); - auto dump_directory = - vfs.OpenDirectory(Common::FS::GetCitronPathString(CitronPath::DumpDir), rw_mode); + auto dump_directory = vfs.OpenDirectory(dump_dir_path, rw_mode); - if (bis_factory == nullptr) { - bis_factory = std::make_unique( + LOG_INFO(Service_FS, + "CreateFactories: opened NAND={}, SDMC={}, Load={}, Dump={}", + nand_directory ? nand_directory->GetFullPath() : "", + sd_directory ? sd_directory->GetFullPath() : "", + load_directory ? load_directory->GetFullPath() : "", + dump_directory ? dump_directory->GetFullPath() : ""); + + std::unique_ptr new_bis_factory; + auto* active_bis_factory = bis_factory.get(); + if (overwrite || active_bis_factory == nullptr) { + new_bis_factory = std::make_unique( nand_directory, std::move(load_directory), std::move(dump_directory)); - system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::SysNAND, - bis_factory->GetSystemNANDContents()); - system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::UserNAND, - bis_factory->GetUserNANDContents()); + active_bis_factory = new_bis_factory.get(); } - if (sdmc_factory == nullptr) { - sdmc_factory = std::make_unique(std::move(sd_directory), - std::move(sd_load_directory)); - system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::SDMC, - sdmc_factory->GetSDMCContents()); + if (active_bis_factory != nullptr) { + const auto* sysnand = active_bis_factory->GetSystemNANDContents(); + const auto* usernand = active_bis_factory->GetUserNANDContents(); + LOG_INFO(Service_FS, + "CreateFactories: prepared NAND providers, sys_entries={}, user_entries={}", + sysnand ? sysnand->ListEntriesFilter().size() : 0, + usernand ? usernand->ListEntriesFilter().size() : 0); } - if (external_provider == nullptr) { + std::unique_ptr new_sdmc_factory; + auto* active_sdmc_factory = sdmc_factory.get(); + if (overwrite || active_sdmc_factory == nullptr) { + new_sdmc_factory = std::make_unique(std::move(sd_directory), + std::move(sd_load_directory)); + active_sdmc_factory = new_sdmc_factory.get(); + } + + std::unique_ptr new_external_provider; + auto* active_external_provider = external_provider.get(); + if (overwrite || active_external_provider == nullptr) { std::vector load_dirs; for (const auto& path : Settings::values.external_content_dirs) { auto dir = vfs.OpenDirectory(path, FileSys::OpenMode::Read); @@ -849,16 +888,36 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove load_dirs.push_back(std::move(dir)); } } - external_provider = + new_external_provider = std::make_unique(std::move(load_dirs)); - system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::External, - external_provider.get()); + active_external_provider = new_external_provider.get(); + } + + system.GetContentProviderUnion().SetSlots( + {{FileSys::ContentProviderUnionSlot::SysNAND, + active_bis_factory != nullptr ? active_bis_factory->GetSystemNANDContents() : nullptr}, + {FileSys::ContentProviderUnionSlot::UserNAND, + active_bis_factory != nullptr ? active_bis_factory->GetUserNANDContents() : nullptr}, + {FileSys::ContentProviderUnionSlot::SDMC, + active_sdmc_factory != nullptr ? active_sdmc_factory->GetSDMCContents() : nullptr}, + {FileSys::ContentProviderUnionSlot::External, active_external_provider}}); + + if (new_bis_factory != nullptr) { + bis_factory = std::move(new_bis_factory); + } + if (new_sdmc_factory != nullptr) { + sdmc_factory = std::move(new_sdmc_factory); + } + if (new_external_provider != nullptr) { + external_provider = std::move(new_external_provider); } // factory that handles sync tasks before a game is even selected if (global_save_data_factory == nullptr || overwrite) { global_save_data_factory = CreateSaveDataFactory(ProgramId{}); } + + init_stage = InitStage::CONTENT_READY; } void FileSystemController::RefreshExternalContentProvider() { @@ -871,9 +930,11 @@ void FileSystemController::RefreshExternalContentProvider() { } } - external_provider = std::make_unique(std::move(load_dirs)); + auto new_external_provider = + std::make_unique(std::move(load_dirs)); system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::External, - external_provider.get()); + new_external_provider.get()); + external_provider = std::move(new_external_provider); } void FileSystemController::Reset() { diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index 4daffa7fc5..908c5fb394 100644 --- a/src/core/hle/service/filesystem/filesystem.h +++ b/src/core/hle/service/filesystem/filesystem.h @@ -68,6 +68,13 @@ enum class ImageDirectoryId : u32 { SdCard, }; +enum class InitStage : u8 { + NONE, + SETTINGS_READY, + FS_READY, + CONTENT_READY, +}; + using ProcessId = u64; using ProgramId = u64; @@ -130,9 +137,11 @@ public: void RefreshExternalContentProvider(); - // Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function - // above is called. - void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite = true); + // Single public entry for creating the SaveData, SDMC, BIS, and content providers. + // Should be called once and before any function above is called. + void InitializeContentSystem(FileSys::VfsFilesystem& vfs, bool overwrite = true); + void SetInitStage(InitStage stage); + InitStage GetInitStage() const; // getter for main.cpp to trigger the sync between custom game paths for separate emulators FileSys::SaveDataFactory& GetSaveDataFactory() { @@ -143,6 +152,7 @@ public: private: std::shared_ptr CreateSaveDataFactory(ProgramId program_id); + void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite); struct Registration { ProgramId program_id; @@ -165,6 +175,12 @@ private: // Global factory for startup tasks and mirroring std::shared_ptr global_save_data_factory; +#ifdef ANDROID + InitStage init_stage{InitStage::NONE}; +#else + InitStage init_stage{InitStage::FS_READY}; +#endif + Core::System& system; }; diff --git a/src/core/hle/service/filesystem/fsp/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp/fsp_srv.cpp index 0b3ad96f58..ee317f8106 100644 --- a/src/core/hle/service/filesystem/fsp/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp/fsp_srv.cpp @@ -2,6 +2,7 @@ // SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include #include #include @@ -16,12 +17,14 @@ #include "common/settings.h" #include "common/string_util.h" #include "core/core.h" +#include "core/file_sys/common_funcs.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/errors.h" #include "core/file_sys/fs_directory.h" #include "core/file_sys/fs_filesystem.h" #include "core/file_sys/nca_metadata.h" #include "core/file_sys/patch_manager.h" +#include "core/file_sys/registered_cache.h" #include "core/file_sys/romfs.h" #include "core/file_sys/romfs_factory.h" #include "core/file_sys/savedata_factory.h" @@ -49,6 +52,21 @@ namespace Service::FileSystem { +static std::vector GetAOCPhysicalTitleIDsForBase(const FileSys::ContentProvider& content_provider, + u64 base) { + std::vector out; + const auto entries = + content_provider.ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); + for (const auto& entry : entries) { + if (FileSys::GetBaseTitleID(entry.title_id) == base) { + out.push_back(entry.title_id); + } + } + + std::sort(out.begin(), out.end()); + return out; +} + FSP_SRV::FSP_SRV(Core::System& system_) : ServiceFramework{system_, "fsp-srv"}, fsc{system.GetFileSystemController()}, content_provider{system.GetContentProvider()}, reporter{system.GetReporter()} { @@ -490,10 +508,35 @@ Result FSP_SRV::OpenDataStorageByCurrentProcess(OutInterface out_inter Result FSP_SRV::OpenDataStorageByDataId(OutInterface out_interface, FileSys::StorageId storage_id, u32 unknown, u64 title_id) { - LOG_DEBUG(Service_FS, "called with storage_id={:02X}, unknown={:08X}, title_id={:016X}", - storage_id, unknown, title_id); + LOG_WARNING(Service_FS, + "OpenDataStorageByDataId called with storage_id={:02X}, unknown={:08X}, " + "title_id={:016X}", + storage_id, unknown, title_id); - auto data = romfs_controller->OpenRomFS(title_id, storage_id, FileSys::ContentRecordType::Data); + u64 resolved_title_id = title_id; + const auto requested_base = FileSys::GetBaseTitleID(title_id); + const auto requested_aoc_base = FileSys::GetAOCBaseTitleID(title_id); + const auto requested_aoc_id = FileSys::GetAOCID(title_id); + const auto requested_is_aoc = + requested_aoc_id > 0 && title_id >= requested_aoc_base && + title_id <= requested_aoc_base + FileSys::AOC_TITLE_ID_MASK; + std::vector matching_aocs; + + if (requested_is_aoc) { + matching_aocs = GetAOCPhysicalTitleIDsForBase(content_provider, requested_base); + if (requested_aoc_id <= matching_aocs.size()) { + resolved_title_id = matching_aocs[static_cast(requested_aoc_id) - 1]; + if (resolved_title_id != title_id) { + LOG_WARNING(Service_FS, + "OpenDataStorageByDataId resolved AOC guest index: " + "requested_title_id={:016X}, guest_index={}, physical_title_id={:016X}", + title_id, requested_aoc_id, resolved_title_id); + } + } + } + + auto data = + romfs_controller->OpenRomFS(resolved_title_id, storage_id, FileSys::ContentRecordType::Data); if (!data) { const auto archive = FileSys::SystemArchive::SynthesizeSystemArchive(title_id); @@ -503,16 +546,43 @@ Result FSP_SRV::OpenDataStorageByDataId(OutInterface out_interface, R_SUCCEED(); } + if (requested_is_aoc) { + std::size_t matching_aoc_count = 0; + for (const auto entry_title_id : matching_aocs) { + ++matching_aoc_count; + LOG_WARNING(Service_FS, + "OpenDataStorageByDataId miss context: installed_aoc_index={}, " + "installed_title_id={:016X}", + matching_aoc_count, entry_title_id); + } + + LOG_WARNING(Service_FS, + "OpenDataStorageByDataId AOC miss: requested_base={:016X}, " + "requested_aoc_base={:016X}, requested_aoc_index={}, " + "resolved_title_id={:016X}, matching_installed_aoc_count={}", + requested_base, requested_aoc_base, requested_aoc_id, resolved_title_id, + matching_aoc_count); + } + LOG_ERROR(Service_FS, "Could not open data storage with title_id={:016X}, storage_id={:02X}", title_id, storage_id); R_RETURN(FileSys::ResultTargetNotFound); } - const FileSys::PatchManager pm{title_id, fsc, content_provider}; + const auto opened_aoc_base = FileSys::GetAOCBaseTitleID(title_id); + if (title_id >= opened_aoc_base && title_id <= opened_aoc_base + FileSys::AOC_TITLE_ID_MASK) { + LOG_WARNING(Service_FS, + "OpenDataStorageByDataId opened AOC: base={:016X}, addon_index={}, " + "requested_title_id={:016X}, physical_title_id={:016X}", + FileSys::GetBaseTitleID(title_id), FileSys::GetAOCID(title_id), title_id, + resolved_title_id); + } + + const FileSys::PatchManager pm{resolved_title_id, fsc, content_provider}; auto base = - romfs_controller->OpenBaseNca(title_id, storage_id, FileSys::ContentRecordType::Data); + romfs_controller->OpenBaseNca(resolved_title_id, storage_id, FileSys::ContentRecordType::Data); auto storage = std::make_shared( system, pm.PatchRomFS(base.get(), std::move(data), FileSys::ContentRecordType::Data)); diff --git a/src/core/hle/service/services.cpp b/src/core/hle/service/services.cpp index b512815b53..d69f2c9501 100644 --- a/src/core/hle/service/services.cpp +++ b/src/core/hle/service/services.cpp @@ -71,7 +71,7 @@ Services::Services(std::shared_ptr& sm, Core::System& system std::stop_token token) { auto& kernel = system.Kernel(); - system.GetFileSystemController().CreateFactories(*system.GetFilesystem(), false); + system.GetFileSystemController().InitializeContentSystem(*system.GetFilesystem(), false); // clang-format off kernel.RunOnHostCoreProcess("audio", [&] { Audio::LoopProcess(system); }).detach();