From 455bc0189a43e4ad1453cf2b1b3727baa4dacf8f Mon Sep 17 00:00:00 2001 From: bdm110 Date: Fri, 26 Jun 2026 12:02:53 +0800 Subject: [PATCH 01/25] refactor: streamline NativeLibrary initialization and enhance logging in filesystem operations --- .../citron/citron_emu/ui/main/MainActivity.kt | 2 +- .../utils/DirectoryInitialization.kt | 1 + src/core/file_sys/registered_cache.cpp | 8 +++++ .../hle/service/filesystem/filesystem.cpp | 29 +++++++++++++++---- 4 files changed, 33 insertions(+), 7 deletions(-) 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..cf3326441b 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 @@ -25,6 +25,7 @@ object DirectoryInitialization { initializeInternalStorage() NativeLibrary.initializeSystem(false) NativeConfig.initializeGlobalConfig() + NativeLibrary.initializeSystem(true) migrateSettings() areDirectoriesReady = true } diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index 0de6fe57d3..ff8e40cf78 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -470,9 +470,17 @@ void RegisteredCache::Refresh() { return; } + meta.clear(); + meta_id.clear(); + citron_meta.clear(); + const auto ids = AccumulateFiles(); ProcessFiles(ids); AccumulateCitronMeta(); + + 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) diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index 4a1803ed3b..d918bce252 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -814,16 +814,26 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove 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); + + 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() : ""); if (bis_factory == nullptr) { bis_factory = std::make_unique( @@ -832,6 +842,13 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove bis_factory->GetSystemNANDContents()); system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::UserNAND, bis_factory->GetUserNANDContents()); + + const auto* sysnand = bis_factory->GetSystemNANDContents(); + const auto* usernand = bis_factory->GetUserNANDContents(); + LOG_INFO(Service_FS, + "CreateFactories: registered NAND providers, sys_entries={}, user_entries={}", + sysnand ? sysnand->ListEntriesFilter().size() : 0, + usernand ? usernand->ListEntriesFilter().size() : 0); } if (sdmc_factory == nullptr) { From 0c187c11dc741df01ca8f9a0e2c9f30089947f80 Mon Sep 17 00:00:00 2001 From: bdm110 Date: Fri, 26 Jun 2026 12:11:29 +0800 Subject: [PATCH 02/25] refactor: improve filesystem factory creation logic and ensure content providers are registered correctly --- src/android/app/src/main/jni/native.cpp | 7 ++++--- src/core/hle/service/filesystem/filesystem.cpp | 9 +++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index c83a3844ff..89d832ff5a 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -209,7 +209,11 @@ void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath) } void EmulationSession::InitializeSystem(bool reload) { + std::scoped_lock lock(m_mutex); + if (!reload) { + m_system.Initialize(); + // Initialize logging system Common::Log::Initialize(); Common::Log::SetColorConsoleBackendEnabled(true); @@ -588,9 +592,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); } diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index d918bce252..075737a6a9 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -838,6 +838,9 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove if (bis_factory == nullptr) { bis_factory = std::make_unique( nand_directory, std::move(load_directory), std::move(dump_directory)); + } + + if (bis_factory != nullptr) { system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::SysNAND, bis_factory->GetSystemNANDContents()); system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::UserNAND, @@ -854,6 +857,9 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove if (sdmc_factory == nullptr) { sdmc_factory = std::make_unique(std::move(sd_directory), std::move(sd_load_directory)); + } + + if (sdmc_factory != nullptr) { system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::SDMC, sdmc_factory->GetSDMCContents()); } @@ -868,6 +874,9 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove } external_provider = std::make_unique(std::move(load_dirs)); + } + + if (external_provider != nullptr) { system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::External, external_provider.get()); } From 6bee23fd38d970d10697057f64dcf9643196668f Mon Sep 17 00:00:00 2001 From: bdm110 Date: Fri, 26 Jun 2026 12:16:38 +0800 Subject: [PATCH 03/25] refactor: enhance system initialization checks and streamline filesystem provider creation --- .../citron_emu/utils/DirectoryInitialization.kt | 3 +-- src/android/app/src/main/jni/native.cpp | 12 +++++++++++- src/core/hle/service/filesystem/filesystem.cpp | 5 +++++ 3 files changed, 17 insertions(+), 3 deletions(-) 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 cf3326441b..dcb0ea17f3 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,8 @@ object DirectoryInitialization { fun start() { if (!areDirectoriesReady) { initializeInternalStorage() - NativeLibrary.initializeSystem(false) NativeConfig.initializeGlobalConfig() - NativeLibrary.initializeSystem(true) + NativeLibrary.initializeSystem(false) migrateSettings() areDirectoriesReady = true } diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 89d832ff5a..dd806c8552 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -211,6 +211,11 @@ 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; + } + if (!reload) { m_system.Initialize(); @@ -894,7 +899,12 @@ 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(); + auto& session = EmulationSession::GetInstance(); + if (session.IsRunning()) { + return ContentManager::AreKeysPresent(); + } + + auto& system = session.System(); system.GetFileSystemController().CreateFactories(*system.GetFilesystem()); Core::Crypto::KeyManager::Instance().ReloadKeys(); return ContentManager::AreKeysPresent(); diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index 075737a6a9..8912794256 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -805,6 +805,11 @@ FileSys::VirtualDir FileSystemController::GetBCATDirectory(u64 title_id) const { void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) { if (overwrite) { + system.ClearContentProvider(FileSys::ContentProviderUnionSlot::SysNAND); + system.ClearContentProvider(FileSys::ContentProviderUnionSlot::UserNAND); + system.ClearContentProvider(FileSys::ContentProviderUnionSlot::SDMC); + system.ClearContentProvider(FileSys::ContentProviderUnionSlot::External); + bis_factory = nullptr; sdmc_factory = nullptr; external_provider = nullptr; From 15df65a324827d52f4b1281e60bb0cb0bf6ec400 Mon Sep 17 00:00:00 2001 From: bdm110 Date: Fri, 26 Jun 2026 12:24:52 +0800 Subject: [PATCH 04/25] refactor: enhance filesystem initialization stages and improve error handling --- src/android/app/src/main/jni/native.cpp | 9 ++++++++- .../app/src/main/jni/native_config.cpp | 19 +++++++++++++++++++ .../hle/service/filesystem/filesystem.cpp | 16 ++++++++++++++++ src/core/hle/service/filesystem/filesystem.h | 15 +++++++++++++++ 4 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index dd806c8552..0b528ee216 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -216,6 +216,12 @@ void EmulationSession::InitializeSystem(bool reload) { 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(); @@ -234,7 +240,8 @@ 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.CreateFactories(*m_vfs); } void EmulationSession::SetAppletId(int applet_id) { diff --git a/src/android/app/src/main/jni/native_config.cpp b/src/android/app/src/main/jni/native_config.cpp index 7293978c51..9153e617af 100644 --- a/src/android/app/src/main/jni/native_config.cpp +++ b/src/android/app/src/main/jni/native_config.cpp @@ -12,11 +12,27 @@ #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().System().GetFileSystemController().SetInitStage(stage); +} + +void PromoteFilesystemInitStage(Service::FileSystem::InitStage stage) { + auto& controller = EmulationSession::GetInstance().System().GetFileSystemController(); + if (controller.GetInitStage() < stage) { + controller.SetInitStage(stage); + } +} + +} // namespace + template Settings::Setting* getSetting(JNIEnv* env, jstring jkey) { auto key = Common::Android::GetJString(env, jkey); @@ -36,14 +52,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/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index 8912794256..41b174867e 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -803,7 +803,21 @@ 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::CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) { + if (init_stage < InitStage::FS_READY) { + LOG_WARNING(Service_FS, "CreateFactories skipped: init_stage={}", + static_cast(init_stage)); + return; + } + if (overwrite) { system.ClearContentProvider(FileSys::ContentProviderUnionSlot::SysNAND); system.ClearContentProvider(FileSys::ContentProviderUnionSlot::UserNAND); @@ -890,6 +904,8 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove if (global_save_data_factory == nullptr || overwrite) { global_save_data_factory = CreateSaveDataFactory(ProgramId{}); } + + init_stage = InitStage::CONTENT_READY; } void FileSystemController::RefreshExternalContentProvider() { diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index 4daffa7fc5..ced11495db 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; @@ -133,6 +140,8 @@ public: // 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); + 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() { @@ -165,6 +174,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; }; From c7b45beac5a4033086551bb6a86b0dc77f02511c Mon Sep 17 00:00:00 2001 From: bdm110 Date: Fri, 26 Jun 2026 12:38:45 +0800 Subject: [PATCH 05/25] refactor: replace CreateFactories with InitializeContentSystem for improved clarity and consistency --- .../citron_emu/utils/DirectoryInitialization.kt | 2 +- src/android/app/src/main/jni/native.cpp | 4 ++-- src/citron/main.cpp | 12 ++++++------ src/citron_cmd/citron.cpp | 2 +- src/core/hle/service/filesystem/filesystem.cpp | 14 ++++++++++++-- src/core/hle/service/filesystem/filesystem.h | 7 ++++--- src/core/hle/service/services.cpp | 2 +- 7 files changed, 27 insertions(+), 16 deletions(-) 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 dcb0ea17f3..9447fa6283 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 @@ -24,8 +24,8 @@ object DirectoryInitialization { if (!areDirectoriesReady) { initializeInternalStorage() NativeConfig.initializeGlobalConfig() - NativeLibrary.initializeSystem(false) migrateSettings() + 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 0b528ee216..595afd3f75 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -241,7 +241,7 @@ void EmulationSession::InitializeSystem(bool reload) { m_system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::FrontendManual, m_manual_provider.get()); fs_controller.SetInitStage(Service::FileSystem::InitStage::FS_READY); - fs_controller.CreateFactories(*m_vfs); + fs_controller.InitializeContentSystem(*m_vfs); } void EmulationSession::SetAppletId(int applet_id) { @@ -912,7 +912,7 @@ jboolean Java_org_citron_citron_1emu_NativeLibrary_areKeysPresent(JNIEnv* env, j } auto& system = session.System(); - system.GetFileSystemController().CreateFactories(*system.GetFilesystem()); + system.GetFileSystemController().InitializeContentSystem(*system.GetFilesystem()); Core::Crypto::KeyManager::Instance().ReloadKeys(); return ContentManager::AreKeysPresent(); } 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/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index 41b174867e..736b9ccedd 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -811,10 +811,20 @@ 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 (init_stage < InitStage::FS_READY) { - LOG_WARNING(Service_FS, "CreateFactories skipped: init_stage={}", - static_cast(init_stage)); + LOG_ERROR(Service_FS, "CreateFactories called too early: init_stage={}", + static_cast(init_stage)); return; } diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index ced11495db..908c5fb394 100644 --- a/src/core/hle/service/filesystem/filesystem.h +++ b/src/core/hle/service/filesystem/filesystem.h @@ -137,9 +137,9 @@ 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; @@ -152,6 +152,7 @@ public: private: std::shared_ptr CreateSaveDataFactory(ProgramId program_id); + void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite); struct Registration { ProgramId program_id; 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(); From 5169d4019f8c92aa8bd12789c5b1dc0bb7cc267b Mon Sep 17 00:00:00 2001 From: bdm110 Date: Fri, 26 Jun 2026 13:40:28 +0800 Subject: [PATCH 06/25] refactor: reload keys during system initialization and enhance logging for key management --- .../utils/DirectoryInitialization.kt | 1 + src/android/app/src/main/jni/native.cpp | 28 +++++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) 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 9447fa6283..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 @@ -25,6 +25,7 @@ object DirectoryInitialization { initializeInternalStorage() 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 595afd3f75..b5dec03e83 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -233,6 +233,10 @@ void EmulationSession::InitializeSystem(bool reload) { m_input_subsystem.Initialize(); } + Core::Crypto::KeyManager::Instance().ReloadKeys(); + LOG_INFO(Frontend, "InitializeSystem: keys_loaded_before_content={}", + Core::Crypto::KeyManager::Instance().AreKeysLoaded()); + // Initialize filesystem. m_system.SetFilesystem(m_vfs); m_system.GetUserChannel().clear(); @@ -577,8 +581,23 @@ 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(); + + bool refreshed_content = false; + if (keys_loaded && !session.IsRunning()) { + auto& system = session.System(); + if (auto filesystem = system.GetFilesystem(); filesystem != nullptr) { + system.GetFileSystemController().InitializeContentSystem(*filesystem); + refreshed_content = true; + } + } + + 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) { @@ -912,8 +931,13 @@ jboolean Java_org_citron_citron_1emu_NativeLibrary_areKeysPresent(JNIEnv* env, j } auto& system = session.System(); - system.GetFileSystemController().InitializeContentSystem(*system.GetFilesystem()); Core::Crypto::KeyManager::Instance().ReloadKeys(); + bool refreshed_content = false; + if (auto filesystem = system.GetFilesystem(); filesystem != nullptr) { + system.GetFileSystemController().InitializeContentSystem(*filesystem); + refreshed_content = true; + } + LOG_INFO(Frontend, "areKeysPresent: refreshed_content={}", refreshed_content); return ContentManager::AreKeysPresent(); } From 242058a019598b9996d7cdd18cc18c159f5ae469 Mon Sep 17 00:00:00 2001 From: bdm110 Date: Fri, 26 Jun 2026 14:31:45 +0800 Subject: [PATCH 07/25] refactor: streamline content provider management and enhance thread safety in registered cache --- src/core/file_sys/patch_manager.cpp | 232 ++++++++---------- src/core/file_sys/registered_cache.cpp | 46 ++++ src/core/file_sys/registered_cache.h | 10 + .../hle/service/filesystem/filesystem.cpp | 78 +++--- 4 files changed, 206 insertions(+), 160 deletions(-) diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 21f93dd4a0..0ac76a3679 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -238,10 +238,8 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { const auto* content_provider_union = static_cast(&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); @@ -575,10 +573,9 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs const auto* content_provider_union = static_cast(&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); @@ -758,84 +755,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); } } @@ -1091,50 +1083,46 @@ PatchManager::ActiveUpdate PatchManager::GetActiveUpdate() const { const auto* content_provider_union = static_cast(&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; } } } @@ -1238,12 +1226,10 @@ PatchManager::Metadata PatchManager::GetControlMetadata() const { const auto* content_provider_union = static_cast(&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 { diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index ff8e40cf78..85ab9bb9f9 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 @@ -854,6 +856,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); @@ -862,6 +865,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; @@ -870,14 +874,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; @@ -887,6 +902,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; @@ -899,6 +915,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; @@ -912,6 +929,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; @@ -925,6 +943,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; @@ -938,6 +957,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; @@ -955,6 +975,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; @@ -975,6 +996,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; @@ -996,6 +1018,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); @@ -1008,6 +1031,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..948688cf37 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" @@ -231,6 +234,8 @@ public: ~ContentProviderUnion() override; void SetSlot(ContentProviderUnionSlot slot, ContentProvider* provider); + void SetSlots( + std::initializer_list> slots); void ClearSlot(ContentProviderUnionSlot slot); void Refresh() override; @@ -251,10 +256,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/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index 736b9ccedd..b0e04af724 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -828,17 +828,6 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove return; } - if (overwrite) { - system.ClearContentProvider(FileSys::ContentProviderUnionSlot::SysNAND); - system.ClearContentProvider(FileSys::ContentProviderUnionSlot::UserNAND); - system.ClearContentProvider(FileSys::ContentProviderUnionSlot::SDMC); - system.ClearContentProvider(FileSys::ContentProviderUnionSlot::External); - - bis_factory = nullptr; - sdmc_factory = nullptr; - external_provider = nullptr; - } - 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"; @@ -864,36 +853,34 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove load_directory ? load_directory->GetFullPath() : "", dump_directory ? dump_directory->GetFullPath() : ""); - if (bis_factory == nullptr) { - bis_factory = std::make_unique( + 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)); + active_bis_factory = new_bis_factory.get(); } - if (bis_factory != nullptr) { - system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::SysNAND, - bis_factory->GetSystemNANDContents()); - system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::UserNAND, - bis_factory->GetUserNANDContents()); - - const auto* sysnand = bis_factory->GetSystemNANDContents(); - const auto* usernand = bis_factory->GetUserNANDContents(); + if (active_bis_factory != nullptr) { + const auto* sysnand = active_bis_factory->GetSystemNANDContents(); + const auto* usernand = active_bis_factory->GetUserNANDContents(); LOG_INFO(Service_FS, - "CreateFactories: registered NAND providers, sys_entries={}, user_entries={}", + "CreateFactories: prepared NAND providers, sys_entries={}, user_entries={}", sysnand ? sysnand->ListEntriesFilter().size() : 0, usernand ? usernand->ListEntriesFilter().size() : 0); } - if (sdmc_factory == nullptr) { - sdmc_factory = std::make_unique(std::move(sd_directory), - std::move(sd_load_directory)); + 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(); } - if (sdmc_factory != nullptr) { - system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::SDMC, - sdmc_factory->GetSDMCContents()); - } - - if (external_provider == nullptr) { + 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); @@ -901,13 +888,28 @@ 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)); + active_external_provider = new_external_provider.get(); } - if (external_provider != nullptr) { - system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::External, - 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 @@ -928,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() { From 12045dcedcb8f618b87e57d305f8541ebbc85364 Mon Sep 17 00:00:00 2001 From: bdm110 Date: Fri, 26 Jun 2026 15:23:30 +0800 Subject: [PATCH 08/25] refactor: add RefreshContentSystemLocked method for improved content system management --- src/android/app/src/main/jni/native.cpp | 12 ++++++++++-- src/android/app/src/main/jni/native.h | 1 + 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index b5dec03e83..3656cff559 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -248,6 +248,14 @@ void EmulationSession::InitializeSystem(bool reload) { fs_controller.InitializeContentSystem(*m_vfs); } +void EmulationSession::RefreshContentSystemLocked() { + std::scoped_lock lock(m_mutex); + auto& fs = m_system.GetFileSystemController(); + if (auto filesystem = m_system.GetFilesystem()) { + fs.InitializeContentSystem(*filesystem); + } +} + void EmulationSession::SetAppletId(int applet_id) { m_applet_id = applet_id; m_system.GetFrontendAppletHolder().SetCurrentAppletId( @@ -589,7 +597,7 @@ jboolean Java_org_citron_citron_1emu_NativeLibrary_reloadKeys(JNIEnv* env, jclas if (keys_loaded && !session.IsRunning()) { auto& system = session.System(); if (auto filesystem = system.GetFilesystem(); filesystem != nullptr) { - system.GetFileSystemController().InitializeContentSystem(*filesystem); + session.RefreshContentSystemLocked(); refreshed_content = true; } } @@ -934,7 +942,7 @@ jboolean Java_org_citron_citron_1emu_NativeLibrary_areKeysPresent(JNIEnv* env, j Core::Crypto::KeyManager::Instance().ReloadKeys(); bool refreshed_content = false; if (auto filesystem = system.GetFilesystem(); filesystem != nullptr) { - system.GetFileSystemController().InitializeContentSystem(*filesystem); + session.RefreshContentSystemLocked(); refreshed_content = true; } LOG_INFO(Frontend, "areKeysPresent: refreshed_content={}", refreshed_content); diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h index 6a4551ada2..8ca1679443 100644 --- a/src/android/app/src/main/jni/native.h +++ b/src/android/app/src/main/jni/native.h @@ -46,6 +46,7 @@ public: const Core::PerfStatsResults& PerfStats(); void ConfigureFilesystemProvider(const std::string& filepath); void InitializeSystem(bool reload); + void RefreshContentSystemLocked(); void SetAppletId(int applet_id); Core::SystemResultStatus InitializeEmulation(const std::string& filepath, const std::size_t program_index, From 6c1a15b2b290fc8a4ef410d060b0e167a8c282cb Mon Sep 17 00:00:00 2001 From: bdm110 Date: Fri, 26 Jun 2026 15:48:47 +0800 Subject: [PATCH 09/25] refactor: introduce GetUnionProvider function and enhance ContentProvider interface for clarity --- src/core/file_sys/patch_manager.cpp | 23 +++++++++++++---------- src/core/file_sys/registered_cache.h | 4 ++++ 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 0ac76a3679..42dfd5f662 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 +inline 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,8 +243,7 @@ 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) { best_update_raw = content_provider_union->GetExternalEntryForVersion( update_tid, ContentRecordType::Program, best_version); @@ -570,8 +577,7 @@ 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) { best_update_raw = content_provider_union->GetExternalEntryForVersion(update_tid, type, @@ -705,8 +711,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) { @@ -1080,8 +1085,7 @@ 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 update_tid = GetUpdateTitleID(title_id); const auto updates = content_provider_union->ListExternalUpdateVersions(update_tid); @@ -1223,8 +1227,7 @@ PatchManager::Metadata PatchManager::GetControlMetadata() const { 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) { auto control_file = content_provider_union->GetExternalEntryForVersion( update_tid, ContentRecordType::Control, active_update.version); diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h index 948688cf37..a04e5e662c 100644 --- a/src/core/file_sys/registered_cache.h +++ b/src/core/file_sys/registered_cache.h @@ -80,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; @@ -233,6 +235,8 @@ 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); From 44e51f96354c9768a0dc95336e7b90dfac63d42f Mon Sep 17 00:00:00 2001 From: bdm110 Date: Fri, 26 Jun 2026 15:54:32 +0800 Subject: [PATCH 10/25] refactor: rename RefreshContentSystemLocked to RefreshContentSystem for consistency --- src/android/app/src/main/jni/native.cpp | 6 +++--- src/android/app/src/main/jni/native.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 3656cff559..6eea6fdaf7 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -248,7 +248,7 @@ void EmulationSession::InitializeSystem(bool reload) { fs_controller.InitializeContentSystem(*m_vfs); } -void EmulationSession::RefreshContentSystemLocked() { +void EmulationSession::RefreshContentSystem() { std::scoped_lock lock(m_mutex); auto& fs = m_system.GetFileSystemController(); if (auto filesystem = m_system.GetFilesystem()) { @@ -597,7 +597,7 @@ jboolean Java_org_citron_citron_1emu_NativeLibrary_reloadKeys(JNIEnv* env, jclas if (keys_loaded && !session.IsRunning()) { auto& system = session.System(); if (auto filesystem = system.GetFilesystem(); filesystem != nullptr) { - session.RefreshContentSystemLocked(); + session.RefreshContentSystem(); refreshed_content = true; } } @@ -942,7 +942,7 @@ jboolean Java_org_citron_citron_1emu_NativeLibrary_areKeysPresent(JNIEnv* env, j Core::Crypto::KeyManager::Instance().ReloadKeys(); bool refreshed_content = false; if (auto filesystem = system.GetFilesystem(); filesystem != nullptr) { - session.RefreshContentSystemLocked(); + session.RefreshContentSystem(); refreshed_content = true; } LOG_INFO(Frontend, "areKeysPresent: refreshed_content={}", refreshed_content); diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h index 8ca1679443..0f45bc2e75 100644 --- a/src/android/app/src/main/jni/native.h +++ b/src/android/app/src/main/jni/native.h @@ -46,7 +46,7 @@ public: const Core::PerfStatsResults& PerfStats(); void ConfigureFilesystemProvider(const std::string& filepath); void InitializeSystem(bool reload); - void RefreshContentSystemLocked(); + void RefreshContentSystem(); void SetAppletId(int applet_id); Core::SystemResultStatus InitializeEmulation(const std::string& filepath, const std::size_t program_index, From d8f28c0257e19daa2d24538f7b08a655dc7211e7 Mon Sep 17 00:00:00 2001 From: bdm110 Date: Fri, 26 Jun 2026 16:09:51 +0800 Subject: [PATCH 11/25] refactor: add RefreshContentIfIdle method to optimize content refresh logic --- src/android/app/src/main/jni/native.cpp | 48 ++++++++++++++----------- src/android/app/src/main/jni/native.h | 1 + 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 6eea6fdaf7..22e059d27c 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -256,6 +256,22 @@ void EmulationSession::RefreshContentSystem() { } } +bool EmulationSession::RefreshContentIfIdle(bool keys_loaded) +{ + std::scoped_lock lock(m_mutex); + + if (!keys_loaded || m_is_running) + return false; + + auto& system = System(); + if (auto filesystem = system.GetFilesystem(); filesystem != nullptr) { + RefreshContentSystem(); + return true; + } + + return false; +} + void EmulationSession::SetAppletId(int applet_id) { m_applet_id = applet_id; m_system.GetFrontendAppletHolder().SetCurrentAppletId( @@ -593,17 +609,10 @@ jboolean Java_org_citron_citron_1emu_NativeLibrary_reloadKeys(JNIEnv* env, jclas Core::Crypto::KeyManager::Instance().ReloadKeys(); const bool keys_loaded = Core::Crypto::KeyManager::Instance().AreKeysLoaded(); - bool refreshed_content = false; - if (keys_loaded && !session.IsRunning()) { - auto& system = session.System(); - if (auto filesystem = system.GetFilesystem(); filesystem != nullptr) { - session.RefreshContentSystem(); - refreshed_content = true; - } - } - - LOG_INFO(Frontend, "reloadKeys: keys_loaded={}, refreshed_content={}", keys_loaded, - refreshed_content); + 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); } @@ -934,18 +943,15 @@ void Java_org_citron_citron_1emu_NativeLibrary_clearFilesystemProvider(JNIEnv* e jboolean Java_org_citron_citron_1emu_NativeLibrary_areKeysPresent(JNIEnv* env, jobject jobj) { auto& session = EmulationSession::GetInstance(); - if (session.IsRunning()) { - return ContentManager::AreKeysPresent(); - } - auto& system = session.System(); Core::Crypto::KeyManager::Instance().ReloadKeys(); - bool refreshed_content = false; - if (auto filesystem = system.GetFilesystem(); filesystem != nullptr) { - session.RefreshContentSystem(); - refreshed_content = true; - } - LOG_INFO(Frontend, "areKeysPresent: refreshed_content={}", refreshed_content); + 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 0f45bc2e75..39f331cf46 100644 --- a/src/android/app/src/main/jni/native.h +++ b/src/android/app/src/main/jni/native.h @@ -47,6 +47,7 @@ public: void ConfigureFilesystemProvider(const std::string& filepath); void InitializeSystem(bool reload); void RefreshContentSystem(); + bool RefreshContentIfIdle(bool keys_loaded); void SetAppletId(int applet_id); Core::SystemResultStatus InitializeEmulation(const std::string& filepath, const std::size_t program_index, From dec7a1e1b260dab41c4818e357d3c0533a8543d2 Mon Sep 17 00:00:00 2001 From: bdm110 Date: Fri, 26 Jun 2026 16:18:43 +0800 Subject: [PATCH 12/25] refactor: change GetUnionProvider to a non-inline function for improved readability --- src/core/file_sys/patch_manager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 42dfd5f662..4e2c285895 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -47,7 +47,7 @@ namespace FileSys { namespace { // patch_manager.cpp only -inline const ContentProviderUnion* GetUnionProvider(const ContentProvider& p) { + const ContentProviderUnion* GetUnionProvider(const ContentProvider& p) { return p.IsUnionProvider() ? static_cast(&p) : nullptr; From e58f8344cdbfbdca1bd17257298291ce17856a04 Mon Sep 17 00:00:00 2001 From: bdm110 Date: Fri, 26 Jun 2026 16:41:24 +0800 Subject: [PATCH 13/25] refactor: separate RefreshContentSystemUnlocked method for improved content refresh handling --- src/android/app/src/main/jni/native.cpp | 23 ++++++++++++----------- src/android/app/src/main/jni/native.h | 1 + 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 22e059d27c..3ca16a4709 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -248,28 +248,29 @@ void EmulationSession::InitializeSystem(bool reload) { fs_controller.InitializeContentSystem(*m_vfs); } -void EmulationSession::RefreshContentSystem() { - std::scoped_lock lock(m_mutex); + +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) { - std::scoped_lock lock(m_mutex); - if (!keys_loaded || m_is_running) return false; - auto& system = System(); - if (auto filesystem = system.GetFilesystem(); filesystem != nullptr) { - RefreshContentSystem(); - return true; - } - - return false; + RefreshContentSystem(); + return true; } void EmulationSession::SetAppletId(int applet_id) { diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h index 39f331cf46..e35c43cc4e 100644 --- a/src/android/app/src/main/jni/native.h +++ b/src/android/app/src/main/jni/native.h @@ -60,6 +60,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); From ce92d0f5a87e181e8fa3eea9c2b75e3ecb6a76e4 Mon Sep 17 00:00:00 2001 From: bdm110 Date: Fri, 26 Jun 2026 16:55:57 +0800 Subject: [PATCH 14/25] refactor: enhance filesystem initialization methods and update registered cache processing --- src/android/app/src/main/jni/native.cpp | 13 +++++++++ src/android/app/src/main/jni/native.h | 3 +++ .../app/src/main/jni/native_config.cpp | 7 ++--- src/core/file_sys/registered_cache.cpp | 27 +++++++++++-------- src/core/file_sys/registered_cache.h | 5 ++-- 5 files changed, 37 insertions(+), 18 deletions(-) diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 3ca16a4709..05df0202a2 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -273,6 +273,19 @@ bool EmulationSession::RefreshContentIfIdle(bool keys_loaded) 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) { m_applet_id = applet_id; m_system.GetFrontendAppletHolder().SetCurrentAppletId( diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h index e35c43cc4e..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" @@ -48,6 +49,8 @@ public: 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, diff --git a/src/android/app/src/main/jni/native_config.cpp b/src/android/app/src/main/jni/native_config.cpp index 9153e617af..a29d965bea 100644 --- a/src/android/app/src/main/jni/native_config.cpp +++ b/src/android/app/src/main/jni/native_config.cpp @@ -21,14 +21,11 @@ std::unique_ptr per_game_config; namespace { void SetFilesystemInitStage(Service::FileSystem::InitStage stage) { - EmulationSession::GetInstance().System().GetFileSystemController().SetInitStage(stage); + EmulationSession::GetInstance().SetFilesystemInitStage(stage); } void PromoteFilesystemInitStage(Service::FileSystem::InitStage stage) { - auto& controller = EmulationSession::GetInstance().System().GetFileSystemController(); - if (controller.GetInitStage() < stage) { - controller.SetInitStage(stage); - } + EmulationSession::GetInstance().PromoteFilesystemInitStage(stage); } } // namespace diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index 85ab9bb9f9..192a84cdbc 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -426,7 +426,8 @@ 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); @@ -444,14 +445,14 @@ void RegisteredCache::ProcessFiles(const std::vector& ids) { if (section0_file->GetExtension() != "cnmt") continue; - meta.insert_or_assign(nca->GetTitleId(), CNMT(section0_file)); - meta_id.insert_or_assign(nca->GetTitleId(), id); + out_meta.insert_or_assign(nca->GetTitleId(), CNMT(section0_file)); + out_meta_id.insert_or_assign(nca->GetTitleId(), id); break; } } } -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; @@ -463,7 +464,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)); } } @@ -472,13 +473,17 @@ void RegisteredCache::Refresh() { return; } - meta.clear(); - meta_id.clear(); - citron_meta.clear(); - 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={}", diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h index a04e5e662c..952ed1eb25 100644 --- a/src/core/file_sys/registered_cache.h +++ b/src/core/file_sys/registered_cache.h @@ -200,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; From d6ee2d798516bfefc85ae6c7752e029c6bf05839 Mon Sep 17 00:00:00 2001 From: bdm Date: Sun, 28 Jun 2026 13:22:54 +0800 Subject: [PATCH 15/25] fix: enable web service and OpenSSL for core crypto functionality refactor: update logging format in areKeysPresent function --- src/android/app/build.gradle.kts | 4 ++-- src/android/app/src/main/jni/native.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts index 36b7ccbc87..a11c000778 100644 --- a/src/android/app/build.gradle.kts +++ b/src/android/app/build.gradle.kts @@ -163,8 +163,8 @@ android { arguments( "-DENABLE_QT=0", // Don't use QT "-DENABLE_SDL2=0", // Don't use SDL - "-DENABLE_WEB_SERVICE=0", // Don't use telemetry - "-DENABLE_OPENSSL=0", // Don't use OpenSSL + "-DENABLE_WEB_SERVICE=1", // Core crypto requires OpenSSL + "-DENABLE_OPENSSL=1", "-DBUNDLE_SPEEX=ON", "-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work "-DCITRON_USE_BUNDLED_VCPKG=ON", diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 05df0202a2..3b1c05f61f 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -964,7 +964,7 @@ jboolean Java_org_citron_citron_1emu_NativeLibrary_areKeysPresent(JNIEnv* env, j const bool refreshed_content = session.RefreshContentIfIdle(keys_loaded); - LOG_INFO(Frontend, "areKeysPresent: refreshed_content={!}", refreshed_content); + LOG_INFO(Frontend, "areKeysPresent: refreshed_content={}", refreshed_content); return ContentManager::AreKeysPresent(); } From 19c10057042ed06f06f42ea1ad65d4e4c0288c14 Mon Sep 17 00:00:00 2001 From: bdm Date: Sun, 28 Jun 2026 13:41:27 +0800 Subject: [PATCH 16/25] refactor: replace debug logs with warning logs in AddOnContentManager --- .../hle/service/aoc/addon_content_manager.cpp | 43 ++++++++++++++++--- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/src/core/hle/service/aoc/addon_content_manager.cpp b/src/core/hle/service/aoc/addon_content_manager.cpp index affbd72605..b7eb6ac037 100644 --- a/src/core/hle/service/aoc/addon_content_manager.cpp +++ b/src/core/hle/service/aoc/addon_content_manager.cpp @@ -98,8 +98,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]; @@ -111,7 +111,9 @@ Result IAddOnContentManager::CountAddOnContent(Out out_count, ClientProcess *out_count = static_cast( std::count_if(add_on_content.begin(), add_on_content.end(), [current](u64 tid) { return CheckAOCTitleIDMatchesBase(tid, current); })); - + LOG_WARNING(Service_AOC, + "CountAddOnContent: program_id={:016X}, count={}", + current, *out_count); R_SUCCEED(); } @@ -147,6 +149,21 @@ Result IAddOnContentManager::ListAddOnContent(Out out_count, R_UNLESS(out.size() >= offset, ResultUnknown); *out_count = static_cast(std::min(out.size() - offset, 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 reconstructed_title_id = + FileSys::GetAOCBaseTitleID(current) + addon_index; + + LOG_WARNING(Service_AOC, + "ListAddOnContent output[{}]: addon_index={}, title_id={:016X}", + i, addon_index, reconstructed_title_id); + } + std::rotate(out.begin(), out.begin() + offset, out.end()); std::memcpy(out_addons.data(), out.data(), *out_count * sizeof(u32)); @@ -212,8 +229,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 +238,30 @@ 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 reconstructed_title_id = + aoc_base_id + static_cast(static_cast(addon_index)); + + LOG_WARNING(Service_AOC, + "PrepareAddOnContent: program_id={:016X}, aoc_base={:016X}, " + "addon_index={}, title_id={:016X}, process_id={}", + program_id, aoc_base_id, addon_index, reconstructed_title_id, process_id.pid); R_SUCCEED(); From 35f94142b1ca092e53a811fd24f24726018a1c1c Mon Sep 17 00:00:00 2001 From: bdm Date: Sun, 28 Jun 2026 14:33:54 +0800 Subject: [PATCH 17/25] fix: correct path formatting in GetRelativePathFromNcaID function --- src/core/file_sys/registered_cache.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index 192a84cdbc..eb3bc94d92 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -73,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)); } From c70fabc8901708280fb7e128e916a711b344cd0c Mon Sep 17 00:00:00 2001 From: bdm Date: Sun, 28 Jun 2026 15:27:09 +0800 Subject: [PATCH 18/25] refactor: improve logging and buffer count calculation in AddOnContentManager and FSP_SRV --- .../hle/service/aoc/addon_content_manager.cpp | 30 ++++++++++--- .../hle/service/filesystem/fsp/fsp_srv.cpp | 42 ++++++++++++++++++- 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/src/core/hle/service/aoc/addon_content_manager.cpp b/src/core/hle/service/aoc/addon_content_manager.cpp index b7eb6ac037..3414d1c229 100644 --- a/src/core/hle/service/aoc/addon_content_manager.cpp +++ b/src/core/hle/service/aoc/addon_content_manager.cpp @@ -148,11 +148,13 @@ 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()); + "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]; @@ -219,7 +221,25 @@ 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 reconstructed_title_id = + FileSys::GetAOCBaseTitleID(current) + addon_index; + + LOG_WARNING(Service_AOC, + "ListAddOnContentByApplicationId output[{}]: addon_index={}, " + "title_id={:016X}", + i, addon_index, reconstructed_title_id); + } + std::rotate(out.begin(), out.begin() + offset, out.end()); std::memcpy(out_addons.data(), out.data(), *out_count * sizeof(u32)); diff --git a/src/core/hle/service/filesystem/fsp/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp/fsp_srv.cpp index 0b3ad96f58..79f4f95fca 100644 --- a/src/core/hle/service/filesystem/fsp/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp/fsp_srv.cpp @@ -16,6 +16,7 @@ #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" @@ -490,8 +491,10 @@ 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); @@ -503,12 +506,47 @@ Result FSP_SRV::OpenDataStorageByDataId(OutInterface out_interface, R_SUCCEED(); } + 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); + if (title_id >= requested_aoc_base && + title_id <= requested_aoc_base + FileSys::AOC_TITLE_ID_MASK) { + std::size_t matching_aoc_count = 0; + const auto entries = content_provider.ListEntriesFilter( + FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); + for (const auto& entry : entries) { + if (FileSys::GetBaseTitleID(entry.title_id) != requested_base) { + continue; + } + + ++matching_aoc_count; + LOG_WARNING(Service_FS, + "OpenDataStorageByDataId miss context: installed_aoc_index={}, " + "installed_title_id={:016X}", + FileSys::GetAOCID(entry.title_id), entry.title_id); + } + + LOG_WARNING(Service_FS, + "OpenDataStorageByDataId AOC miss: requested_base={:016X}, " + "requested_aoc_base={:016X}, requested_aoc_index={}, " + "matching_installed_aoc_count={}", + requested_base, requested_aoc_base, requested_aoc_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 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={}, " + "title_id={:016X}", + FileSys::GetBaseTitleID(title_id), FileSys::GetAOCID(title_id), title_id); + } + const FileSys::PatchManager pm{title_id, fsc, content_provider}; auto base = From 2b7311a9c66911dc05d624175bc513e378760fba Mon Sep 17 00:00:00 2001 From: bdm Date: Sun, 28 Jun 2026 15:45:28 +0800 Subject: [PATCH 19/25] fix: update OpenSSL platform restrictions in vcpkg.json --- src/core/hle/service/filesystem/fsp/fsp_srv.cpp | 1 + vcpkg.json | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/core/hle/service/filesystem/fsp/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp/fsp_srv.cpp index 79f4f95fca..e68055e06f 100644 --- a/src/core/hle/service/filesystem/fsp/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp/fsp_srv.cpp @@ -23,6 +23,7 @@ #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" diff --git a/vcpkg.json b/vcpkg.json index 714382d03a..e5904551de 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -37,7 +37,7 @@ "openal-soft", { "name": "openssl", - "platform": "!linux & !osx & !android" + "platform": "!linux & !osx" }, { "name": "opus", @@ -56,7 +56,7 @@ "dependencies": [ { "name": "openssl", - "platform": "!linux & !osx & !android" + "platform": "!linux & !osx" } ] }, From 5bd5d106d35e8a13ea231f973af5c7317c517c62 Mon Sep 17 00:00:00 2001 From: bdm Date: Sun, 28 Jun 2026 16:13:13 +0800 Subject: [PATCH 20/25] refactor: simplify layout direction check in HomeSettingsFragment --- .../org/citron/citron_emu/fragments/HomeSettingsFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) From c5f396a134b0b2b2d05e77acdf366c94c26e2af6 Mon Sep 17 00:00:00 2001 From: bdm Date: Sun, 28 Jun 2026 16:18:59 +0800 Subject: [PATCH 21/25] refactor: enhance logging in RegisteredCache::ProcessFiles for better error tracking --- src/core/file_sys/registered_cache.cpp | 46 +++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index eb3bc94d92..4dae985105 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -431,24 +431,62 @@ void RegisteredCache::ProcessFiles(const std::vector& ids, std::map(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; - out_meta.insert_or_assign(nca->GetTitleId(), CNMT(section0_file)); + CNMT cnmt(section0_file); + LOG_INFO(Service_FS, + "RegisteredCache ProcessFiles: parsed meta nca_id={}, title_id={:016X}, " + "records={}", + Common::HexToString(id, false), cnmt.GetTitleID(), + cnmt.GetContentRecords().size()); + + out_meta.insert_or_assign(nca->GetTitleId(), std::move(cnmt)); out_meta_id.insert_or_assign(nca->GetTitleId(), 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()); + } } } From ee8f1b90814075db44f12c4e2f2151fc6577bf2b Mon Sep 17 00:00:00 2001 From: bdm Date: Sun, 28 Jun 2026 16:46:51 +0800 Subject: [PATCH 22/25] fix: update logging in RegisteredCache::ProcessFiles for title ID clarity --- src/core/file_sys/registered_cache.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index 4dae985105..47c0ca3ec0 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -469,14 +469,15 @@ void RegisteredCache::ProcessFiles(const std::vector& ids, std::mapGetTitleId(), title_id, cnmt.GetContentRecords().size()); - out_meta.insert_or_assign(nca->GetTitleId(), std::move(cnmt)); - out_meta_id.insert_or_assign(nca->GetTitleId(), id); + out_meta.insert_or_assign(title_id, std::move(cnmt)); + out_meta_id.insert_or_assign(title_id, id); found_cnmt = true; break; } From 016bdf53b9992db621f2bce9ba61314be807ff30 Mon Sep 17 00:00:00 2001 From: bdm Date: Sun, 28 Jun 2026 17:23:38 +0800 Subject: [PATCH 23/25] refactor: enhance logging in RegisteredCache::ProcessFiles for better title type and record tracking --- src/core/file_sys/patch_manager.cpp | 20 +++++++++++++++++++- src/core/file_sys/registered_cache.cpp | 19 +++++++++++++++++-- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 4e2c285895..df950da0fb 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -588,6 +588,14 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs } } + 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 @@ -1224,6 +1232,10 @@ 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) { @@ -1238,14 +1250,20 @@ PatchManager::Metadata PatchManager::GetControlMetadata() const { } 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 47c0ca3ec0..31fa2c97a1 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -472,9 +472,24 @@ void RegisteredCache::ProcessFiles(const std::vector& ids, std::mapGetTitleId(), title_id, - cnmt.GetContentRecords().size()); + 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); From 32f21c8e719cc2d60194c86479b30fb22a68a219 Mon Sep 17 00:00:00 2001 From: bdm Date: Sun, 28 Jun 2026 18:15:40 +0800 Subject: [PATCH 24/25] feat: implement ticket persistence in KeyManager and update NSP ticket handling --- src/android/app/src/main/jni/native.cpp | 1 + src/core/crypto/key_manager.cpp | 123 +++++++++++++++++++++-- src/core/crypto/key_manager.h | 1 + src/core/file_sys/submission_package.cpp | 4 + 4 files changed, 120 insertions(+), 9 deletions(-) diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 3b1c05f61f..3a8e610895 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -234,6 +234,7 @@ void EmulationSession::InitializeSystem(bool reload) { } Core::Crypto::KeyManager::Instance().ReloadKeys(); + Core::Crypto::KeyManager::Instance().PopulateTickets(); LOG_INFO(Frontend, "InitializeSystem: keys_loaded_before_content={}", Core::Crypto::KeyManager::Instance().AreKeysLoaded()); 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/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()); + } } } From fb234d8196d62182c2616de8a2263ef4d2205454 Mon Sep 17 00:00:00 2001 From: bdm Date: Sun, 28 Jun 2026 18:59:24 +0800 Subject: [PATCH 25/25] refactor: improve AOC title ID handling and logging in FSP_SRV and IAddOnContentManager --- src/android/app/src/main/jni/native.cpp | 10 ++- .../hle/service/aoc/addon_content_manager.cpp | 83 ++++++++++--------- .../hle/service/filesystem/fsp/fsp_srv.cpp | 71 +++++++++++----- 3 files changed, 104 insertions(+), 60 deletions(-) diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 3a8e610895..2bce197fe5 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -267,10 +267,16 @@ void EmulationSession::RefreshContentSystem() { bool EmulationSession::RefreshContentIfIdle(bool keys_loaded) { - if (!keys_loaded || m_is_running) + if (!keys_loaded) { return false; + } - RefreshContentSystem(); + std::scoped_lock lock(m_mutex); + if (m_is_running || m_load_result == Core::SystemResultStatus::Success) { + return false; + } + + RefreshContentSystemUnlocked(); return true; } diff --git a/src/core/hle/service/aoc/addon_content_manager.cpp b/src/core/hle/service/aoc/addon_content_manager.cpp index 3414d1c229..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"} { @@ -108,9 +124,7 @@ 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); @@ -126,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); @@ -158,12 +168,11 @@ Result IAddOnContentManager::ListAddOnContent(Out out_count, for (u32 i = 0; i < *out_count; ++i) { const auto addon_index = out[offset + i]; - const auto reconstructed_title_id = - FileSys::GetAOCBaseTitleID(current) + addon_index; + const auto physical_title_id = matching_aocs[addon_index - 1]; LOG_WARNING(Service_AOC, - "ListAddOnContent output[{}]: addon_index={}, title_id={:016X}", - i, addon_index, reconstructed_title_id); + "ListAddOnContent output[{}]: addon_index={}, physical_title_id={:016X}", + i, addon_index, physical_title_id); } std::rotate(out.begin(), out.begin() + offset, out.end()); @@ -185,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(); } @@ -201,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); @@ -231,13 +236,12 @@ Result IAddOnContentManager::ListAddOnContentByApplicationId( for (u32 i = 0; i < *out_count; ++i) { const auto addon_index = out[offset + i]; - const auto reconstructed_title_id = - FileSys::GetAOCBaseTitleID(current) + addon_index; + const auto physical_title_id = matching_aocs[addon_index - 1]; LOG_WARNING(Service_AOC, "ListAddOnContentByApplicationId output[{}]: addon_index={}, " - "title_id={:016X}", - i, addon_index, reconstructed_title_id); + "physical_title_id={:016X}", + i, addon_index, physical_title_id); } std::rotate(out.begin(), out.begin() + offset, out.end()); @@ -275,13 +279,16 @@ Result IAddOnContentManager::GetAddOnContentBaseId(Out out_title_id, Result IAddOnContentManager::PrepareAddOnContent(s32 addon_index, ClientProcessId process_id) { const auto program_id = system.GetApplicationProcessProgramID(); const auto aoc_base_id = FileSys::GetAOCBaseTitleID(program_id); - const auto reconstructed_title_id = - aoc_base_id + static_cast(static_cast(addon_index)); + 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={}, title_id={:016X}, process_id={}", - program_id, aoc_base_id, addon_index, reconstructed_title_id, + "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/fsp/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp/fsp_srv.cpp index e68055e06f..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 @@ -51,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()} { @@ -497,7 +513,30 @@ Result FSP_SRV::OpenDataStorageByDataId(OutInterface out_interface, "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); @@ -507,31 +546,22 @@ Result FSP_SRV::OpenDataStorageByDataId(OutInterface out_interface, R_SUCCEED(); } - 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); - if (title_id >= requested_aoc_base && - title_id <= requested_aoc_base + FileSys::AOC_TITLE_ID_MASK) { + if (requested_is_aoc) { std::size_t matching_aoc_count = 0; - const auto entries = content_provider.ListEntriesFilter( - FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); - for (const auto& entry : entries) { - if (FileSys::GetBaseTitleID(entry.title_id) != requested_base) { - continue; - } - + 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}", - FileSys::GetAOCID(entry.title_id), entry.title_id); + matching_aoc_count, entry_title_id); } LOG_WARNING(Service_FS, "OpenDataStorageByDataId AOC miss: requested_base={:016X}, " "requested_aoc_base={:016X}, requested_aoc_index={}, " - "matching_installed_aoc_count={}", - requested_base, requested_aoc_base, requested_aoc_id, matching_aoc_count); + "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, @@ -544,14 +574,15 @@ Result FSP_SRV::OpenDataStorageByDataId(OutInterface out_interface, 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={}, " - "title_id={:016X}", - FileSys::GetBaseTitleID(title_id), FileSys::GetAOCID(title_id), title_id); + "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{title_id, fsc, content_provider}; + 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));