From 0fd1e81f5f9cae0ad648a019889caf273212ec17 Mon Sep 17 00:00:00 2001 From: Collecting Date: Fri, 19 Dec 2025 18:48:17 +0000 Subject: [PATCH] fix: NTFS Directory Scanning w/ Linux Signed-off-by: Collecting --- src/citron/game_list_worker.cpp | 233 ++++++++++++++------------------ 1 file changed, 103 insertions(+), 130 deletions(-) diff --git a/src/citron/game_list_worker.cpp b/src/citron/game_list_worker.cpp index 51a7085f4..c6786d9d6 100644 --- a/src/citron/game_list_worker.cpp +++ b/src/citron/game_list_worker.cpp @@ -349,7 +349,19 @@ void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager, const bool HasSupportedFileExtension(const std::string& file_name) { const QFileInfo file = QFileInfo(QString::fromStdString(file_name)); - return GameList::supported_file_extensions.contains(file.suffix(), Qt::CaseInsensitive); + const QString suffix = file.suffix().toLower(); + + // 1. Check if it's a standard game container (.nsp, .xci, etc.) + if (GameList::supported_file_extensions.contains(suffix)) { + return true; + } + + // 2. Only allow .nca if the user explicitly enabled it in the UI Settings + if (suffix == QStringLiteral("nca") && UISettings::values.scan_nca.GetValue()) { + return true; + } + + return false; } bool IsExtractedNCAMain(const std::string& file_name) { @@ -553,127 +565,98 @@ void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir, const std::map void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, bool deep_scan, GameListDir* parent_dir, const std::map>& online_stats) { const auto callback = [this, target, parent_dir, &online_stats](const std::filesystem::path& path) -> bool { - if (stop_requested) { - // Breaks the callback loop. - return false; - } + if (stop_requested) return false; const auto physical_name = Common::FS::PathToUTF8String(path); - const auto is_dir = Common::FS::IsDir(path); - if (!is_dir && - (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) { - // Try to get cached metadata first - const auto* cached_metadata = GetCachedGameMetadata(physical_name); - - const auto file = vfs->OpenFile(physical_name, FileSys::OpenMode::Read); - if (!file) { - return true; - } - - auto loader = Loader::GetLoader(system, file); - if (!loader) { - return true; - } - - const auto file_type = loader->GetFileType(); - if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) { - return true; - } - - u64 program_id = 0; - const auto res2 = loader->ReadProgramId(program_id); - - if (target == ScanTarget::FillManualContentProvider) { - if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) { - provider->AddEntry(FileSys::TitleType::Application, - FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()), - program_id, file); - } else if (res2 == Loader::ResultStatus::Success && - (file_type == Loader::FileType::XCI || - file_type == Loader::FileType::NSP)) { - const auto nsp = file_type == Loader::FileType::NSP - ? std::make_shared(file) - : FileSys::XCI{file}.GetSecurePartitionNSP(); - for (const auto& title : nsp->GetNCAs()) { - for (const auto& entry : title.second) { - provider->AddEntry(entry.first.first, entry.first.second, title.first, - entry.second->GetBaseFile()); - } - } - } - } else { - std::vector program_ids; - loader->ReadProgramIds(program_ids); - - if (res2 == Loader::ResultStatus::Success && program_ids.size() > 1 && - (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) { - for (const auto id : program_ids) { - loader = Loader::GetLoader(system, file, id); - if (!loader) { - continue; - } - - std::vector icon; - [[maybe_unused]] const auto res1 = loader->ReadIcon(icon); - - std::string name = " "; - [[maybe_unused]] const auto res3 = loader->ReadTitle(name); - - const FileSys::PatchManager patch{id, system.GetFileSystemController(), - system.GetContentProvider()}; - - auto entry = MakeGameListEntry( - physical_name, name, Common::FS::GetSize(physical_name), icon, *loader, - id, compatibility_list, play_time_manager, patch, online_stats); - - RecordEvent( - [=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); }); - } - } else { - // Use cached metadata if available, otherwise read from file - std::vector icon; - std::string name = " "; - std::size_t file_size = 0; - - if (cached_metadata && cached_metadata->program_id == program_id) { - // Use cached data - icon = cached_metadata->icon; - name = cached_metadata->title; - file_size = cached_metadata->file_size; - } else { - // Read from file - [[maybe_unused]] const auto res1 = loader->ReadIcon(icon); - [[maybe_unused]] const auto res3 = loader->ReadTitle(name); - file_size = Common::FS::GetSize(physical_name); - - // Cache it for next time - if (res2 == Loader::ResultStatus::Success) { - CacheGameMetadata(physical_name, program_id, file_type, file_size, name, icon); - } - } - - const FileSys::PatchManager patch{program_id, system.GetFileSystemController(), - system.GetContentProvider()}; - - auto entry = MakeGameListEntry( - physical_name, name, file_size, icon, *loader, - program_id, compatibility_list, play_time_manager, patch, online_stats); - - RecordEvent( - [=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); }); - } - } - } else if (is_dir) { - watch_list.append(QString::fromStdString(physical_name)); + // Do not touch the disk if the extension isn't a game + if (!HasSupportedFileExtension(physical_name) && !IsExtractedNCAMain(physical_name)) { + return true; } + // Only now do we do "Heavy" disk I/O + const auto file = vfs->OpenFile(physical_name, FileSys::OpenMode::Read); + if (!file) return true; + + auto loader = Loader::GetLoader(system, file); + if (!loader) return true; + + const auto file_type = loader->GetFileType(); + if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) { + return true; + } + + u64 program_id = 0; + const auto res2 = loader->ReadProgramId(program_id); + + // Handle Content Provider AND Game List in one pass + if (target == ScanTarget::FillManualContentProvider || target == ScanTarget::Both) { + if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) { + provider->AddEntry(FileSys::TitleType::Application, + FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()), + program_id, file); + } else if (res2 == Loader::ResultStatus::Success && + (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) { + const auto nsp = file_type == Loader::FileType::NSP + ? std::make_shared(file) + : FileSys::XCI{file}.GetSecurePartitionNSP(); + for (const auto& title : nsp->GetNCAs()) { + for (const auto& entry : title.second) { + provider->AddEntry(entry.first.first, entry.first.second, title.first, + entry.second->GetBaseFile()); + } + } + } + } + + if (target == ScanTarget::PopulateGameList || target == ScanTarget::Both) { + std::vector program_ids; + loader->ReadProgramIds(program_ids); + + if (res2 == Loader::ResultStatus::Success && program_ids.size() > 1 && + (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) { + for (const auto id : program_ids) { + auto sub_loader = Loader::GetLoader(system, file, id); + if (!sub_loader) continue; + + std::vector icon; + sub_loader->ReadIcon(icon); + std::string name = " "; + sub_loader->ReadTitle(name); + + const FileSys::PatchManager patch{id, system.GetFileSystemController(), system.GetContentProvider()}; + auto entry = MakeGameListEntry(physical_name, name, Common::FS::GetSize(physical_name), icon, *sub_loader, id, compatibility_list, play_time_manager, patch, online_stats); + RecordEvent([=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); }); + } + } else { + const auto* cached_metadata = GetCachedGameMetadata(physical_name); + std::vector icon; + std::string name = " "; + std::size_t file_size = 0; + + if (cached_metadata && cached_metadata->program_id == program_id) { + icon = cached_metadata->icon; + name = cached_metadata->title; + file_size = cached_metadata->file_size; + } else { + loader->ReadIcon(icon); + loader->ReadTitle(name); + file_size = Common::FS::GetSize(physical_name); + if (res2 == Loader::ResultStatus::Success) { + CacheGameMetadata(physical_name, program_id, file_type, file_size, name, icon); + } + } + + const FileSys::PatchManager patch{program_id, system.GetFileSystemController(), system.GetContentProvider()}; + auto entry = MakeGameListEntry(physical_name, name, file_size, icon, *loader, program_id, compatibility_list, play_time_manager, patch, online_stats); + RecordEvent([=](GameList* game_list) { game_list->AddEntry(entry, parent_dir); }); + } + } return true; }; if (deep_scan) { - Common::FS::IterateDirEntriesRecursively(dir_path, callback, - Common::FS::DirEntryFilter::All); + Common::FS::IterateDirEntriesRecursively(dir_path, callback, Common::FS::DirEntryFilter::File); } else { Common::FS::IterateDirEntries(dir_path, callback, Common::FS::DirEntryFilter::File); } @@ -703,30 +686,20 @@ void GameListWorker::run() { }; for (UISettings::GameDir& game_dir : game_dirs) { - if (stop_requested) { - break; - } + if (stop_requested) break; - if (game_dir.path == std::string("SDMC")) { - auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SdmcDir); - DirEntryReady(game_list_dir); - AddTitlesToGameList(game_list_dir, online_stats); - } else if (game_dir.path == std::string("UserNAND")) { - auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::UserNandDir); - DirEntryReady(game_list_dir); - AddTitlesToGameList(game_list_dir, online_stats); - } else if (game_dir.path == std::string("SysNAND")) { - auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SysNandDir); + if (game_dir.path == std::string("SDMC") || game_dir.path == std::string("UserNAND") || game_dir.path == std::string("SysNAND")) { + auto type = (game_dir.path == "SDMC") ? GameListItemType::SdmcDir : (game_dir.path == "UserNAND" ? GameListItemType::UserNandDir : GameListItemType::SysNandDir); + auto* const game_list_dir = new GameListDir(game_dir, type); DirEntryReady(game_list_dir); AddTitlesToGameList(game_list_dir, online_stats); } else { watch_list.append(QString::fromStdString(game_dir.path)); auto* const game_list_dir = new GameListDir(game_dir); DirEntryReady(game_list_dir); - ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path, game_dir.deep_scan, - game_list_dir, online_stats); - ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path, game_dir.deep_scan, - game_list_dir, online_stats); + + // ONE PASS SCAN: Replaces the two separate ScanFileSystem calls + ScanFileSystem(ScanTarget::Both, game_dir.path, game_dir.deep_scan, game_list_dir, online_stats); } }